commit 236a019432c4e5326c9d5f7ecf981196f20f4fda Author: Denis Denisov Date: Tue Jan 13 07:25:19 2026 +0300 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0e56cf2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +config.h diff --git a/HotWater.items b/HotWater.items new file mode 100644 index 0000000..6766ecf --- /dev/null +++ b/HotWater.items @@ -0,0 +1,4 @@ +Number HotWater_T_target "HotWater target temperature [%.1f °C]" {channel="modbus:data:HotWater:H:HotWater_T_target:number"} +Number HotWater_Hyst "HotWater hysteresis [%.1f °C]" {channel="modbus:data:HotWater:H:HotWater_Hyst:number"} +Number HotWater_T "HotWater current temperature [%.1f °C]" {channel="modbus:data:HotWater:H:HotWater_T:number"} +Switch HotWater_State "HotWater state" {channel="modbus:data:HotWater:H:HotWater_State:switch"} diff --git a/HotWater.things b/HotWater.things new file mode 100644 index 0000000..c7f2fb1 --- /dev/null +++ b/HotWater.things @@ -0,0 +1,8 @@ +Bridge modbus:tcp:HotWater [ host="192.168.0.46", port=15000, id=1 ] { + Bridge poller H[start = 0, length = 6, type = "holding", refresh = 500] { + Thing data HotWater_T_target[readStart = "0", readValueType = "int16", readTransform="JS(divide100.js)", writeStart="0", writeValueType="int16", writeTransform="JS(multiply100.js)", writeType="holding"] + Thing data HotWater_Hyst[readStart = "1", readValueType = "int16", readTransform="JS(divide100.js)", writeStart="1", writeValueType="int16", writeTransform="JS(multiply100.js)", writeType="holding"] + Thing data HotWater_T[readStart = "2", readValueType = "int16", readTransform="JS(divide100.js)"] + Thing data HotWater_State[readStart = "3", readValueType = "int16", writeStart="4", writeValueType="int16", writeType="holding"] + } +} diff --git a/config.h.sample b/config.h.sample new file mode 100644 index 0000000..22910e6 --- /dev/null +++ b/config.h.sample @@ -0,0 +1,8 @@ + +#define WIFI_NAME "your wi-fi name" +#define WIFI_PASS "you wi-fi password" +#define REMOTE_ADDR "IP address of host to send data to" +#define REMOTE_PORT 7000 +#define MY_IP 192,168,0,21 +#define MY_GW 192,168,0,1 +#define MY_NET 255,255,255,0 diff --git a/hotwater.ino b/hotwater.ino new file mode 100644 index 0000000..ca75a79 --- /dev/null +++ b/hotwater.ino @@ -0,0 +1,188 @@ +#include +#include +#include +#include +#include "config.h" +#include "LittleFS.h" +#include + +// Display +#define LCD_COLUMNS 16 +#define LCD_ROWS 2 +#define LCD_ADDRESS 0x27 + +#define RELAY_PIN D7 +#define TEMP_SENSOR_PIN D5 +// network +// After how much time without connection we will +// reboot (in millis) +#define NON_CONNECTED_REBOOT_TIMEOUT 60000 +#define RECONNECT_TIMEOUT 10000 + +IPAddress my_ip(MY_IP); +IPAddress gateway(MY_GW); +IPAddress subnet(MY_NET); + +// modbus consts and works +#define TCP_PORT 15000 +#define T_UPDATE_INTERVAL 1000 + +#define REGISTER_TARGET_T 0 +#define REGISTER_HYSTERESIS 1 +#define REGISTER_CUR_TEMP 2 +#define REGISTER_CUR_STATE 3 + +ModbusIP mb; +OneWire oneWire(TEMP_SENSOR_PIN); +DallasTemperature sensor(&oneWire); + +LiquidCrystal_I2C lcd(LCD_ADDRESS, LCD_COLUMNS, LCD_ROWS); + +long last_poll; +long last_update; + +int t_target = 7000; +int hyst = 200; +int cur_t = 0; +int cur_state = 0; + +bool TryLoad(char *filename) +{ + File f = LittleFS.open(filename, "r"); + if (!f) return false; + String s = f.readStringUntil('\n'); + t_target = atoi(s.c_str()); + s = f.readStringUntil('\n'); + hyst = atoi(s.c_str()); + f.close(); + if (t_target == 0) return false; + return true; +} + +void LoadState() +{ + if (TryLoad("/settings.txt")) return; + TryLoad("/settings.txt.bak"); +} + +void SaveTo(char *filename) +{ + File f = LittleFS.open(filename, "w"); + if (!f) return; + f.println(t_target); + f.println(hyst); + f.close(); +} + +void SaveState() +{ + SaveTo("/settings.txt"); + SaveTo("/settings.txt.bak"); +} + + +void setup() +{ + lcd.init(); + lcd.backlight(); + lcd.setCursor(0, 0); + if (!LittleFS.begin()) + { + Serial.println("Unable to mount the filesystem"); + lcd.print("ERR: FS"); + ESP.restart(); + } + LoadState(); + pinMode(RELAY_PIN, OUTPUT); + pinMode(TEMP_SENSOR_PIN, INPUT); + Serial.begin(115200); + Serial.println("Initialised..."); + + WiFi.mode(WIFI_STA); + WiFi.config(my_ip, gateway, subnet); + WiFi.setAutoReconnect(true); + WiFi.begin(WIFI_NAME, WIFI_PASS); + Serial.print("Connecting"); + lcd.print("Connecting..."); + delay(500); + Serial.println(); + + last_poll = millis(); + + mb.server(15000); + mb.addHreg(REGISTER_TARGET_T, t_target); + mb.addHreg(REGISTER_HYSTERESIS, hyst); + mb.addHreg(REGISTER_CUR_TEMP, cur_t); + mb.addHreg(REGISTER_CUR_STATE, cur_state); + // + sensor.begin(); + sensor.setResolution(12); +} + +void UpdateCurrent() +{ + sensor.requestTemperatures(); + cur_t = int(sensor.getTempCByIndex(0) * 100); + last_update = millis(); +} + +unsigned long last_connected = 0; +unsigned long last_connecting = 0; + +void loop() +{ + unsigned long current = millis(); + int u = 0; + if (current < last_update || current - last_update >= T_UPDATE_INTERVAL) + { + UpdateCurrent(); + u = 1; + } + mb.task(); + t_target = mb.Hreg(REGISTER_TARGET_T); + hyst = mb.Hreg(REGISTER_HYSTERESIS); + if (cur_t < t_target - hyst) cur_state = 1; + else if (cur_t > t_target + hyst) cur_state = 0; + mb.Hreg(REGISTER_CUR_STATE, cur_state); + digitalWrite(RELAY_PIN, cur_state); + if (u) + { + SaveState(); + Serial.println("Cur state"); + Serial.println(cur_state); + Serial.println(cur_t); + lcd.clear(); + lcd.setCursor(0, 0); + lcd.print("T: "); + lcd.print((cur_t) / 100.0, 2); + lcd.print(" ("); + lcd.print(t_target / 100); + lcd.print(")"); + lcd.setCursor(0, 1); + lcd.print("S: "); + lcd.print(cur_state); + lcd.print("WiFi: "); + lcd.print(WiFi.status()); + } + mb.Hreg(REGISTER_TARGET_T, t_target); + mb.Hreg(REGISTER_HYSTERESIS, hyst); + mb.Hreg(REGISTER_CUR_TEMP, cur_t); + mb.Hreg(REGISTER_CUR_STATE, cur_state); + if (WiFi.status() != WL_CONNECTED) + { + if (current < last_connecting || current - last_connecting > RECONNECT_TIMEOUT) + { + WiFi.reconnect(); + last_connecting = current; + } + } + else + { + last_connected = current; + } + if (current < last_connected || current - last_connected > NON_CONNECTED_REBOOT_TIMEOUT) + { + SaveState(); + ESP.restart(); + } +}