diff --git a/README.md b/README.md index 77636ee..e26ccd9 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ | [Ferroli DOMIcompact C 24](https://github.com/Laxilef/OTGateway/issues/3#issuecomment-1765310058)
Board: MF08FA | 211 | Pressure sensor not supported | | [Thermet Ecocondens Silver 35kW](https://github.com/Laxilef/OTGateway/issues/3#issuecomment-1767026384) | default | Pressure sensor not supported | | [BAXI LUNA-3](https://github.com/Laxilef/OTGateway/issues/3#issuecomment-1794187178) | default | - | +| ITALTHERM TIME MAX 30F | default | Pressure sensor not supported
**Important:** on the "setup" page you need to set the checkbox "Opentherm summer/winter mode" | ## PCB diff --git a/platformio.ini b/platformio.ini index f0a75cc..25922f1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -65,6 +65,7 @@ build_flags = -D SENSOR_INDOOR_PIN_DEFAULT=14 -D LED_STATUS_PIN=13 -D LED_OT_RX_PIN=15 + -D USE_TELNET=1 [env:d1_mini_lite] platform = ${esp8266_defaults.platform} @@ -80,6 +81,7 @@ build_flags = -D SENSOR_INDOOR_PIN_DEFAULT=14 -D LED_STATUS_PIN=13 -D LED_OT_RX_PIN=15 + -D USE_TELNET=1 [env:d1_mini_pro] platform = ${esp8266_defaults.platform} @@ -95,6 +97,7 @@ build_flags = -D SENSOR_INDOOR_PIN_DEFAULT=14 -D LED_STATUS_PIN=13 -D LED_OT_RX_PIN=15 + -D USE_TELNET=1 [env:s2_mini] platform = ${esp32_defaults.platform} @@ -110,6 +113,7 @@ build_flags = -D SENSOR_INDOOR_PIN_DEFAULT=7 -D LED_STATUS_PIN=11 -D LED_OT_RX_PIN=12 + -D USE_TELNET=1 [env:s3_mini] platform = ${esp32_defaults.platform} @@ -125,6 +129,7 @@ build_flags = -D SENSOR_INDOOR_PIN_DEFAULT=12 -D LED_STATUS_PIN=11 -D LED_OT_RX_PIN=10 + -D USE_TELNET=1 [env:c3_mini] platform = ${esp32_defaults.platform} @@ -140,6 +145,7 @@ build_flags = -D SENSOR_INDOOR_PIN_DEFAULT=1 -D LED_STATUS_PIN=4 -D LED_OT_RX_PIN=5 + -D USE_TELNET=1 [env:nodemcu_32s] platform = ${esp32_defaults.platform} @@ -154,4 +160,9 @@ build_flags = -D SENSOR_OUTDOOR_PIN_DEFAULT=12 -D SENSOR_INDOOR_PIN_DEFAULT=13 -D LED_STATUS_PIN=2 ; 18 - -D LED_OT_RX_PIN=19 \ No newline at end of file + -D LED_OT_RX_PIN=19 + -D USE_TELNET=1 + ;-D WOKWI=1 + ;-D USE_TELNET=0 + ;-D DEBUG_BY_DEFAULT=1 + ;-D WM_DEBUG_MODE=3 \ No newline at end of file diff --git a/src/MqttTask.h b/src/MqttTask.h index e9bfb50..519fe4f 100644 --- a/src/MqttTask.h +++ b/src/MqttTask.h @@ -141,7 +141,6 @@ protected: if (!doc["heating"]["maxTemp"].isNull() && doc["heating"]["maxTemp"].is()) { if (doc["heating"]["maxTemp"].as() > 0 && doc["heating"]["maxTemp"].as() <= 100 && doc["heating"]["maxTemp"].as() > settings.heating.minTemp) { settings.heating.maxTemp = doc["heating"]["maxTemp"].as(); - vars.parameters.heatingMaxTemp = settings.heating.maxTemp; flag = true; } } @@ -149,7 +148,6 @@ protected: if (!doc["heating"]["minTemp"].isNull() && doc["heating"]["minTemp"].is()) { if (doc["heating"]["minTemp"].as() >= 0 && doc["heating"]["minTemp"].as() < 100 && doc["heating"]["minTemp"].as() < settings.heating.maxTemp) { settings.heating.minTemp = doc["heating"]["minTemp"].as(); - vars.parameters.heatingMinTemp = settings.heating.minTemp; flag = true; } } @@ -171,7 +169,6 @@ protected: if (!doc["dhw"]["maxTemp"].isNull() && doc["dhw"]["maxTemp"].is()) { if (doc["dhw"]["maxTemp"].as() > 0 && doc["dhw"]["maxTemp"].as() <= 100 && doc["dhw"]["maxTemp"].as() > settings.dhw.minTemp) { settings.dhw.maxTemp = doc["dhw"]["maxTemp"].as(); - vars.parameters.dhwMaxTemp = settings.dhw.maxTemp; flag = true; } } @@ -179,7 +176,6 @@ protected: if (!doc["dhw"]["minTemp"].isNull() && doc["dhw"]["minTemp"].is()) { if (doc["dhw"]["minTemp"].as() >= 0 && doc["dhw"]["minTemp"].as() < 100 && doc["dhw"]["minTemp"].as() < settings.dhw.maxTemp) { settings.dhw.minTemp = doc["dhw"]["minTemp"].as(); - vars.parameters.dhwMinTemp = settings.dhw.minTemp; flag = true; } } @@ -433,8 +429,8 @@ protected: bool published = false; bool isStupidMode = !settings.pid.enable && !settings.equitherm.enable; - byte heatingMinTemp = isStupidMode ? vars.parameters.heatingMinTemp : 10; - byte heatingMaxTemp = isStupidMode ? vars.parameters.heatingMaxTemp : 30; + byte heatingMinTemp = isStupidMode ? settings.heating.minTemp : 10; + byte heatingMaxTemp = isStupidMode ? settings.heating.maxTemp : 30; bool editableOutdoorTemp = settings.sensors.outdoor.type == 1; bool editableIndoorTemp = settings.sensors.indoor.type == 1; @@ -479,12 +475,12 @@ protected: published = true; } - if (_dhwPresent && (force || _dhwMinTemp != vars.parameters.dhwMinTemp || _dhwMaxTemp != vars.parameters.dhwMaxTemp)) { - _dhwMinTemp = vars.parameters.dhwMinTemp; - _dhwMaxTemp = vars.parameters.dhwMaxTemp; + if (_dhwPresent && (force || _dhwMinTemp != settings.dhw.minTemp || _dhwMaxTemp != settings.dhw.maxTemp)) { + _dhwMinTemp = settings.dhw.minTemp; + _dhwMaxTemp = settings.dhw.maxTemp; - haHelper.publishNumberDHWTarget(vars.parameters.dhwMinTemp, vars.parameters.dhwMaxTemp, false); - haHelper.publishClimateDHW(vars.parameters.dhwMinTemp, vars.parameters.dhwMaxTemp); + haHelper.publishNumberDHWTarget(settings.dhw.minTemp, settings.dhw.maxTemp, false); + haHelper.publishClimateDHW(settings.dhw.minTemp, settings.dhw.maxTemp); published = true; } diff --git a/src/OpenThermTask.h b/src/OpenThermTask.h index 1756614..70f3d54 100644 --- a/src/OpenThermTask.h +++ b/src/OpenThermTask.h @@ -21,11 +21,6 @@ protected: } void setup() { - vars.parameters.heatingMinTemp = settings.heating.minTemp; - vars.parameters.heatingMaxTemp = settings.heating.maxTemp; - vars.parameters.dhwMinTemp = settings.dhw.minTemp; - vars.parameters.dhwMaxTemp = settings.dhw.maxTemp; - ot = new CustomOpenTherm(settings.opentherm.inPin, settings.opentherm.outPin); ot->setHandleSendRequestCallback(this->sendRequestCallback); @@ -35,9 +30,9 @@ protected: static_cast(self)->delay(10); }, this); -#ifdef LED_OT_RX_PIN - pinMode(LED_OT_RX_PIN, OUTPUT); -#endif + #ifdef LED_OT_RX_PIN + pinMode(LED_OT_RX_PIN, OUTPUT); + #endif } void loop() { @@ -53,10 +48,19 @@ protected: } bool heatingEnabled = (vars.states.emergency || settings.heating.enable) && pump && isReady(); + bool heatingCh2Enabled = settings.opentherm.heatingCh2Enabled; + if (settings.opentherm.heatingCh1ToCh2) { + heatingCh2Enabled = heatingEnabled; + } + localResponse = ot->setBoilerStatus( heatingEnabled, settings.opentherm.dhwPresent && settings.dhw.enable, - false, false, true, false, false + false, + false, + heatingCh2Enabled, + settings.opentherm.summerWinterMode, + false ); if (!ot->isValidResponse(localResponse)) { @@ -87,13 +91,34 @@ protected: DEBUG_F("Slave type: %u, version: %u\r\n", vars.parameters.slaveType, vars.parameters.slaveVersion); if (settings.opentherm.dhwPresent) { - updateMinMaxDhwTemp(); + if (updateMinMaxDhwTemp()) { + if (settings.dhw.minTemp < vars.parameters.dhwMinTemp || settings.dhw.maxTemp > vars.parameters.dhwMaxTemp) { + settings.dhw.minTemp = vars.parameters.dhwMinTemp; + settings.dhw.maxTemp = vars.parameters.dhwMaxTemp; + } + + } else { + WARN("Failed get min/max DHW temp"); + } } - updateMinMaxHeatingTemp(); + + if (updateMinMaxHeatingTemp()) { + if (settings.heating.minTemp < vars.parameters.heatingMinTemp || settings.heating.maxTemp > vars.parameters.heatingMaxTemp) { + settings.heating.minTemp = vars.parameters.heatingMinTemp; + settings.heating.maxTemp = vars.parameters.heatingMaxTemp; + + } else { + WARN("Failed get min/max heating temp"); + } + } + + // force + setMaxHeatingTemp(settings.heating.maxTemp); if (settings.sensors.outdoor.type == 0) { updateOutsideTemp(); } + if (vars.states.fault) { updateFaultCode(); ot->sendBoilerReset(); @@ -129,8 +154,8 @@ protected: // Температура ГВС byte newDHWTemp = settings.dhw.target; if (settings.opentherm.dhwPresent && settings.dhw.enable && (needSetDhwTemp() || newDHWTemp != currentDHWTemp)) { - if (newDHWTemp < vars.parameters.dhwMinTemp || newDHWTemp > vars.parameters.dhwMaxTemp) { - newDHWTemp = constrain(newDHWTemp, vars.parameters.dhwMinTemp, vars.parameters.dhwMaxTemp); + if (newDHWTemp < settings.dhw.minTemp || newDHWTemp > settings.dhw.maxTemp) { + newDHWTemp = constrain(newDHWTemp, settings.dhw.minTemp, settings.dhw.maxTemp); } INFO_F("Set DHW temp = %u\r\n", newDHWTemp); @@ -158,6 +183,12 @@ protected: } else { WARN("Failed set heating temp"); } + + if (settings.opentherm.heatingCh1ToCh2) { + if (!ot->setBoilerTemperature2(vars.parameters.heatingSetpoint)) { + WARN("Failed set ch2 heating temp"); + } + } } // коммутационная разность (hysteresis) @@ -197,14 +228,14 @@ protected: vars.states.otStatus = true; } -#ifdef LED_OT_RX_PIN + #ifdef LED_OT_RX_PIN { digitalWrite(LED_OT_RX_PIN, true); unsigned long ts = millis(); while (millis() - ts < 2) {} digitalWrite(LED_OT_RX_PIN, false); } -#endif + #endif break; default: @@ -345,8 +376,8 @@ protected: byte maxTemp = (response & 0xFFFF) >> 8; if (minTemp >= 0 && maxTemp > 0 && maxTemp > minTemp) { - vars.parameters.dhwMinTemp = minTemp < settings.dhw.minTemp ? settings.dhw.minTemp : minTemp; - vars.parameters.dhwMaxTemp = maxTemp > settings.dhw.maxTemp ? settings.dhw.maxTemp : maxTemp; + vars.parameters.dhwMinTemp = minTemp; + vars.parameters.dhwMaxTemp = maxTemp; return true; } @@ -364,15 +395,23 @@ protected: byte maxTemp = (response & 0xFFFF) >> 8; if (minTemp >= 0 && maxTemp > 0 && maxTemp > minTemp) { - vars.parameters.heatingMinTemp = minTemp < settings.heating.minTemp ? settings.heating.minTemp : minTemp; - vars.parameters.heatingMaxTemp = maxTemp > settings.heating.maxTemp ? settings.heating.maxTemp : maxTemp; - + vars.parameters.heatingMinTemp = minTemp; + vars.parameters.heatingMaxTemp = maxTemp; return true; } return false; } + bool setMaxHeatingTemp(byte value) { + unsigned long response = ot->sendRequest(ot->buildRequest(OpenThermMessageType::WRITE_DATA, OpenThermMessageID::MaxTSet, ot->temperatureToData(value))); + if (!ot->isValidResponse(response)) { + return false; + } + + return true; + } + bool updateOutsideTemp() { unsigned long response = ot->sendRequest(ot->buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Toutside, 0)); if (!ot->isValidResponse(response)) { diff --git a/src/RegulatorTask.h b/src/RegulatorTask.h index 1a7a864..5450cd0 100644 --- a/src/RegulatorTask.h +++ b/src/RegulatorTask.h @@ -65,8 +65,8 @@ protected: } // Ограничиваем, если до этого не ограничило - if (newTemp < vars.parameters.heatingMinTemp || newTemp > vars.parameters.heatingMaxTemp) { - newTemp = constrain(newTemp, vars.parameters.heatingMinTemp, vars.parameters.heatingMaxTemp); + if (newTemp < settings.heating.minTemp || newTemp > settings.heating.maxTemp) { + newTemp = constrain(newTemp, settings.heating.minTemp, settings.heating.maxTemp); } if (abs(vars.parameters.heatingSetpoint - newTemp) + 0.0001 >= 1) { @@ -80,7 +80,7 @@ protected: // if use equitherm if (settings.emergency.useEquitherm && settings.sensors.outdoor.type != 1) { - float etResult = getEquithermTemp(vars.parameters.heatingMinTemp, vars.parameters.heatingMaxTemp); + float etResult = getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp); if (fabs(prevEtResult - etResult) + 0.0001 >= 0.5) { prevEtResult = etResult; @@ -115,7 +115,7 @@ protected: // if use equitherm if (settings.equitherm.enable) { - float etResult = getEquithermTemp(vars.parameters.heatingMinTemp, vars.parameters.heatingMaxTemp); + float etResult = getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp); if (fabs(prevEtResult - etResult) + 0.0001 >= 0.5) { prevEtResult = etResult; @@ -184,7 +184,7 @@ protected: } else if (vars.tuning.regulator == 1) { // PID tuner float defaultTemp = settings.equitherm.enable - ? getEquithermTemp(vars.parameters.heatingMinTemp, vars.parameters.heatingMaxTemp) + ? getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp) : settings.heating.target; if (tunerInit && pidTuner.getState() == 3) { diff --git a/src/Settings.h b/src/Settings.h index 72df3b4..e61bef0 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -1,5 +1,5 @@ struct Settings { - bool debug = false; + bool debug = DEBUG_BY_DEFAULT; char hostname[80] = "opentherm"; struct { @@ -7,6 +7,9 @@ struct Settings { byte outPin = OT_OUT_PIN_DEFAULT; unsigned int memberIdCode = 0; bool dhwPresent = true; + bool summerWinterMode = false; + bool heatingCh2Enabled = true; + bool heatingCh1ToCh2 = false; } opentherm; struct { diff --git a/src/WifiManagerTask.h b/src/WifiManagerTask.h index f6b879a..8427ba9 100644 --- a/src/WifiManagerTask.h +++ b/src/WifiManagerTask.h @@ -16,6 +16,9 @@ UnsignedIntParameter* wmOtInPin; UnsignedIntParameter* wmOtOutPin; UnsignedIntParameter* wmOtMemberIdCode; CheckboxParameter* wmOtDHWPresent; +CheckboxParameter* wmOtSummerWinterMode; +CheckboxParameter* wmOtHeatingCh2Enabled; +CheckboxParameter* wmOtHeatingCh1ToCh2; UnsignedIntParameter* wmOutdoorSensorPin; UnsignedIntParameter* wmIndoorSensorPin; @@ -40,8 +43,11 @@ protected: } void setup() { - wm.setDebugOutput(settings.debug); - //wm.setDebugOutput(settings.debug, WM_DEBUG_VERBOSE); + #ifdef WOKWI + WiFi.begin("Wokwi-GUEST", "", 6); + #endif + + wm.setDebugOutput(settings.debug, WM_DEBUG_MODE); wm.setTitle("OpenTherm Gateway"); wm.setCustomMenuHTML(PSTR( @@ -91,6 +97,15 @@ protected: wmOtDHWPresent = new CheckboxParameter("ot_dhw_present", "Opentherm DHW present", settings.opentherm.dhwPresent); wm.addParameter(wmOtDHWPresent); + wmOtSummerWinterMode = new CheckboxParameter("ot_summer_winter_mode", "Opentherm summer/winter mode", settings.opentherm.summerWinterMode); + wm.addParameter(wmOtSummerWinterMode); + + wmOtHeatingCh2Enabled = new CheckboxParameter("ot_heating_ch2_enabled", "Opentherm CH2 enabled", settings.opentherm.heatingCh2Enabled); + wm.addParameter(wmOtHeatingCh2Enabled); + + wmOtHeatingCh1ToCh2 = new CheckboxParameter("ot_heating_ch1_to_ch2", "Opentherm heating CH1 to CH2", settings.opentherm.heatingCh1ToCh2); + wm.addParameter(wmOtHeatingCh1ToCh2); + wmSep2 = new SeparatorParameter(); wm.addParameter(wmSep2); @@ -122,8 +137,14 @@ protected: wm.stopWebPortal(); } - #ifdef USE_TELNET - TelnetStream.stop(); + wm.setCaptivePortalEnable(true); + + if (!wm.getConfigPortalActive()) { + wm.startConfigPortal(); + } + + #if USE_TELNET + TelnetStream.stop(); #endif INFO("[wifi] Disconnected"); @@ -136,12 +157,14 @@ protected: wm.stopConfigPortal(); } + wm.setCaptivePortalEnable(false); + if (!wm.getWebPortalActive()) { wm.startWebPortal(); } - #ifdef USE_TELNET - TelnetStream.begin(); + #if USE_TELNET + TelnetStream.begin(); #endif INFO_F("[wifi] Connected. IP address: %s, RSSI: %d\n", WiFi.localIP().toString().c_str(), WiFi.RSSI()); @@ -230,6 +253,24 @@ protected: settings.opentherm.dhwPresent = wmOtDHWPresent->getCheckboxValue(); } + if (wmOtSummerWinterMode->getCheckboxValue() != settings.opentherm.summerWinterMode) { + changed = true; + + settings.opentherm.summerWinterMode = wmOtSummerWinterMode->getCheckboxValue(); + } + + if (wmOtHeatingCh2Enabled->getCheckboxValue() != settings.opentherm.heatingCh2Enabled) { + changed = true; + + settings.opentherm.heatingCh2Enabled = wmOtHeatingCh2Enabled->getCheckboxValue(); + } + + if (wmOtHeatingCh1ToCh2->getCheckboxValue() != settings.opentherm.heatingCh1ToCh2) { + changed = true; + + settings.opentherm.heatingCh1ToCh2 = wmOtHeatingCh1ToCh2->getCheckboxValue(); + } + if (wmOutdoorSensorPin->getValue() != settings.sensors.outdoor.pin) { changed = true; needRestart = true; @@ -265,6 +306,9 @@ protected: " OT out pin: %d\r\n" " OT member id code: %d\r\n" " OT DHW present: %d\r\n" + " OT summer/winter mode: %d\r\n" + " OT heating ch2 enabled: %d\r\n" + " OT heating ch1 to ch2: %d\r\n" " Outdoor sensor pin: %d\r\n" " Indoor sensor pin: %d\r\n", settings.hostname, @@ -278,6 +322,9 @@ protected: settings.opentherm.outPin, settings.opentherm.memberIdCode, settings.opentherm.dhwPresent, + settings.opentherm.summerWinterMode, + settings.opentherm.heatingCh2Enabled, + settings.opentherm.heatingCh1ToCh2, settings.sensors.outdoor.pin, settings.sensors.indoor.pin ); diff --git a/wokwi/nodemcu_32s/diagram.json b/wokwi/nodemcu_32s/diagram.json new file mode 100644 index 0000000..2b1d9fa --- /dev/null +++ b/wokwi/nodemcu_32s/diagram.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "editor": "wokwi", + "parts": [ + { "type": "wokwi-esp32-devkit-v1", "id": "esp", "top": 0, "left": 0, "attrs": {} } + ], + "connections": [ + [ "esp:TX0", "$serialMonitor:RX", "", [] ], + [ "esp:RX0", "$serialMonitor:TX", "", [] ] + ] + } + \ No newline at end of file diff --git a/wokwi/nodemcu_32s/wokwi.toml b/wokwi/nodemcu_32s/wokwi.toml new file mode 100644 index 0000000..524a1da --- /dev/null +++ b/wokwi/nodemcu_32s/wokwi.toml @@ -0,0 +1,8 @@ +[wokwi] +version = 1 +elf = "../../.pio/build/nodemcu_32s/firmware.elf" +firmware = "../../.pio/build/nodemcu_32s/firmware.bin" + +[[net.forward]] +from = "localhost:9080" +to = "target:80" \ No newline at end of file