From cff46248b8e8841e110384823e9eb9f1d82c6a4b Mon Sep 17 00:00:00 2001 From: Denis Denisov Date: Mon, 12 Sep 2022 23:32:00 +0300 Subject: [PATCH] Initial commit. Heater rev2 - features cut-off on relay temperature --- .gitignore | 1 + Heater.items | 5 ++ Heater.things | 9 ++++ config.h.sample | 5 ++ heater_v2.ino | 138 ++++++++++++++++++++++++++++++++++++++++++++++++ scheme.pdf | Bin 0 -> 8537 bytes scheme.rrb | Bin 0 -> 2248 bytes 7 files changed, 158 insertions(+) create mode 100644 .gitignore create mode 100644 Heater.items create mode 100644 Heater.things create mode 100644 config.h.sample create mode 100644 heater_v2.ino create mode 100644 scheme.pdf create mode 100644 scheme.rrb 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/Heater.items b/Heater.items new file mode 100644 index 0000000..84dfd65 --- /dev/null +++ b/Heater.items @@ -0,0 +1,5 @@ +Number Heater_Mode "Heater mode" {channel="modbus:data:Heater:H:Heater_Mode:number"} +Switch Heater_State "Heater state" {channel="modbus:data:Heater:H:Heater_State:switch"} +Number Heater_RelayT "Heater relay temperature [%.1f °C]" {channel="modbus:data:Heater:H:Heater_RelayT:number"} +Number Heater_Overheat "Heater relay overheated" {channel="modbus:data:Heater:H:Heater_Overheat:number"} +Switch Heater_OverrideState "Heater override state" {channel="modbus:data:Heater:H:Heater_OverrideState:switch"} diff --git a/Heater.things b/Heater.things new file mode 100644 index 0000000..c573f20 --- /dev/null +++ b/Heater.things @@ -0,0 +1,9 @@ +Bridge modbus:tcp:Heater [ host="192.168.0.21", port=15000, id=1 ] { + Bridge poller H[start = 0, length = 5, type = "holding", refresh = 500] { + Thing data Heater_Mode[readStart = "0", readValueType = "int16", writeStart="0", writeValueType="int16", writeType="holding"] + Thing data Heater_State[readStart = "1", readValueType = "int16", writeStart="1", writeValueType="int16", writeType="holding"] + Thing data Heater_RelayT[readStart = "2", readValueType = "int16", readTransform="JS(divide100.js)"] + Thing data Heater_Overheat[readStart = "3", readValueType = "int16"] + Thing data Heater_OverrideState[readStart = "4", readValueType = "int16", writeStart="4", writeValueType="int16", writeType="holding"] + } +} diff --git a/config.h.sample b/config.h.sample new file mode 100644 index 0000000..9d6aa18 --- /dev/null +++ b/config.h.sample @@ -0,0 +1,5 @@ + +#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 diff --git a/heater_v2.ino b/heater_v2.ino new file mode 100644 index 0000000..609f1b7 --- /dev/null +++ b/heater_v2.ino @@ -0,0 +1,138 @@ +#include +#include +#include "config.h" + +// modbus consts and works +#define TCP_PORT 15000 +#define POLL_INTERVAL 10000 +#define T_UPDATE_INTERVAL 500 + +#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 + +// critical relay temp (degrees multiplied by 100) +#define CRITICAL_T 6000 + +ModbusIP mb; + +char id[64]; + +long last_poll; +long last_update; + +int cur_mode = MODE_AUTO; +int cur_state = 0; +int relay_t = 0; +int overheat = 0; +int override_state = 0; + +void setup() +{ + pinMode(D1, OUTPUT); + Serial.begin(115200); + Serial.println("Initialised..."); + + WiFi.begin(WIFI_NAME, WIFI_PASS); + Serial.print("Connecting"); + while (WiFi.status() != WL_CONNECTED) + { + delay(500); + Serial.print("."); + } + 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); + last_poll = millis(); + + 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); +} + +// thermistor +#define THERMISTORPIN A0 +#define THERMISTORNOMINAL 10000 +#define TEMPERATURENOMINAL 25 +#define NUMSAMPLES 5 +#define BCOEFFICIENT 3950 +#define SERIESRESISTOR 10000 + +void UpdateCurrent() +{ + 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_update = millis(); +} + +void loop() +{ + unsigned long current = millis(); + int u = 0; + if (current < last_update || current - last_update >= T_UPDATE_INTERVAL) + { + UpdateCurrent(); + u = 1; + } + mb.task(); + cur_mode = mb.Hreg(REGISTER_MODE); + override_state = mb.Hreg(REGISTER_OVERRIDE_STATE); + if (cur_mode == MODE_AUTO) + { + cur_state = mb.Hreg(REGISTER_CUR_STATE); + } + 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(D1, cur_state); + if (u) + { + Serial.printf("Cur state %d Cur relay t %d\n", cur_state, relay_t); + } + 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); + if (WiFi.status() != WL_CONNECTED) + { + // no wifi + Serial.println("No WiFi connection"); + ESP.restart(); + } +} diff --git a/scheme.pdf b/scheme.pdf new file mode 100644 index 0000000000000000000000000000000000000000..547342bf9e31cee0c59fda433a656ed859f0ba40 GIT binary patch literal 8537 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 literal 0 HcmV?d00001 diff --git a/scheme.rrb b/scheme.rrb new file mode 100644 index 0000000000000000000000000000000000000000..baccc51437eb81a52a1a463e35a49fc87aaacea6 GIT binary patch literal 2248 zcmV;(2sih@+5i9q000000000000000000262mk;diwFP!000040PVd8U>r%7B_=Oz z%6orG^-9;^bfE}=m|4%5?jt2%F*mnTosm*u)i zS9P&$^I1AA>gl>@(@B-pv)?#5UaiVvnzco>{K>p-=*MS!pDeO(S7rXR z$QF5<*Prc-AKXtD3;X5EY-YdP9q;p3PpY=97O$&vme*fo&D+o3)ayL__+{3#xqg09 z=2@LBr}-DfvRJGa)W`1L1BP@`tyX#cJi8{uKHK|^lhfHn9%+8>fLlK+%G?ZL9|Q0) z{~~+Gtv?;#6WI8Yu>T^fFN!6>S_`cGefzBpuiN0>{rjJCle0y3kvC_{C)xDvMP03z zv)6fME~KD*Ag0N+{>(PU#u4sOv~;r{at1>_ZU^9xV@~_ zb<@N6Reo6r5B$c->s2<*pXBXTo-g@LM}+(LxR*Ecyk2DGtGq1o^EdNi`gTbi2Q>6+ z{iHv$Z>rVnqI}y-YXYu)tW3TvM;pHE*1N^b$8AA6I(Yk%xn zwQOGyfATNdfzPx*27G_)Y0<38?E1@^$bc^E8NgiQJ4|DFeNioc?8|jq7R#J4BdLa* z{Mhb~(X&ImBLDDTHJ=ooq-*-rK{9okxX!j$7-QGj_SUUKz zANw>4^3$6^?hS%`5C!?*W{~#=K^{aw9^4Fae-PxuD9DF5gSJb9cORGt5ti zVeUp@?%oXZ!7$9dD9pW^VIB;_yeBZpvb`^FZW#Q26zDCub9cimsT6g9-hw}Ohq-B2 z<){Pn796@e%=Nn}NgbfK;L+V->D*OW3V_~ z^kD?%bLj3L3#{Ug?K$Dv^$6^Gz*hn7_dfMH*CzHE=)_6eP$zpsU){bFD}I|g`OuN5 zI1bXJ^VM!B{T%n?K`m&KLDxs)&9ZzcVZ=e08(#|U#F9}#NYFDSGSY*my>$~62D<2{+!(Fb@B!= zOt%y34DStTVe@?S-TaHH`G`WLtZ9lVT4{p;&{od(d@JJ5ga!TP_x9oLd}^YeIOKgR z+yUhKG03kvkT(Oi0v!PUGzR$T?)K}G=kEjD3UUDaK@|Aj_Uos|?*m)~xC8tk3jE&t z0JcIK0Dc$+y#GFct)O~<$xg=rfBxd>`#`o~XaIRP2KoL^z7JpRt5vWafbW!eE?e-3;@T)GdD`bO)dBJscym z|6{vIrqkcMd*l6wcs_NXN@CXskBl)TkU8bq8RMsW3|65c?fYY$adoH8J18edWFgVh{1T z;iP-J4l(&{|H1hF4xUek5bQ#H5b!46msbwRQ^m9Ae|#%|yZHPdc+dR3iE`aMdq~5* zT{7i+yZ6EJd*cV>CXnok;@H`HaPQMS3F6TkED(_4MD z`@ntG0qA|@#DuUua9Dpz&Vj{xcW2LF4c&h~_=O(D-b>xz)2}?>4-N2pAJPBwrfAx# zPIvF_rQc1r$j!i9(la+qoDOC$d!;mqAzef419eboWzedY5Nc`+92Pj~irEY_cXdf#CEwV9S283hie z{TjgEgK0nY#&JL1Rc~fOG=`bB+t&j(EJyxsUtrq7A#Y~d1N$*QAHcNMMybXo;1BW5 z12#>&pN6XWqc^9ozPPP@+S?VUQGlKa7%_V4|#fgy8(`$PsCdQ literal 0 HcmV?d00001