OK,
Please follow the istructions how to install Aruino IDE and Hubduino Libraries from this thread:
The requred hardware is:
-
Any ESP32 Development Board
Something like this:
Amazon.com: AITRIP 3PCS Type C D1 Mini ESP32 ESP-WROOM-32 CP2104 WLAN WiFi+Bluetooth Internet of Things IoT Development Board for Arduino NodeMCU : Electronics -
Plain RJ45 Connector
Something like this:
Amazon.com: whiteeeen 4pcs RJ45 Breakout Board 8P8C Jack Ethernet Connector with 8Pin 2 Type Pin Headers (Straight and Right Angel) : Electronics -
Any sutable size breadboard
Something like this:
Amazon.com: hiBCTR 64 pcs Green PCB Board - Double Sided Circuit and perf Board with 5 Sizes for DIY Electronics, Compatible with Bread Boards and Soldering Projects : Electronics -
Any small DC-DC Converter with 5V DC Output
Something like this:
Amazon.com: UMLIFE 10PCS High Efficiency Output 3.3V 5V 9V 12V 5A Mini560 Step Down DC-DC Converter Voltage Regulator Buck Stabilized Power Supply Module : Electronics
Wiring is very simple so I did not even bother with schematic.
Here is a Touch2O RJ45 Connector Pins:

