From abadb7fe8156f07a145b6447fb2dcf0bbf6eb663 Mon Sep 17 00:00:00 2001 From: Denis Denisov Date: Sun, 25 Sep 2022 16:35:55 +0300 Subject: [PATCH] First version. Saves target t to flash, features cut-off by relay temperature. --- .gitignore | 1 + config.h.sample | 8 ++ heater_v3.ino | 331 +++++++++++++++++++++++++++++++++++++++++++++++ heater_v3.items | 8 ++ heater_v3.things | 12 ++ make_config.pl | 27 ++++ scheme.rrb | Bin 2596 -> 2624 bytes 7 files changed, 387 insertions(+) create mode 100644 .gitignore create mode 100644 config.h.sample create mode 100644 heater_v3.ino create mode 100644 heater_v3.items create mode 100644 heater_v3.things create mode 100644 make_config.pl 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/config.h.sample b/config.h.sample new file mode 100644 index 0000000..0153099 --- /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 MY_IP 192,168,0,26 +#define MY_GW 192,168,0,1 +#define MY_NET 255,255,255,0 +#define SENSOR_IP 192,168,0,25 +#define SENSOR_PORT 15000 diff --git a/heater_v3.ino b/heater_v3.ino new file mode 100644 index 0000000..4781023 --- /dev/null +++ b/heater_v3.ino @@ -0,0 +1,331 @@ +#include "DHT.h" +#include +#include +#include "config.h" +#include "LittleFS.h" + +// network + +IPAddress my_ip(MY_IP); +IPAddress gateway(MY_GW); +IPAddress subnet(MY_NET); +IPAddress sensor_ip(SENSOR_IP); + +// After how much time without connection we will +// reboot (in millis) +#define NON_CONNECTED_REBOOT_TIMEOUT 60000 + +// dht sensor +#define DHTPIN D6 +#define DHTTYPE AM2301 +DHT dht(DHTPIN, DHTTYPE); +#define DHT_UPDATE_INTERVAL 500 + +// relay +#define RELAY_PIN D1 +#define T_UPDATE_INTERVAL 500 + +// external t +#define EXTERNAL_UPDATE_INTERVAL 1000 + +// logic consts + +// deviation from target t allowed (in degrees multiplied by 100) +#define T_HYSTERESIS 150 +// timeout to switch from external to internal temperature data (in milliseconds) +#define SWITCH_TO_INTERNAL_TIMEOUT 50000 + +// modbus consts and works +#define TCP_PORT 15000 + +#define MODE_AUTO 0 +#define MODE_MANUAL 1 + +#define REGISTER_MODE 0 +#define REGISTER_CUR_STATE 1 +#define REGISTER_RELAY_TEMP 2 +#define REGISTER_OVERHEAT 3 +#define REGISTER_OVERRIDE_STATE 4 +// desired temperature +#define REGISTER_TARGET_T 5 +// temperature from internal sensor +#define REGISTER_INTERNAL_T 6 +// temperatur from external sensor (received via modbus-tcp) +#define REGISTER_EXTERNAL_T 7 + +// critical relay temp (degrees multiplied by 100) +#define CRITICAL_T 6000 + +ModbusIP mb; + +char id[64]; + + +int cur_mode = MODE_AUTO; +int cur_state = 0; +int relay_t = 0; +int overheat = 0; +int override_state = 0; +int target_t = 0; +int internal_t = 0; +int external_t = 0; + +void LoadState() +{ + File f = LittleFS.open("/target_t.txt", "r"); + if (!f) return; + String s = f.readStringUntil('\n'); + target_t = atoi(s.c_str()); + f.close(); +} + +void SaveState() +{ + File f = LittleFS.open("/target_t.txt", "w"); + if (!f) return; + f.println(target_t); + f.close(); +} + +void setup() +{ + if (!LittleFS.begin()) + { + Serial.println("Unable to mount the filesystem"); + ESP.restart(); + } + LoadState(); + pinMode(DHTPIN, INPUT); + pinMode(RELAY_PIN, OUTPUT); + dht.begin(); + 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"); + unsigned long start = millis(); + while (WiFi.status() != WL_CONNECTED) + { + delay(500); + Serial.print("."); + if (millis() - start > NON_CONNECTED_REBOOT_TIMEOUT) + { + ESP.restart(); + } + } + Serial.println(); + + Serial.print("Connected, IP address: "); + Serial.println(WiFi.localIP()); + uint8_t macAddr[6]; + WiFi.macAddress(macAddr); + sprintf(id, "%02x:%02x:%02x:%02x:%02x:%02x", macAddr[0], macAddr[1], macAddr[2], macAddr[3], macAddr[4], macAddr[5]); + Serial.printf("Connected, mac address: %s\n", id); + + mb.server(15000); + mb.addHreg(REGISTER_MODE, cur_mode); + mb.addHreg(REGISTER_CUR_STATE, cur_state); + mb.addHreg(REGISTER_RELAY_TEMP, relay_t); + mb.addHreg(REGISTER_OVERHEAT, overheat); + mb.addHreg(REGISTER_OVERRIDE_STATE, override_state); + mb.addHreg(REGISTER_TARGET_T, target_t); + mb.addHreg(REGISTER_INTERNAL_T, internal_t); + mb.addHreg(REGISTER_EXTERNAL_T, external_t); +} + +////////////////////////////////////////////////////////////////// +// thermistor (relay temperature) +////////////////////////////////////////////////////////////////// +#define THERMISTORPIN A0 +#define THERMISTORNOMINAL 10000 +#define TEMPERATURENOMINAL 25 +#define NUMSAMPLES 5 +#define BCOEFFICIENT 3950 +#define SERIESRESISTOR 10000 + +unsigned long last_relay_update = 0; + +void UpdateRelayT() +{ + float average = 0; + for (int i = 0; i < NUMSAMPLES; i++) + { + average += analogRead(THERMISTORPIN); + delay(10); + } + average /= NUMSAMPLES; + average = 1023 / average - 1; + average = SERIESRESISTOR / average; + float steinhart; + steinhart = average / THERMISTORNOMINAL; // (R/Ro) + steinhart = log(steinhart); // ln(R/Ro) + steinhart /= BCOEFFICIENT; // 1/B * ln(R/Ro) + steinhart += 1.0 / (TEMPERATURENOMINAL + 273.15); // + (1/To) + steinhart = 1.0 / steinhart; // инвертируем + steinhart -= 273.15; // конвертируем в градусы по Цельсию + relay_t = int(steinhart * 100); + last_relay_update = millis(); +} + +//////////////////////////////////////////////////////////////////////// +// DHT (internal thermo sensor) +//////////////////////////////////////////////////////////////////////// + +unsigned long last_dht_update = 0; + +void UpdateDHT() +{ + float t = dht.readTemperature(); + internal_t = (int)(t * 100); + last_dht_update = millis(); +} + +//////////////////////////////////////////////////////////////////////// +// Modbus client (external sensor t) +//////////////////////////////////////////////////////////////////////// + +#define ANSWER_READ_TIMEOUT 1000 + +unsigned long last_external_update = 0; +bool was_external_update = false; + +WiFiClient client; + +#define SENSOR_T_REGISTER 0 +uint8_t mb_message[50]; +uint8_t mb_answer[50]; +int mb_answer_len; + +void UpdateExternalT() +{ + if (!client.connect(sensor_ip, SENSOR_PORT)) + { + Serial.println("Unable to connect to sensor"); + return; + } + mb_message[0] = 0; // transaction id + mb_message[1] = 0; + mb_message[2] = 0; // protocol id + mb_message[3] = 0; + mb_message[4] = 0; // length + mb_message[5] = 6; + mb_message[6] = 0; // unit id + mb_message[7] = 3; // function + mb_message[8] = (SENSOR_T_REGISTER >> 8) & 0xFF; // address - high + mb_message[9] = (SENSOR_T_REGISTER) & 0xFF; // address - low + mb_message[10] = 0; // amount + mb_message[11] = 1; + client.write(mb_message, 12); + client.flush(); + // read answer (it should be 8 (header) + 1 (amount received bytes) + 2 (actual data) + mb_answer_len = 0; + unsigned long start = millis(); + while (millis() - start < ANSWER_READ_TIMEOUT && mb_answer_len < 11) + { + int result = client.read(); + if (result != -1) + { + mb_answer[mb_answer_len++] = result; + } + } + if (mb_answer_len < 11) + { + Serial.println("Too short answer from sensor"); + return; + } + external_t = (((int)mb_answer[9]) << 8) | ((int)mb_answer[10]); + last_external_update = millis(); + was_external_update = true; +} + +//////////////////////////////////////////////////////////////////////// + +int GetCurrentT() +{ + unsigned long current = millis(); + // case when we did not receive external T since device boot + if (!was_external_update) return internal_t; + if (current < last_external_update || current - last_external_update < SWITCH_TO_INTERNAL_TIMEOUT) + { + return external_t; + } + return internal_t; +} + +unsigned long last_connected = 0; + +void loop() +{ + unsigned long current = millis(); + int u = 0; + if (current < last_relay_update || current - last_relay_update >= T_UPDATE_INTERVAL) + { + UpdateRelayT(); + u = 1; + } + if (current < last_dht_update || current - last_dht_update >= DHT_UPDATE_INTERVAL) + { + UpdateDHT(); + u = 1; + } + if (current < last_external_update || current - last_external_update >= EXTERNAL_UPDATE_INTERVAL) + { + UpdateExternalT(); + u = 1; + } + mb.task(); + cur_mode = mb.Hreg(REGISTER_MODE); + override_state = mb.Hreg(REGISTER_OVERRIDE_STATE); + target_t = mb.Hreg(REGISTER_TARGET_T); + int current_t = GetCurrentT(); + if (cur_mode == MODE_AUTO) + { + if (current_t < target_t - T_HYSTERESIS) + { + cur_state = 1; + } + else if (current_t > target_t + T_HYSTERESIS) + { + cur_state = 0; + } + } + else + { + cur_state = override_state; + } + if (relay_t > CRITICAL_T) + { + Serial.printf("Critical overheat, t = %d\n", relay_t); + cur_state = 0; + overheat = 1; + } + else overheat = 0; + digitalWrite(RELAY_PIN, cur_state); + if (u) + { + Serial.printf("State %d Relay t %d Internal t %d External t %d Current t %d Target t %d\n", cur_state, relay_t, internal_t, external_t, current_t, target_t); + SaveState(); + } + mb.Hreg(REGISTER_MODE, cur_mode); + mb.Hreg(REGISTER_RELAY_TEMP, relay_t); + mb.Hreg(REGISTER_OVERHEAT, overheat); + mb.Hreg(REGISTER_CUR_STATE, cur_state); + mb.Hreg(REGISTER_INTERNAL_T, internal_t); + mb.Hreg(REGISTER_EXTERNAL_T, external_t); + if (WiFi.status() != WL_CONNECTED) + { + WiFi.reconnect(); + } + else + { + last_connected = current; + } + if (current < last_connected || current - last_connected > NON_CONNECTED_REBOOT_TIMEOUT) + { + SaveState(); + ESP.restart(); + } +} diff --git a/heater_v3.items b/heater_v3.items new file mode 100644 index 0000000..6a3112e --- /dev/null +++ b/heater_v3.items @@ -0,0 +1,8 @@ +Number HEATER_NAME_Mode "HEATER_NAME mode" {channel="modbus:data:HEATER_NAME:H:HEATER_NAME_Mode:number"} +Switch HEATER_NAME_State "HEATER_NAME state" {channel="modbus:data:HEATER_NAME:H:HEATER_NAME_State:switch"} +Number HEATER_NAME_RelayT "HEATER_NAME relay temperature [%.1f °C]" {channel="modbus:data:HEATER_NAME:H:HEATER_NAME_RelayT:number"} +Number HEATER_NAME_Overheat "HEATER_NAME relay overheated" {channel="modbus:data:HEATER_NAME:H:HEATER_NAME_Overheat:number"} +Switch HEATER_NAME_OverrideState "HEATER_NAME override state" {channel="modbus:data:HEATER_NAME:H:HEATER_NAME_OverrideState:switch"} +Number HEATER_NAME_TargetT "HEATER_NAME desired temperature [%.1f °C]" {channel="modbus:data:HEATER_NAME:H:HEATER_NAME_TargetT:number"} +Number HEATER_NAME_InternalT "HEATER_NAME internal temperature [%.1f °C]" {channel="modbus:data:HEATER_NAME:H:HEATER_NAME_InternalT:number"} +Number HEATER_NAME_ExternalT "HEATER_NAME external temperature [%.1f °C]" {channel="modbus:data:HEATER_NAME:H:HEATER_NAME_ExternalT:number"} diff --git a/heater_v3.things b/heater_v3.things new file mode 100644 index 0000000..aa89d8d --- /dev/null +++ b/heater_v3.things @@ -0,0 +1,12 @@ +Bridge modbus:tcp:HEATER_NAME [ host="192.168.0.24", port=15000, id=1 ] { + Bridge poller H[start = 0, length = 8, type = "holding", refresh = 500] { + Thing data HEATER_NAME_Mode[readStart = "0", readValueType = "int16", writeStart="0", writeValueType="int16", writeType="holding"] + Thing data HEATER_NAME_State[readStart = "1", readValueType = "int16"] + Thing data HEATER_NAME_RelayT[readStart = "2", readValueType = "int16", readTransform="JS(divide100.js)"] + Thing data HEATER_NAME_Overheat[readStart = "3", readValueType = "int16"] + Thing data HEATER_NAME_OverrideState[readStart = "4", readValueType = "int16", writeStart="4", writeValueType="int16", writeType="holding"] + Thing data HEATER_NAME_TargetT[readStart = "5", readValueType = "int16", readTransform="JS(divide100.js)", writeStart="5", writeValueType="int16", writeType="holding", writeTransform="JS(multiply100.js)"] + Thing data HEATER_NAME_InternalT[readStart = "6", readValueType = "int16", readTransform="JS(divide100.js)"] + Thing data HEATER_NAME_ExternalT[readStart = "7", readValueType = "int16", readTransform="JS(divide100.js)"] + } +} diff --git a/make_config.pl b/make_config.pl new file mode 100644 index 0000000..bfbcfc5 --- /dev/null +++ b/make_config.pl @@ -0,0 +1,27 @@ +#!/usr/bin/perl + +if (scalar(@ARGV) < 1) +{ + die("Usage: make_config.pl HEATER_NAME"); +} +my $name = shift @ARGV; + +Process("heater_v3.things", $name.".things"); +Process("heater_v3.items", $name.".items"); + +sub Process +{ + my $src = shift; + my $dst = shift; + my $fh; + open($fh, "< ".$src); + my $ofh; + open($ofh, "> ".$dst); + while (<$fh>) + { + $_ =~ s/HEATER_NAME/$name/g; + print($ofh $_); + } + close($fh); + close($ofh); +} \ No newline at end of file diff --git a/scheme.rrb b/scheme.rrb index d0808634367de89ec8fa5c4a2b002bdf2b0cc89c..316a99e146dddca8eb119736d697778ed5e928fa 100644 GIT binary patch delta 933 zcmV;W16ur~6u=aa6agr)6<-E_ub+1g`Fn4dOxxb>eel5E_yL(s;$;z}oxKP5KHW2( z9&rcs8TC8_>_6*k0 zh4+JB=uzywRQ5gn$^-t;0KYfh`#Qi~+ROouCyvONx^di(ch#Gj5RF}bZDgOpzINTP z9QnI_-5c)?;1Tc3C^QA_Js-XLi2k29MblPwx_fso{cgHNW*2iw-|X-rMM}#rv$UZV ztYUegs_$l|CBsYqKq!bbyPw{_cYj|ny#H|L{(Xn#Fw@$XM*d5}nY*|8>cPJIssqsb z%I7Px>K5xyclLKI)}MZVdf#CEwV9R-y8;K(ehuL7!L*-xX}e0BfIq}H4_IICe%jI6Z%$u*@zIYRN0oNhlI-n@mHzZo zu^a+Q|Joftbaw7Gc8Q>|)z`{+}rX0z8$0o>}ehZjH`d`zCX7;Q2x;O&5VAkkRJG--x4Bo$^GC>`}h9W zz>qma-#5^VOZ+~6crkx;N2-6{yJ<%I3giLvhbWM}2jL5}=Jk6GIekOEK0$nl7Q%2W zO2*gURqT`tZ^G<=IfNPherF^fJ^l8Zy}dzyRPEKlE(1_Wdf&)_-e-k)@O9soux0l> zUysm%d0Rd6{Z63t(N9jN0Uhp}fG`S*nOe1F57g)H0Z1Z$7^9`Ske&`f>htpf^Rt;G zMa_P2Z%=uKNp?F4Ibe?V0LVKCYMpSH&uc86afse6<-E_FM`xVd~TTN-mXJRe%pUAzQ2R#(;?)#5FZ3=iTCA| z19DRF?D-$x3g9k2KM3A4e{Z5%H^m-`^4>04@x9&qVE4W81M(0^Y(;16>^->m>7H?F zS#bG}hdcks-(=x$`7e$9mym?tdHCtAzS@1@zUlzFcvrod3DMZqM)n!(Yu63S zk-yv5z47h<9`U}6?pDy=^UK_BG;LL=Q4DUbOxqshbIn1>7rIG)VaOUoGDo+DuD6i~9;W@1dSaOeP50F!LGUno{7IDH1=2y)M|S0)O)(Ukt1058MFKP?x)7k z-G8W~Jn%oiB}{f+KHUi+e!3&S?Z3}R*?9@yB<{N>HhpLRz2S)qb1#G@bF3c6?t4Ic z=%6{r>fVn3_w68miT834Fs}Z!`~KYaK>0)8H#7RFLVDnTeoKhVCHI3j?ce)f14HHz zecwPc-v9gH#r)A7ss4TMrWx%kkO#~kqCoZ@gfGzY-tRS}KZe2@g7^?EgyC3}Ojxt4 z*eMs@gxLXe2s8Zs&PYCb`t3J+dqb!hdv&nO092CRH%Ov?_gNtxeBHMtLfd`M*CTXb z-qs*wzY`OF^pn$RK!^J#_KkvKrdDm)1NHfP0Fq$MXsIrwr$dnX{CvRtY$nM@9s0q& zJ>?lD+3h6cfH~R&Anzbha>8Lgza_|r_(t`c7aD0FpQ>4VaC?XkMg3U-2FYDtvzX7L zL6@@G!(IGNEf(>f4*FJ%W&ubZLICXE`t`_=nBf(W8ISz+Ubgfh^Si#O9A^F|8{;Jx zhT(No(nIEZGsX}k5pBou#Vt`3_7~Z