DC-DC Converter Input takes power from RJ45, Pin-1
Connect 5V Output to the ESP32 VIN pin.
All GND signals must be connected together.
Here is a coplete Arduino/Hubduino sketch (only Valve Open/Close commands are implemented:
//*******************************************************************************
// File: Touch2O_Controller_HE_ESP32Mini.ino
//
// Summary: This is Delta Touch2O Controller Arduino Sketch
// Only Faucet Open/Close commnds are supported
//
// Change History:
//
// Date Who What
// ---- --- ----
// 11-06-2025 Vitaliy Kholyavenko Original Creation
//
//*******************************************************************************
// SmartThings Libraries
#include <SmartThingsESP32WiFi.h>
// WiFi Credentials
#include "credentials.h"
//======================================
// ESP32 Pins Definition
//======================================
// On Board LED Pin
// This is System Heart Beat LED
#define HB_LED_PIN 2
// UART-2 Pins
#define RXD2_PIN 16
#define TXD2_PIN 17
// Handshake Pin (Request - Acknowelge)
#define HANDSHAKE_PIN 21
//******************************************************************************************
// ESP32 WiFi Information
//******************************************************************************************
// Get Credentials from the credentials.h file
const char* str_ssid = mySSID;
const char* str_password = myPASSWORD;
IPAddress ip (192, 168, 20, 57); // Device IP Address // <---You must edit this line!
IPAddress gateway (192, 168, 20, 1); // Router Gateway // <---You must edit this line!
IPAddress subnet (255, 255, 255, 0); // LAN Subnet Mask // <---You must edit this line!
IPAddress dnsserver(192, 168, 20, 1); // DNS Server // <---You must edit this line!
const unsigned int serverPort = 8090; // Port to run the HTTP Server on
// Hubitat Hub TCP/IP Address
IPAddress hubIp (192, 168, 20, 91); // Hubitat Hub IP // <---You must edit this line!
// Hubitat Hub TCP/IP Address
const unsigned int hubPort = 39501; // Hubitat Nub Port
// Callout function forward decalaration
SmartThingsCallout_t messageCallout;
//Create a SmartThings Ethernet ESP8266WiFi object
st::SmartThingsESP32WiFi smartthing(str_ssid,
str_password,
ip,
gateway,
subnet,
dnsserver,
serverPort,
hubIp,
hubPort,
messageCallout
);
//*****************************************************************************
// Global Variables
//*****************************************************************************
// Timer-0 Interrupt Period in uS
#define Timer0_IRQ_Period 10000
// Timer-1 Interrupt Period in uS
#define Timer1_IRQ_Period 100
// Heart Beat LED Period in uS
#define HB_LED_Period 1000000
uint16_t HB_LED_Counter;
// Timer Flags
volatile bool Timer_Flag_100uS;
volatile bool Timer_Flag_10mS;
volatile bool Timer_Flag_1S;
// Commands from HE
bool HE_Cmd_OpenValve;
bool HE_Cmd_CloseValve;
// Touch2O Heartbeat Parameters
// Time Unit is Number of 100uS Ticks
#define T2O_HB_PERIOD 50000
#define T2O_CMD_PULSE_WIDTH 5
#define T2O_SEND_HB_DELAY 7
bool T2O_SendHB_Flag;
int T2O_HB_Cntr;
bool T2O_SendCmdPulse_Flag;
int T2O_CmdPulse_Cntr;
// Touch2O Heartbeat String
const unsigned char CmdString_Heartbeat[10] = {0xAA, 0x06, 0x09, 0x00, 0x00, 0x15, 0x00, 0x00, 0xFF, 0x67};
// Touch2O Command Strings
const unsigned char CmdString_ValveOpen[10] = {0xAA, 0x06, 0x82, 0x02, 0x00, 0x15, 0x00, 0x00, 0xFE, 0x34};
const unsigned char CmdString_ValveClose[10] = {0xAA, 0x06, 0x82, 0x00, 0x00, 0x15, 0x00, 0x00, 0x7D, 0x70};
// Touch2O Status Strings
const unsigned char StatusString_ValveOpened[7] = {0xAA, 0x03, 0x82, 0x02, 0x00, 0x42, 0xD5};
const unsigned char StatusString_ValveClosed[7] = {0xAA, 0x03, 0x82, 0x00, 0x00, 0x20, 0xB3};
// Status Report Rx Buffer Parameters
#define HEADER_BYTE 0xAA
#define STATUS_ID 0x03
#define HEARTBEAT_ID 0x06
#define ID_INDEX 0x01
#define STATUS_VALVE_OPEN 0x02
#define STATUS_VALVE_CLOSED 0x00
#define HB_PACKET_LEN 10 // Heart Beat Packet
#define STATUS_PACKET_LEN 7 // Status Packet
#define RX_BUFFER_SIZE 9
enum RxState
{
WAIT_HEADER,
RECEIVE_PACKET
};
RxState statePacket = WAIT_HEADER;
uint8_t bufferRx[RX_BUFFER_SIZE] = {0};
uint8_t indexRx = 0;
uint8_t lengthPacket = 0;
//======================================
// ESP32 Timer-0 ISR
//======================================
// Timer-0 Period is 10mS
void Timer0_ISR(void* arg)
{
Timer_Flag_10mS = true;
}
//======================================
// ESP32 Timer-1 ISR
//======================================
// Timer-0 Period is 1mS
void Timer1_ISR(void* arg)
{
Timer_Flag_100uS = true;
}
//======================================
// Setup a Periodic ESP Timer
//======================================
esp_timer_handle_t setupPeriodicTimer(void (*callback)(void*),
uint64_t period_us
)
{
esp_timer_handle_t timer;
const esp_timer_create_args_t args =
{
.callback = callback,
.arg = nullptr,
.dispatch_method = ESP_TIMER_TASK,
.name = "user_timer"
};
esp_timer_create(&args, &timer);
esp_timer_start_periodic(timer, period_us); // in microseconds
return timer;
}
//******************************************************************************************
// Arduino Setup()
//******************************************************************************************
void setup()
{
// Configure IO Pins
pinMode(HB_LED_PIN, OUTPUT);
// Turn OFF On-Board LED
digitalWrite(HB_LED_PIN, LOW);
// Configure HANDSHAKE_PIN and set it to HIGH
pinMode(HANDSHAKE_PIN, OUTPUT_OPEN_DRAIN);
digitalWrite(HANDSHAKE_PIN, HIGH);
gpio_pullup_en((gpio_num_t)HANDSHAKE_PIN);
// Initialize Timers
setupPeriodicTimer(Timer0_ISR, Timer0_IRQ_Period);
setupPeriodicTimer(Timer1_ISR, Timer1_IRQ_Period);
// Reset all variables to the default values
// Reset Timer Flags
Timer_Flag_100uS = false;
Timer_Flag_10mS = false;
Timer_Flag_1S = false;
// Commands from HE
HE_Cmd_OpenValve = false;
HE_Cmd_CloseValve = false;
// Reset Heartbeat Touch2O Counter
T2O_SendHB_Flag = false;
T2O_HB_Cntr = 0;
T2O_SendCmdPulse_Flag = false;
T2O_CmdPulse_Cntr = 0;
// Reset Rx Buffer Index
indexRx = 0;
// Initialize RS232 Port for PC Communication
Serial.begin(115200);
while (!Serial);
// Initialize UART-2 Port for communication with
// the Reverie Bed
Serial2.begin(9600, SERIAL_8N1, RXD2_PIN, TXD2_PIN);
while (!Serial2);
//Run the SmartThings init() routine to make sure the ThingShield is connected to the ST Hub
smartthing.init();
// Controller is Ready
Serial.print("Controller is Ready");
Serial.println();
Serial.println();
}
//******************************************************************************************
// Arduino Loop()
//******************************************************************************************
void loop()
{
// 100uS Timer Flag
if (Timer_Flag_100uS == true)
{
Timer_Flag_100uS = false;
// Touch2O Heartbeat Counter
T2O_HeartBeat_Counter();
// Generate Touch2O Heartbeat/Command Pulse
T2O_SendCmd_Pulse();
// Send a Command to the Touch2O
T2O_Cmd_Heartbeat();
T2O_Cmd_OpenValve();
T2O_Cmd_CloseValve();
}
// 10mS Timer Flag
if (Timer_Flag_10mS == true)
{
Timer_Flag_10mS = false;
// Blink Heart Beat LED
// (This is on-bord Blue LED)
HeartBeatLED();
}
// 1S Timer Flag
if (Timer_Flag_1S == true)
{
Timer_Flag_1S = false;
}
// Process Serial Input Data
while (Serial2.available () > 0)
{
ReadSerialByte(Serial2.read());
}
// run smartthing logic
smartthing.run();
}
//*****************************************************************************
// ***** Heart Beat LED *****
void HeartBeatLED()
{
if (HB_LED_Counter < (HB_LED_Period / Timer0_IRQ_Period))
{
HB_LED_Counter++;
}
else
{
Timer_Flag_1S = true;
HB_LED_Counter = 0;
digitalWrite(HB_LED_PIN, !digitalRead(HB_LED_PIN));
}
}
//*****************************************************************************
// ***** Heart Beat Touch2O Counter *****
void T2O_HeartBeat_Counter()
{
if (T2O_HB_Cntr < T2O_HB_PERIOD)
{
T2O_HB_Cntr++;
}
else
{
T2O_HB_Cntr = 0;
T2O_SendHB_Flag = true;
T2O_SendCmdPulse_Flag = true;
}
}
//*****************************************************************************
// ***** Generate 500uS Touch2O Command Pulse *****
void T2O_SendCmd_Pulse()
{
if (T2O_SendCmdPulse_Flag == true)
{
if (T2O_CmdPulse_Cntr == 0)
{
digitalWrite(HANDSHAKE_PIN, LOW);
}
else if (T2O_CmdPulse_Cntr == T2O_CMD_PULSE_WIDTH)
{
digitalWrite(HANDSHAKE_PIN, HIGH);
}
else if (T2O_CmdPulse_Cntr == T2O_SEND_HB_DELAY)
{
T2O_SendCmdPulse_Flag = false;
T2O_CmdPulse_Cntr = 0;
}
if (T2O_SendCmdPulse_Flag == true)
{
T2O_CmdPulse_Cntr++;
}
}
}
//*****************************************************************************
// ***** Send Heartbeat Command to Touch2O *****
void T2O_Cmd_Heartbeat()
{
if ((T2O_SendHB_Flag == true ) &&
(T2O_SendCmdPulse_Flag == false) &&
(HE_Cmd_OpenValve == false) &&
(HE_Cmd_CloseValve == false)
)
{
T2O_SendHB_Flag = false;
Serial2.write(CmdString_Heartbeat, sizeof(CmdString_Heartbeat));
}
}
//*****************************************************************************
// ***** Send Open Valve Command to Touch2O *****
void T2O_Cmd_OpenValve()
{
if ((T2O_SendHB_Flag == false) &&
(T2O_SendCmdPulse_Flag == false) &&
(HE_Cmd_OpenValve == true ) &&
(HE_Cmd_CloseValve == false)
)
{
HE_Cmd_OpenValve = false;
Serial2.write(CmdString_ValveOpen, sizeof(CmdString_ValveOpen));
}
}
//*****************************************************************************
// ***** Send Close Valve Command to Touch2O *****
void T2O_Cmd_CloseValve()
{
if ((T2O_SendHB_Flag == false) &&
(T2O_SendCmdPulse_Flag == false) &&
(HE_Cmd_OpenValve == false) &&
(HE_Cmd_CloseValve == true )
)
{
HE_Cmd_CloseValve = false;
Serial2.write(CmdString_ValveClose, sizeof(CmdString_ValveClose));
}
}
//*****************************************************************************
// ***** Process Serial Input Data *****
void ReadSerialByte(const byte inByte)
{
switch (statePacket)
{
// Wait for Header Byte
case WAIT_HEADER:
if (inByte == HEADER_BYTE)
{
bufferRx[0] = inByte;
indexRx = 1;
statePacket = RECEIVE_PACKET;
}
break;
// Receive Packet
case RECEIVE_PACKET:
bufferRx[indexRx] = inByte;
if (indexRx == ID_INDEX)
{
if (bufferRx[ID_INDEX] == HEARTBEAT_ID)
{
lengthPacket = HB_PACKET_LEN;
}
else if (bufferRx[ID_INDEX] == STATUS_ID)
{
lengthPacket = STATUS_PACKET_LEN;
}
else
{
// Unknown Packet ID, Reset Rx SM
statePacket = WAIT_HEADER;
indexRx = 0;
break;
}
}
indexRx++;
// Check Packet Status
if (indexRx == lengthPacket)
{
if (lengthPacket == STATUS_PACKET_LEN)
{
printPacket(bufferRx, lengthPacket);
Serial.println();
// Report Valve status to HE
ReportValveStatus();
}
// Reset Rx SM
statePacket = WAIT_HEADER;
indexRx = 0;
}
// Rx Buffer Overflow, Reset Rx SM
else if (indexRx > RX_BUFFER_SIZE)
{
statePacket = WAIT_HEADER;
indexRx = 0;
}
break;
// Process undefined State
default:
break;
}
}
//*****************************************************************************
// ***** Report Valve Status to HE *****
void ReportValveStatus()
{
if (memcmp(bufferRx, StatusString_ValveOpened, STATUS_PACKET_LEN) == 0)
{
smartthing.send("valve1 open");
}
else if (memcmp(bufferRx, StatusString_ValveClosed, STATUS_PACKET_LEN) == 0)
{
smartthing.send("valve1 closed");
}
}
//*****************************************************************************
// ***** Message from Hubitat *****
void messageCallout(String message)
{
String HE_Command = "";
String HE_Command_Value = "";
Serial.print("Received Message from HE: ");
Serial.print(message);
Serial.println();
// Remove leading/trailing spaces
message.trim();
HE_Command = message;
Serial.print("HE Command is : " + HE_Command);
Serial.println();
// ***** Command Processor *****
// Ignore a Refresh Command
if (HE_Command.equals("refresh"))
{
}
else if (HE_Command.equals("valve1 open"))
{
HE_Cmd_OpenValve = true;
T2O_SendCmdPulse_Flag = true;
}
else if (HE_Command.equals("valve1 close"))
{
HE_Cmd_CloseValve = true;
T2O_SendCmdPulse_Flag = true;
}
// Command is not recognised
else
{
Serial.print("Illigal Command");
Serial.println();
}
}
//*****************************************************************************
// Process Rx Buffer
void printPacket(uint8_t* data, uint8_t length)
{
for (uint8_t i = 0; i < length; i++)
{
Serial.print("0x");
if (data[i] < 0x10)
{
Serial.print("0");
}
Serial.print(data[i], HEX);
Serial.print(" ");
}
Serial.println();
}
//*****************************************************************************
Create credentials.h text file and put in these two lines:
char mySSID = "Your WiFi SSID";
char myPASSWORD = "Your WiFi Password";
How do you want a Temp Reporting:
Getting Tem Value by Request or report it on any change?
You have to destroy a VoiceIQ Module if you want to use its water frow sensor. Case should be opened, original board must be removed and small HALL Efferct sensor must be mounted in about the same place where original part is (small 3-pin part on a picture above, right side almost in a middle of the board).
Depend on type of the HALL Sensor (it could be with analog or digital output) an apropriate ESP32 Pin must be used as an Input.
A Pulse Counter Code needs to be added to the Hubduino sketch.