14 Commits
1.2.1 ... 1.3.1

Author SHA1 Message Date
Yurii
6d3172b73b fix pid 2023-10-11 19:04:58 +03:00
Yurii
fca6dc9393 fix set hysteresis 2023-10-11 18:46:37 +03:00
Yurii
b54ea9b745 dependency update (temporary) 2023-10-09 06:59:41 +03:00
Yurii
5de3238f6f Added DHW present switch 2023-10-09 06:20:55 +03:00
Yurii
2270b12b36 upd readme 2023-09-22 00:20:45 +03:00
Yurii
fd4fd119da upd readme 2023-09-21 23:47:43 +03:00
Yurii
47849eab01 upd readme & pcb 2023-09-21 23:09:47 +03:00
Yurii
ef99d2af96 upd readme 2023-09-21 23:02:04 +03:00
Yurii
826581562a upd readme, small fix 2023-09-21 22:51:14 +03:00
Yurii
d10d44bd13 upd pcb 2023-09-21 06:23:26 +03:00
Yurii
229628fdc5 Many changes.
1. Migrate from microDS18B20 to DallasTemperature
2. Refactoring of sensors: added an external temperature sensor inside the house, added an "offset" parameter for sensors
3. Fixed PID
4. New parameters added:
- settings.heating.minTemp
- settings.heating.maxTemp
- settings.dhw.minTemp
- settings.dhw.maxTemp
- settings.pid.minTemp
- settings.pid.maxTemp
- settings.sensors.outdoor.type
- settings.sensors.outdoor.pin
- settings.sensors.outdoor.offset
- settings.sensors.indoor.type
- settings.sensors.indoor.pin
- settings.sensors.indoor.offset
5. Fixed and updated HomeAssistantHelper
7. Added check for validity of settings. After some updates, the settings may be reset to default, but this will prevent the settings from being distorted.
2023-09-21 05:18:05 +03:00
Laxilef
b0e01afecb Update README.md 2023-09-19 22:01:40 +03:00
Yurii
f544baee0a Merge branch 'master' of https://github.com/Laxilef/OTGateway 2023-09-19 21:59:46 +03:00
Yurii
3ff68f544f upd readme
upd gitignore
add ino file for arduino ide
2023-09-19 21:59:29 +03:00
21 changed files with 2743 additions and 1611 deletions

5
.gitignore vendored
View File

@@ -1,5 +1,2 @@
.pio .pio
.vscode/.browse.c_cpp.db* .vscode
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

101
README.md
View File

@@ -7,12 +7,12 @@
- PID - PID
- Equithermic curves - adjusts the temperature based on indoor and outdoor temperatures - Equithermic curves - adjusts the temperature based on indoor and outdoor temperatures
- Hysteresis setting (for accurate maintenance of room temperature) - Hysteresis setting (for accurate maintenance of room temperature)
- Ability to connect an external sensor to monitor outdoor temperature (DS18B20) - Ability to connect an external sensors to monitor outdoor and indoor temperature ([compatible sensors](#compatible-temperature-sensors))
- Emergency mode. If the Wi-Fi connection is lost or the gateway cannot connect to the MQTT server, the mode will turn on. This mode will automatically maintain the set temperature and prevent your home from freezing. In this mode it is also possible to use equithermal curves (weather-compensated control). - Emergency mode. If the Wi-Fi connection is lost or the gateway cannot connect to the MQTT server, the mode will turn on. This mode will automatically maintain the set temperature and prevent your home from freezing. In this mode it is also possible to use equithermal curves (weather-compensated control).
- Automatic error reset (not with all boilers) - Automatic error reset (not with all boilers)
- Diagnostics: - Diagnostics:
- The process of heating the coolant for heating: works / does not work - The process of heating the coolant for heating: works/does not work
- The process of heating water for hot water: working / not working - The process of heating water for hot water: working/not working
- Display of boiler errors - Display of boiler errors
- Burner status: on/off - Burner status: on/off
- Burner modulation level in percent - Burner modulation level in percent
@@ -28,52 +28,54 @@
![logo](/assets/ha.png) ![logo](/assets/ha.png)
## Tested on ## Tested on
| Boiler | Master Member ID | | Boiler | Master Member ID | Notes |
| --- | --- | | --- | --- | --- |
| BAXI ECO Nova | default or 4 | | BAXI ECO Nova | default | Pressure sensor not supported, modulation level not stable |
| BAXI Ampera | 1028 | | BAXI Ampera | 1028 | Pressure sensor not supported, only heating (DHW not tested) |
| [Remeha Calenta Ace 40C](https://github.com/Laxilef/OTGateway/issues/1#issuecomment-1726081554) | default | - |
## PCB ## PCB
<img src="/assets/pcb.svg" width="25%" /> <img src="/assets/pcb_3d.png" width="30%" /> <img src="/assets/after_assembly.png" width="37%" /> <img src="/assets/pcb.svg" width="27%" /> <img src="/assets/pcb_3d.png" width="30%" /> <img src="/assets/after_assembly.png" width="40%" />
Housing for installation on DIN rail - D2MG. Occupies only 2 DIN modules.<br> Housing for installation on DIN rail - D2MG. Occupies only 2 DIN modules.<br>
The 220V > 5V power supply is already on the board, so additional power supplies are not needed.<br> The 220V > 5V power supply is already on the board, so additional power supplies are not needed.<br>
To save money, 2 levels are ordered as one board. After manufacturing, the boards need to be divided into 2 parts - upper and lower.<br> To save money, 2 levels are ordered as one board. After manufacturing, the boards need to be divided into 2 parts - upper and lower. The boards are inexpensively (5pcs for $2) manufactured at JLCPCB (Remove Order Number = Specify a location).<br><br>
**Important!** On this board opentherm IN pin = 5, OUT pin = 4 Some components can be replaced with similar ones (for example use a fuse and led with legs). Some SMD components (for example optocouplers) can be replaced with similar SOT components.<br>Most of the components can be purchased inexpensively on Aliexpress, the rest in your local stores.<br><br>
The outdoor temperature sensor must be connected to the **TEMP1** connector, the indoor temperature sensor must be connected to the **TEMP2** connector. The power supply for the sensors must be connected to the **3.3V** connector, GND to **GND**.<br>
**The opentherm connection polarity does not matter.**
<!-- **Important!** On this board opentherm IN pin = 5, OUT pin = 4 -->
- [Sheet](/assets/sheet.pdf) - [Schematic](/assets/Schematic.pdf)
- [BOM](/assets/BOM.xlsx) - [BOM](/assets/BOM.xlsx)
- [Gerber](/assets/gerber.zip) - [Gerber](/assets/gerber.zip)
## Another compatible Open Therm Adapters ## Another compatible OpenTherm Adapters
- [Ihor Melnyk OpenTherm Adapter](http://ihormelnyk.com/opentherm_adapter) - [Ihor Melnyk OpenTherm Adapter](http://ihormelnyk.com/opentherm_adapter)
- [DIYLESS Master OpenTherm Shield](https://diyless.com/product/master-opentherm-shield)
- [OpenTherm master shield for Wemos/Lolin](https://www.tindie.com/products/thehognl/opentherm-master-shield-for-wemoslolin/) - [OpenTherm master shield for Wemos/Lolin](https://www.tindie.com/products/thehognl/opentherm-master-shield-for-wemoslolin/)
- And others. It's just that the adapter must implement [the schema](http://ihormelnyk.com/Content/Pages/opentherm_adapter/opentherm_adapter_schematic_o.png) - And others. It's just that the adapter must implement [the schema](http://ihormelnyk.com/Content/Pages/opentherm_adapter/opentherm_adapter_schematic_o.png)
## Compatible Temperature Sensors
* DS18B20
* DS1822
* DS1820
* MAX31820
* MAX31850
[See more](https://github.com/milesburton/Arduino-Temperature-Control-Library#usage)
# Quick Start # Quick Start
## Dependencies 1. Download the latest firmware from the [releases page](https://github.com/Laxilef/OTGateway/releases) (or compile yourself) and flash your ESP8266 board using the [ESP Flash Download Tool](https://www.espressif.com/en/support/download/other-tools) or other software.
- [ESP8266Scheduler](https://github.com/nrwiersma/ESP8266Scheduler) 2. Connect to *OpenTherm Gateway* hotspot, password: otgateway123456
- [NTPClient](https://github.com/arduino-libraries/NTPClient) 3. Open configuration page in browser: 192.168.4.1
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson) 4. Set up a connection to your wifi network
- [OpenTherm Library](https://github.com/ihormelnyk/opentherm_library) 5. Set up a connection to your MQTT server: ip, port, user, password
- [PubSubClient](https://github.com/knolleary/pubsubclient) 6. Set up a **Opentherm pin IN** & **Opentherm pin OUT**. No change for my board. Typically used **IN** = 4, **OUT** = 5
- [TelnetStream](https://github.com/jandrassy/TelnetStream) 7. Set up a **Outdoor sensor pin** & **Indoor sensor pin**. No change for my board.
- [EEManager](https://github.com/GyverLibs/EEManager) 8. if necessary, set up a the master member ID ([see more](#tested-on))
- [GyverPID](https://github.com/GyverLibs/GyverPID) 9. Restart module (required after changing OT pins and/or sensors pins!)
- [microDS18B20](https://github.com/GyverLibs/microDS18B20)
- [WiFiManager](https://github.com/tzapu/WiFiManager)
After connecting to your wifi network, you can go to the setup page at the address that ESP8266 received.
## Settings
1. Connect to *OpenTherm Gateway* hotspot, password: otgateway123456
2. Open configuration page in browser: 192.168.4.1
3. Set up a connection to your wifi network
4. Set up a connection to your MQTT server
5. Set up a **Opentherm pin IN** & **Opentherm pin OUT**. Typically used **IN** = 4, **OUT** = 5
6. if necessary, set the master member ID.
7. Restart module (required after changing OT pins!)
After connecting to your wifi network, you can go to the setup page at the address that esp8266 received.
The OTGateway device will be automatically added to homeassistant if MQTT server ip, login and password are correct. The OTGateway device will be automatically added to homeassistant if MQTT server ip, login and password are correct.
## HomeAsssistant settings ## HomeAsssistant settings
@@ -109,7 +111,7 @@ The temperature inside the house can be set using simple automation:
``` ```
</details> </details>
If your boiler does not support the installation of an outdoor temperature sensor or does not provide this value via the opentherm protocol, then you can use an external DS18B20 sensor or use automation. If your boiler does not support the installation of an outdoor temperature sensor or does not provide this value via the opentherm protocol, then you can use an external sensor or use automation.
<details> <details>
<summary>Simple automation</summary> <summary>Simple automation</summary>
@@ -166,13 +168,16 @@ Range: 0...10, default: 2, step 0.01
#### Instructions for fit coefficients: #### Instructions for fit coefficients:
**Tip.** I created a [table in Excel](/assets/equitherm_calc.xlsx) in which you can enter temperature parameters inside and outside the house and select coefficients. On the graph you can see the temperature that the boiler will set. **Tip.** I created a [table in Excel](/assets/equitherm_calc.xlsx) in which you can enter temperature parameters inside and outside the house and select coefficients. On the graph you can see the temperature that the boiler will set.
1. The first thing you need to do is to fit the curve (***N*** coefficient). If your home has low heat loss, then start with 0.5. Otherwise start at 0.7. When the temperature inside the house stops changing, increase or decrease the coefficient value in increments of 0.1 to select the optimal curve.<br> 1. Set the ***K*** and ***T*** coefficients to 0.
2. The first thing you need to do is to fit the curve (***N*** coefficient). If your home has low heat loss, then start with 0.5. Otherwise start at 0.7. When the temperature inside the house stops changing, increase or decrease the coefficient value in increments of 0.1 to select the optimal curve.<br>
Please note that passive heating (sun) will affect the house temperature during curve fitting. This process is not fast and will take you 1-2 days. Please note that passive heating (sun) will affect the house temperature during curve fitting. This process is not fast and will take you 1-2 days.
Important. During curve fitting, the temperature must be kept stable as the outside temperature changes. The temperature does not have to be equal to the set one.<br> Important. During curve fitting, the temperature must be kept stable as the outside temperature changes.<br>
For example. You fit curve 0.67; set temperature 23; the temperature in the house is 20 degrees while the outside temperature is -10 degrees and -5 degrees. This is good. At this stage, it is important for you to stabilize the indoor temperature at exactly 20 (+- 0.5) degrees.<br>
2. After fitting the curve, you must select the ***K*** coefficient. It influences the boiler temperature correction to maintain the set temperature. For example. You fit curve 0.67; set temperature 20; the temperature in the house is 20.1 degrees while the outside temperature is -10 degrees and -5 degrees. This is good.
For example. Set temperature: 23 degrees; temperature in the house: 20 degrees. Try setting it to 5 and see how the temperature in the house changes after stabilization. Select the value so that the temperature in the house is close to the set. 3. After fitting the curve, you must select the ***K*** coefficient. It influences the boiler temperature correction to maintain the set temperature.
3. Now you can choose the ***T*** coefficient. Simply put, it affects the sharpness of the temperature change. If you want fast heating, then set a high value (6-10), but then the room may overheat. If you want smooth heating, set 1-5. Choose the optimal value for yourself. For example. Set temperature: 23 degrees; temperature in the house: 20 degrees. Try setting it to 2 and see how the temperature in the house changes after stabilization. Select the value so that the temperature in the house is close to the set.
4. Now you can choose the ***T*** coefficient. Simply put, it affects the sharpness of the temperature change. If you want fast heating, then set a high value (6-10), but then the room may overheat. If you want smooth heating, set 1-5. Choose the optimal value for yourself.
5. Check to see if it works correctly at different set temperatures over several days.
Read more about the algorithm [here](https://wdn.su/blog/1154). Read more about the algorithm [here](https://wdn.su/blog/1154).
@@ -185,6 +190,18 @@ In Google you can find instructions for tuning the PID controller.
### Use Equitherm mode + PID mode ### Use Equitherm mode + PID mode
@todo @todo
## Dependencies
- [ESP8266Scheduler](https://github.com/nrwiersma/ESP8266Scheduler)
- [NTPClient](https://github.com/arduino-libraries/NTPClient)
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson)
- [OpenTherm Library](https://github.com/ihormelnyk/opentherm_library)
- [PubSubClient](https://github.com/knolleary/pubsubclient)
- [TelnetStream](https://github.com/jandrassy/TelnetStream)
- [EEManager](https://github.com/GyverLibs/EEManager)
- [GyverPID](https://github.com/GyverLibs/GyverPID)
- [DallasTemperature](https://github.com/milesburton/Arduino-Temperature-Control-Library)
- [WiFiManager](https://github.com/tzapu/WiFiManager)
## Debug ## Debug
To display DEBUG messages you must enable debug in settings (switch is disabled by default). To display DEBUG messages you must enable debug in settings (switch is disabled by default).
You can connect via Telnet to read messages. IP: esp8266 ip, port: 23 You can connect via Telnet to read messages. IP: ESP8266 ip, port: 23

1
assets/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/*.priv.*

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 689 KiB

After

Width:  |  Height:  |  Size: 716 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

After

Width:  |  Height:  |  Size: 236 KiB

View File

@@ -0,0 +1,38 @@
class IntParameter : public WiFiManagerParameter {
public:
IntParameter(const char* id, const char* label, int value, const uint8_t length = 10) : WiFiManagerParameter("") {
init(id, label, String(value).c_str(), length, "", WFM_LABEL_DEFAULT);
}
int getValue() {
return atoi(WiFiManagerParameter::getValue());
}
};
class CheckboxParameter : public WiFiManagerParameter {
public:
const char* checked = "type=\"checkbox\" checked";
const char* noChecked = "type=\"checkbox\"";
const char* trueVal = "T";
CheckboxParameter(const char* id, const char* label, bool value): WiFiManagerParameter("") {
init(id, label, value ? trueVal : "0", 1, "", WFM_LABEL_AFTER);
}
const char* getValue() const override {
return trueVal;
}
const char* getCustomHTML() const override {
return strcmp(WiFiManagerParameter::getValue(), trueVal) == 0 ? checked : noChecked;
}
bool getCheckboxValue() {
return strcmp(WiFiManagerParameter::getValue(), trueVal) == 0 ? true : false;
}
};
class SeparatorParameter : public WiFiManagerParameter {
public:
SeparatorParameter(): WiFiManagerParameter("<hr>") {}
};

4
otgateway.ino Normal file
View File

@@ -0,0 +1,4 @@
/*
This file is needed by the Arduino IDE because the ino file needs to be named as the directory name.
Don't worry, the Arduino compiler will "merge" all files, including src/main.cpp
*/

View File

@@ -21,8 +21,9 @@ lib_deps =
jandrassy/TelnetStream@^1.2.4 jandrassy/TelnetStream@^1.2.4
gyverlibs/EEManager@^2.0 gyverlibs/EEManager@^2.0
gyverlibs/GyverPID@^3.3 gyverlibs/GyverPID@^3.3
gyverlibs/microDS18B20@^3.10 milesburton/DallasTemperature@^3.11.0
https://github.com/tzapu/WiFiManager.git#v2.0.16-rc.2 https://github.com/Laxilef/WiFiManager/archive/refs/heads/patch-1.zip
; https://github.com/tzapu/WiFiManager.git#v2.0.16-rc.2
build_flags = -D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH build_flags = -D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH
upload_speed = 921600 upload_speed = 921600
monitor_speed = 115200 monitor_speed = 115200

View File

@@ -27,10 +27,10 @@ public:
_deviceConfigUrl = value; _deviceConfigUrl = value;
} }
bool publishSelectOutdoorTempSource(bool enabledByDefault = true) { bool publishSelectOutdoorSensorType(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc; StaticJsonDocument<1536> doc;
doc[F("command_topic")] = _prefix + F("/settings/set"); doc[F("command_topic")] = _prefix + F("/settings/set");
doc[F("command_template")] = F("{\"outdoorTempSource\": {% if value == 'Boiler' %}0{% elif value == 'Manual' %}1{% elif value == 'External' %}2{% endif %}}"); doc[F("command_template")] = F("{\"sensors\": {\"outdoor\": {\"type\": {% if value == 'Boiler' %}0{% elif value == 'Manual' %}1{% elif value == 'External' %}2{% endif %}}}}");
doc[F("device")][F("identifiers")][0] = _prefix; doc[F("device")][F("identifiers")][0] = _prefix;
doc[F("device")][F("sw_version")] = _deviceVersion; doc[F("device")][F("sw_version")] = _deviceVersion;
doc[F("device")][F("manufacturer")] = _deviceManufacturer; doc[F("device")][F("manufacturer")] = _deviceManufacturer;
@@ -40,17 +40,17 @@ public:
doc[F("device")][F("configuration_url")] = _deviceConfigUrl; doc[F("device")][F("configuration_url")] = _deviceConfigUrl;
} }
doc[F("enabled_by_default")] = enabledByDefault; doc[F("enabled_by_default")] = enabledByDefault;
doc[F("unique_id")] = _prefix + F("_outdoorTempSource"); doc[F("unique_id")] = _prefix + F("_outdoor_sensor_type");
doc[F("object_id")] = _prefix + F("_outdoorTempSource"); doc[F("object_id")] = _prefix + F("_outdoor_sensor_type");
doc[F("entity_category")] = F("config"); doc[F("entity_category")] = F("config");
doc[F("name")] = F("Outdoor temperature source"); doc[F("name")] = F("Outdoor temperature source");
doc[F("state_topic")] = _prefix + F("/settings"); doc[F("state_topic")] = _prefix + F("/settings");
doc[F("value_template")] = F("{% if value_json.outdoorTempSource == 0 %}Boiler{% elif value_json.outdoorTempSource == 1 %}Manual{% elif value_json.outdoorTempSource == 2 %}External{% endif %}"); doc[F("value_template")] = F("{% if value_json.sensors.outdoor.type == 0 %}Boiler{% elif value_json.sensors.outdoor.type == 1 %}Manual{% elif value_json.sensors.outdoor.type == 2 %}External{% endif %}");
doc[F("options")][0] = F("Boiler"); doc[F("options")][0] = F("Boiler");
doc[F("options")][1] = F("Manual"); doc[F("options")][1] = F("Manual");
doc[F("options")][2] = F("External"); doc[F("options")][2] = F("External");
client.beginPublish((F("homeassistant/select/") + _prefix + F("/outdoorTempSource/config")).c_str(), measureJson(doc), true); client.beginPublish((F("homeassistant/select/") + _prefix + F("/outdoor_sensor_type/config")).c_str(), measureJson(doc), true);
//BufferingPrint bufferedClient(client, 32); //BufferingPrint bufferedClient(client, 32);
//serializeJson(doc, bufferedClient); //serializeJson(doc, bufferedClient);
//bufferedClient.flush(); //bufferedClient.flush();
@@ -58,6 +58,105 @@ public:
return client.endPublish(); return client.endPublish();
} }
bool publishSelectIndoorSensorType(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc;
doc[F("command_topic")] = _prefix + F("/settings/set");
doc[F("command_template")] = F("{\"sensors\": {\"indoor\": {\"type\": {% if value == 'Manual' %}1{% elif value == 'External' %}2{% endif %}}}}");
doc[F("device")][F("identifiers")][0] = _prefix;
doc[F("device")][F("sw_version")] = _deviceVersion;
doc[F("device")][F("manufacturer")] = _deviceManufacturer;
doc[F("device")][F("model")] = _deviceModel;
doc[F("device")][F("name")] = _deviceName;
if (_deviceConfigUrl) {
doc[F("device")][F("configuration_url")] = _deviceConfigUrl;
}
doc[F("enabled_by_default")] = enabledByDefault;
doc[F("unique_id")] = _prefix + F("_indoor_sensor_type");
doc[F("object_id")] = _prefix + F("_indoor_sensor_type");
doc[F("entity_category")] = F("config");
doc[F("name")] = F("Indoor temperature source");
doc[F("state_topic")] = _prefix + F("/settings");
doc[F("value_template")] = F("{% if value_json.sensors.indoor.type == 1 %}Manual{% elif value_json.sensors.indoor.type == 2 %}External{% endif %}");
doc[F("options")][0] = F("Manual");
doc[F("options")][1] = F("External");
client.beginPublish((F("homeassistant/select/") + _prefix + F("/indoor_sensor_type/config")).c_str(), measureJson(doc), true);
//BufferingPrint bufferedClient(client, 32);
//serializeJson(doc, bufferedClient);
//bufferedClient.flush();
serializeJson(doc, client);
return client.endPublish();
}
bool publishNumberOutdoorSensorOffset(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc;
doc[F("availability")][F("topic")] = _prefix + F("/settings");
doc[F("availability")][F("value_template")] = F("{{ iif(value_json.sensors.outdoor.type != 1, 'online', 'offline') }}");
doc[F("device")][F("identifiers")][0] = _prefix;
doc[F("device")][F("sw_version")] = _deviceVersion;
doc[F("device")][F("manufacturer")] = _deviceManufacturer;
doc[F("device")][F("model")] = _deviceModel;
doc[F("device")][F("name")] = _deviceName;
if (_deviceConfigUrl) {
doc[F("device")][F("configuration_url")] = _deviceConfigUrl;
}
doc[F("enabled_by_default")] = enabledByDefault;
doc[F("unique_id")] = _prefix + F("_outdoor_sensor_offset");
doc[F("object_id")] = _prefix + F("_outdoor_sensor_offset");
doc[F("entity_category")] = F("config");
doc[F("device_class")] = F("temperature");
doc[F("unit_of_measurement")] = F("°C");
doc[F("name")] = F("Outdoor sensor offset");
doc[F("icon")] = F("mdi:altimeter");
doc[F("state_topic")] = _prefix + F("/settings");
doc[F("value_template")] = F("{{ value_json.sensors.outdoor.offset|float(0)|round(2) }}");
doc[F("command_topic")] = _prefix + F("/settings/set");
doc[F("command_template")] = F("{\"sensors\": {\"outdoor\" : {\"offset\" : {{ value }}}}}");
doc[F("min")] = -10;
doc[F("max")] = 10;
doc[F("step")] = 0.1;
doc[F("mode")] = "box";
client.beginPublish((F("homeassistant/number/") + _prefix + F("/outdoor_sensor_offset/config")).c_str(), measureJson(doc), true);
serializeJson(doc, client);
return client.endPublish();
}
bool publishNumberIndoorSensorOffset(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc;
doc[F("availability")][F("topic")] = _prefix + F("/settings");
doc[F("availability")][F("value_template")] = F("{{ iif(value_json.sensors.indoor.type != 1, 'online', 'offline') }}");
doc[F("device")][F("identifiers")][0] = _prefix;
doc[F("device")][F("sw_version")] = _deviceVersion;
doc[F("device")][F("manufacturer")] = _deviceManufacturer;
doc[F("device")][F("model")] = _deviceModel;
doc[F("device")][F("name")] = _deviceName;
if (_deviceConfigUrl) {
doc[F("device")][F("configuration_url")] = _deviceConfigUrl;
}
doc[F("enabled_by_default")] = enabledByDefault;
doc[F("unique_id")] = _prefix + F("_indoor_sensor_offset");
doc[F("object_id")] = _prefix + F("_indoor_sensor_offset");
doc[F("entity_category")] = F("config");
doc[F("device_class")] = F("temperature");
doc[F("unit_of_measurement")] = F("°C");
doc[F("name")] = F("Indoor sensor offset");
doc[F("icon")] = F("mdi:altimeter");
doc[F("state_topic")] = _prefix + F("/settings");
doc[F("value_template")] = F("{{ value_json.sensors.indoor.offset|float(0)|round(2) }}");
doc[F("command_topic")] = _prefix + F("/settings/set");
doc[F("command_template")] = F("{\"sensors\": {\"indoor\" : {\"offset\" : {{ value }}}}}");
doc[F("min")] = -10;
doc[F("max")] = 10;
doc[F("step")] = 0.1;
doc[F("mode")] = "box";
client.beginPublish((F("homeassistant/number/") + _prefix + F("/indoor_sensor_offset/config")).c_str(), measureJson(doc), true);
serializeJson(doc, client);
return client.endPublish();
}
bool publishSwitchDebug(bool enabledByDefault = true) { bool publishSwitchDebug(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc; StaticJsonDocument<1536> doc;
doc[F("device")][F("identifiers")][0] = _prefix; doc[F("device")][F("identifiers")][0] = _prefix;
@@ -148,6 +247,7 @@ public:
doc[F("min")] = 5; doc[F("min")] = 5;
doc[F("max")] = 50; doc[F("max")] = 50;
doc[F("step")] = 0.5; doc[F("step")] = 0.5;
doc[F("mode")] = "box";
client.beginPublish((F("homeassistant/number/") + _prefix + F("/emergency_target/config")).c_str(), measureJson(doc), true); client.beginPublish((F("homeassistant/number/") + _prefix + F("/emergency_target/config")).c_str(), measureJson(doc), true);
//BufferingPrint bufferedClient(client, 32); //BufferingPrint bufferedClient(client, 32);
@@ -160,7 +260,7 @@ public:
bool publishSwitchEmergencyUseEquitherm(bool enabledByDefault = true) { bool publishSwitchEmergencyUseEquitherm(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc; StaticJsonDocument<1536> doc;
doc[F("availability")][F("topic")] = _prefix + F("/settings"); doc[F("availability")][F("topic")] = _prefix + F("/settings");
doc[F("availability")][F("value_template")] = F("{{ iif(value_json.outdoorTempSource != 1, 'online', 'offline') }}"); doc[F("availability")][F("value_template")] = F("{{ iif(value_json.sensors.outdoor.type != 1, 'online', 'offline') }}");
doc[F("device")][F("identifiers")][0] = _prefix; doc[F("device")][F("identifiers")][0] = _prefix;
doc[F("device")][F("sw_version")] = _deviceVersion; doc[F("device")][F("sw_version")] = _deviceVersion;
doc[F("device")][F("manufacturer")] = _deviceManufacturer; doc[F("device")][F("manufacturer")] = _deviceManufacturer;
@@ -282,8 +382,9 @@ public:
doc[F("command_topic")] = _prefix + F("/settings/set"); doc[F("command_topic")] = _prefix + F("/settings/set");
doc[F("command_template")] = F("{\"heating\": {\"target\" : {{ value }}}}"); doc[F("command_template")] = F("{\"heating\": {\"target\" : {{ value }}}}");
doc[F("min")] = minTemp; doc[F("min")] = minTemp;
doc[F("max")] = maxTemp; doc[F("max")] = maxTemp <= minTemp ? maxTemp : maxTemp;
doc[F("step")] = 0.5; doc[F("step")] = 0.5;
doc[F("mode")] = "box";
client.beginPublish((F("homeassistant/number/") + _prefix + F("/heating_target/config")).c_str(), measureJson(doc), true); client.beginPublish((F("homeassistant/number/") + _prefix + F("/heating_target/config")).c_str(), measureJson(doc), true);
//BufferingPrint bufferedClient(client, 32); //BufferingPrint bufferedClient(client, 32);
@@ -318,6 +419,7 @@ public:
doc[F("min")] = 0; doc[F("min")] = 0;
doc[F("max")] = 5; doc[F("max")] = 5;
doc[F("step")] = 0.1; doc[F("step")] = 0.1;
doc[F("mode")] = "box";
client.beginPublish((F("homeassistant/number/") + _prefix + F("/heating_hysteresis/config")).c_str(), measureJson(doc), true); client.beginPublish((F("homeassistant/number/") + _prefix + F("/heating_hysteresis/config")).c_str(), measureJson(doc), true);
//BufferingPrint bufferedClient(client, 32); //BufferingPrint bufferedClient(client, 32);
@@ -358,6 +460,126 @@ public:
return client.endPublish(); return client.endPublish();
} }
bool publishSensorCurrentHeatingMinTemp(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc;
doc[F("availability")][F("topic")] = _prefix + F("/status");
doc[F("device")][F("identifiers")][0] = _prefix;
doc[F("device")][F("sw_version")] = _deviceVersion;
doc[F("device")][F("manufacturer")] = _deviceManufacturer;
doc[F("device")][F("model")] = _deviceModel;
doc[F("device")][F("name")] = _deviceName;
if (_deviceConfigUrl) {
doc[F("device")][F("configuration_url")] = _deviceConfigUrl;
}
doc[F("enabled_by_default")] = enabledByDefault;
doc[F("unique_id")] = _prefix + F("_current_heating_min_temp");
doc[F("object_id")] = _prefix + F("_current_heating_min_temp");
doc[F("entity_category")] = F("diagnostic");
doc[F("device_class")] = F("temperature");
doc[F("state_class")] = F("measurement");
doc[F("unit_of_measurement")] = F("°C");
doc[F("name")] = F("Current heating min temp");
doc[F("icon")] = F("mdi:thermometer-chevron-down");
doc[F("state_topic")] = _prefix + F("/state");
doc[F("value_template")] = F("{{ value_json.parameters.heatingMinTemp|int(0) }}");
client.beginPublish((F("homeassistant/sensor/") + _prefix + F("/current_heating_min_temp/config")).c_str(), measureJson(doc), true);
serializeJson(doc, client);
return client.endPublish();
}
bool publishSensorCurrentHeatingMaxTemp(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc;
doc[F("availability")][F("topic")] = _prefix + F("/status");
doc[F("device")][F("identifiers")][0] = _prefix;
doc[F("device")][F("sw_version")] = _deviceVersion;
doc[F("device")][F("manufacturer")] = _deviceManufacturer;
doc[F("device")][F("model")] = _deviceModel;
doc[F("device")][F("name")] = _deviceName;
if (_deviceConfigUrl) {
doc[F("device")][F("configuration_url")] = _deviceConfigUrl;
}
doc[F("enabled_by_default")] = enabledByDefault;
doc[F("unique_id")] = _prefix + F("_current_heating_max_temp");
doc[F("object_id")] = _prefix + F("_current_heating_max_temp");
doc[F("entity_category")] = F("diagnostic");
doc[F("device_class")] = F("temperature");
doc[F("state_class")] = F("measurement");
doc[F("unit_of_measurement")] = F("°C");
doc[F("name")] = F("Current heating max temp");
doc[F("icon")] = F("mdi:thermometer-chevron-up");
doc[F("state_topic")] = _prefix + F("/state");
doc[F("value_template")] = F("{{ value_json.parameters.heatingMaxTemp|int(0) }}");
client.beginPublish((F("homeassistant/sensor/") + _prefix + F("/current_heating_max_temp/config")).c_str(), measureJson(doc), true);
serializeJson(doc, client);
return client.endPublish();
}
bool publishNumberHeatingMinTemp(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc;
doc[F("device")][F("identifiers")][0] = _prefix;
doc[F("device")][F("sw_version")] = _deviceVersion;
doc[F("device")][F("manufacturer")] = _deviceManufacturer;
doc[F("device")][F("model")] = _deviceModel;
doc[F("device")][F("name")] = _deviceName;
if (_deviceConfigUrl) {
doc[F("device")][F("configuration_url")] = _deviceConfigUrl;
}
doc[F("enabled_by_default")] = enabledByDefault;
doc[F("unique_id")] = _prefix + F("_heating_min_temp");
doc[F("object_id")] = _prefix + F("_heating_min_temp");
doc[F("entity_category")] = F("config");
doc[F("device_class")] = F("temperature");
doc[F("unit_of_measurement")] = F("°C");
doc[F("name")] = F("Heating min temp");
doc[F("icon")] = F("mdi:thermometer-chevron-down");
doc[F("state_topic")] = _prefix + F("/settings");
doc[F("value_template")] = F("{{ value_json.heating.minTemp|float(0)|round(1) }}");
doc[F("command_topic")] = _prefix + F("/settings/set");
doc[F("command_template")] = F("{\"heating\": {\"minTemp\" : {{ value }}}}");
doc[F("min")] = 0;
doc[F("max")] = 99;
doc[F("step")] = 1;
doc[F("mode")] = "box";
client.beginPublish((F("homeassistant/number/") + _prefix + F("/heating_min_temp/config")).c_str(), measureJson(doc), true);
serializeJson(doc, client);
return client.endPublish();
}
bool publishNumberHeatingMaxTemp(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc;
doc[F("device")][F("identifiers")][0] = _prefix;
doc[F("device")][F("sw_version")] = _deviceVersion;
doc[F("device")][F("manufacturer")] = _deviceManufacturer;
doc[F("device")][F("model")] = _deviceModel;
doc[F("device")][F("name")] = _deviceName;
if (_deviceConfigUrl) {
doc[F("device")][F("configuration_url")] = _deviceConfigUrl;
}
doc[F("enabled_by_default")] = enabledByDefault;
doc[F("unique_id")] = _prefix + F("_heating_max_temp");
doc[F("object_id")] = _prefix + F("_heating_max_temp");
doc[F("entity_category")] = F("config");
doc[F("device_class")] = F("temperature");
doc[F("unit_of_measurement")] = F("°C");
doc[F("name")] = F("Heating max temp");
doc[F("icon")] = F("mdi:thermometer-chevron-up");
doc[F("state_topic")] = _prefix + F("/settings");
doc[F("value_template")] = F("{{ value_json.heating.maxTemp|float(0)|round(1) }}");
doc[F("command_topic")] = _prefix + F("/settings/set");
doc[F("command_template")] = F("{\"heating\": {\"maxTemp\" : {{ value }}}}");
doc[F("min")] = 1;
doc[F("max")] = 100;
doc[F("step")] = 1;
doc[F("mode")] = "box";
client.beginPublish((F("homeassistant/number/") + _prefix + F("/heating_max_temp/config")).c_str(), measureJson(doc), true);
serializeJson(doc, client);
return client.endPublish();
}
bool publishSwitchDHW(bool enabledByDefault = true) { bool publishSwitchDHW(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc; StaticJsonDocument<1536> doc;
@@ -416,8 +638,9 @@ public:
doc[F("command_topic")] = _prefix + F("/settings/set"); doc[F("command_topic")] = _prefix + F("/settings/set");
doc[F("command_template")] = F("{\"dhw\": {\"target\" : {{ value|int(0) }}}}"); doc[F("command_template")] = F("{\"dhw\": {\"target\" : {{ value|int(0) }}}}");
doc[F("min")] = minTemp; doc[F("min")] = minTemp;
doc[F("max")] = maxTemp; doc[F("max")] = maxTemp <= minTemp ? maxTemp : maxTemp;
doc[F("step")] = 1; doc[F("step")] = 1;
doc[F("mode")] = "box";
client.beginPublish((F("homeassistant/number/") + _prefix + F("/dhw_target/config")).c_str(), measureJson(doc), true); client.beginPublish((F("homeassistant/number/") + _prefix + F("/dhw_target/config")).c_str(), measureJson(doc), true);
//BufferingPrint bufferedClient(client, 32); //BufferingPrint bufferedClient(client, 32);
@@ -427,6 +650,126 @@ public:
return client.endPublish(); return client.endPublish();
} }
bool publishSensorCurrentDHWMinTemp(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc;
doc[F("availability")][F("topic")] = _prefix + F("/status");
doc[F("device")][F("identifiers")][0] = _prefix;
doc[F("device")][F("sw_version")] = _deviceVersion;
doc[F("device")][F("manufacturer")] = _deviceManufacturer;
doc[F("device")][F("model")] = _deviceModel;
doc[F("device")][F("name")] = _deviceName;
if (_deviceConfigUrl) {
doc[F("device")][F("configuration_url")] = _deviceConfigUrl;
}
doc[F("enabled_by_default")] = enabledByDefault;
doc[F("unique_id")] = _prefix + F("_current_dhw_min_temp");
doc[F("object_id")] = _prefix + F("_current_dhw_min_temp");
doc[F("entity_category")] = F("diagnostic");
doc[F("device_class")] = F("temperature");
doc[F("state_class")] = F("measurement");
doc[F("unit_of_measurement")] = F("°C");
doc[F("name")] = F("Current DHW min temp");
doc[F("icon")] = F("mdi:thermometer-chevron-down");
doc[F("state_topic")] = _prefix + F("/state");
doc[F("value_template")] = F("{{ value_json.parameters.dhwMinTemp|int(0) }}");
client.beginPublish((F("homeassistant/sensor/") + _prefix + F("/current_dhw_min_temp/config")).c_str(), measureJson(doc), true);
serializeJson(doc, client);
return client.endPublish();
}
bool publishSensorCurrentDHWMaxTemp(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc;
doc[F("availability")][F("topic")] = _prefix + F("/status");
doc[F("device")][F("identifiers")][0] = _prefix;
doc[F("device")][F("sw_version")] = _deviceVersion;
doc[F("device")][F("manufacturer")] = _deviceManufacturer;
doc[F("device")][F("model")] = _deviceModel;
doc[F("device")][F("name")] = _deviceName;
if (_deviceConfigUrl) {
doc[F("device")][F("configuration_url")] = _deviceConfigUrl;
}
doc[F("enabled_by_default")] = enabledByDefault;
doc[F("unique_id")] = _prefix + F("_current_dhw_max_temp");
doc[F("object_id")] = _prefix + F("_current_dhw_max_temp");
doc[F("entity_category")] = F("diagnostic");
doc[F("device_class")] = F("temperature");
doc[F("state_class")] = F("measurement");
doc[F("unit_of_measurement")] = F("°C");
doc[F("name")] = F("Current DHW max temp");
doc[F("icon")] = F("mdi:thermometer-chevron-up");
doc[F("state_topic")] = _prefix + F("/state");
doc[F("value_template")] = F("{{ value_json.parameters.dhwMaxTemp|int(0) }}");
client.beginPublish((F("homeassistant/sensor/") + _prefix + F("/current_dhw_max_temp/config")).c_str(), measureJson(doc), true);
serializeJson(doc, client);
return client.endPublish();
}
bool publishNumberDHWMinTemp(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc;
doc[F("device")][F("identifiers")][0] = _prefix;
doc[F("device")][F("sw_version")] = _deviceVersion;
doc[F("device")][F("manufacturer")] = _deviceManufacturer;
doc[F("device")][F("model")] = _deviceModel;
doc[F("device")][F("name")] = _deviceName;
if (_deviceConfigUrl) {
doc[F("device")][F("configuration_url")] = _deviceConfigUrl;
}
doc[F("enabled_by_default")] = enabledByDefault;
doc[F("unique_id")] = _prefix + F("_dhw_min_temp");
doc[F("object_id")] = _prefix + F("_dhw_min_temp");
doc[F("entity_category")] = F("config");
doc[F("device_class")] = F("temperature");
doc[F("unit_of_measurement")] = F("°C");
doc[F("name")] = F("DHW min temp");
doc[F("icon")] = F("mdi:thermometer-chevron-down");
doc[F("state_topic")] = _prefix + F("/settings");
doc[F("value_template")] = F("{{ value_json.dhw.minTemp|float(0)|round(1) }}");
doc[F("command_topic")] = _prefix + F("/settings/set");
doc[F("command_template")] = F("{\"dhw\": {\"minTemp\" : {{ value }}}}");
doc[F("min")] = 0;
doc[F("max")] = 99;
doc[F("step")] = 1;
doc[F("mode")] = "box";
client.beginPublish((F("homeassistant/number/") + _prefix + F("/dhw_min_temp/config")).c_str(), measureJson(doc), true);
serializeJson(doc, client);
return client.endPublish();
}
bool publishNumberDHWMaxTemp(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc;
doc[F("device")][F("identifiers")][0] = _prefix;
doc[F("device")][F("sw_version")] = _deviceVersion;
doc[F("device")][F("manufacturer")] = _deviceManufacturer;
doc[F("device")][F("model")] = _deviceModel;
doc[F("device")][F("name")] = _deviceName;
if (_deviceConfigUrl) {
doc[F("device")][F("configuration_url")] = _deviceConfigUrl;
}
doc[F("enabled_by_default")] = enabledByDefault;
doc[F("unique_id")] = _prefix + F("_dhw_max_temp");
doc[F("object_id")] = _prefix + F("_dhw_max_temp");
doc[F("entity_category")] = F("config");
doc[F("device_class")] = F("temperature");
doc[F("unit_of_measurement")] = F("°C");
doc[F("name")] = F("DHW max temp");
doc[F("icon")] = F("mdi:thermometer-chevron-up");
doc[F("state_topic")] = _prefix + F("/settings");
doc[F("value_template")] = F("{{ value_json.dhw.maxTemp|float(0)|round(1) }}");
doc[F("command_topic")] = _prefix + F("/settings/set");
doc[F("command_template")] = F("{\"dhw\": {\"maxTemp\" : {{ value }}}}");
doc[F("min")] = 1;
doc[F("max")] = 100;
doc[F("step")] = 1;
doc[F("mode")] = "box";
client.beginPublish((F("homeassistant/number/") + _prefix + F("/dhw_max_temp/config")).c_str(), measureJson(doc), true);
serializeJson(doc, client);
return client.endPublish();
}
bool publishSwitchPID(bool enabledByDefault = true) { bool publishSwitchPID(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc; StaticJsonDocument<1536> doc;
@@ -482,6 +825,7 @@ public:
doc[F("min")] = 0.001; doc[F("min")] = 0.001;
doc[F("max")] = 10; doc[F("max")] = 10;
doc[F("step")] = 0.001; doc[F("step")] = 0.001;
doc[F("mode")] = "box";
client.beginPublish((F("homeassistant/number/") + _prefix + F("/pid_p_factor/config")).c_str(), measureJson(doc), true); client.beginPublish((F("homeassistant/number/") + _prefix + F("/pid_p_factor/config")).c_str(), measureJson(doc), true);
//BufferingPrint bufferedClient(client, 32); //BufferingPrint bufferedClient(client, 32);
@@ -513,6 +857,7 @@ public:
doc[F("min")] = 0; doc[F("min")] = 0;
doc[F("max")] = 10; doc[F("max")] = 10;
doc[F("step")] = 0.001; doc[F("step")] = 0.001;
doc[F("mode")] = "box";
client.beginPublish((F("homeassistant/number/") + _prefix + F("/pid_i_factor/config")).c_str(), measureJson(doc), true); client.beginPublish((F("homeassistant/number/") + _prefix + F("/pid_i_factor/config")).c_str(), measureJson(doc), true);
//BufferingPrint bufferedClient(client, 32); //BufferingPrint bufferedClient(client, 32);
@@ -544,6 +889,7 @@ public:
doc[F("min")] = 0; doc[F("min")] = 0;
doc[F("max")] = 10; doc[F("max")] = 10;
doc[F("step")] = 0.001; doc[F("step")] = 0.001;
doc[F("mode")] = "box";
client.beginPublish((F("homeassistant/number/") + _prefix + F("/pid_d_factor/config")).c_str(), measureJson(doc), true); client.beginPublish((F("homeassistant/number/") + _prefix + F("/pid_d_factor/config")).c_str(), measureJson(doc), true);
//BufferingPrint bufferedClient(client, 32); //BufferingPrint bufferedClient(client, 32);
@@ -553,6 +899,70 @@ public:
return client.endPublish(); return client.endPublish();
} }
bool publishNumberPIDMinTemp(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc;
doc[F("device")][F("identifiers")][0] = _prefix;
doc[F("device")][F("sw_version")] = _deviceVersion;
doc[F("device")][F("manufacturer")] = _deviceManufacturer;
doc[F("device")][F("model")] = _deviceModel;
doc[F("device")][F("name")] = _deviceName;
if (_deviceConfigUrl) {
doc[F("device")][F("configuration_url")] = _deviceConfigUrl;
}
doc[F("enabled_by_default")] = enabledByDefault;
doc[F("unique_id")] = _prefix + F("_pid_min_temp");
doc[F("object_id")] = _prefix + F("_pid_min_temp");
doc[F("entity_category")] = F("config");
doc[F("device_class")] = F("temperature");
doc[F("unit_of_measurement")] = F("°C");
doc[F("name")] = F("PID min temp");
doc[F("icon")] = F("mdi:thermometer-chevron-down");
doc[F("state_topic")] = _prefix + F("/settings");
doc[F("value_template")] = F("{{ value_json.pid.minTemp|float(0)|round(1) }}");
doc[F("command_topic")] = _prefix + F("/settings/set");
doc[F("command_template")] = F("{\"pid\": {\"minTemp\" : {{ value }}}}");
doc[F("min")] = 0;
doc[F("max")] = 99;
doc[F("step")] = 1;
doc[F("mode")] = "box";
client.beginPublish((F("homeassistant/number/") + _prefix + F("/pid_min_temp/config")).c_str(), measureJson(doc), true);
serializeJson(doc, client);
return client.endPublish();
}
bool publishNumberPIDMaxTemp(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc;
doc[F("device")][F("identifiers")][0] = _prefix;
doc[F("device")][F("sw_version")] = _deviceVersion;
doc[F("device")][F("manufacturer")] = _deviceManufacturer;
doc[F("device")][F("model")] = _deviceModel;
doc[F("device")][F("name")] = _deviceName;
if (_deviceConfigUrl) {
doc[F("device")][F("configuration_url")] = _deviceConfigUrl;
}
doc[F("enabled_by_default")] = enabledByDefault;
doc[F("unique_id")] = _prefix + F("_pid_max_temp");
doc[F("object_id")] = _prefix + F("_pid_max_temp");
doc[F("entity_category")] = F("config");
doc[F("device_class")] = F("temperature");
doc[F("unit_of_measurement")] = F("°C");
doc[F("name")] = F("PID max temp");
doc[F("icon")] = F("mdi:thermometer-chevron-up");
doc[F("state_topic")] = _prefix + F("/settings");
doc[F("value_template")] = F("{{ value_json.pid.maxTemp|float(0)|round(1) }}");
doc[F("command_topic")] = _prefix + F("/settings/set");
doc[F("command_template")] = F("{\"pid\": {\"maxTemp\" : {{ value }}}}");
doc[F("min")] = 1;
doc[F("max")] = 100;
doc[F("step")] = 1;
doc[F("mode")] = "box";
client.beginPublish((F("homeassistant/number/") + _prefix + F("/pid_max_temp/config")).c_str(), measureJson(doc), true);
serializeJson(doc, client);
return client.endPublish();
}
bool publishSwitchEquitherm(bool enabledByDefault = true) { bool publishSwitchEquitherm(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc; StaticJsonDocument<1536> doc;
@@ -608,6 +1018,7 @@ public:
doc[F("min")] = 0.001; doc[F("min")] = 0.001;
doc[F("max")] = 5; doc[F("max")] = 5;
doc[F("step")] = 0.001; doc[F("step")] = 0.001;
doc[F("mode")] = "box";
client.beginPublish((F("homeassistant/number/") + _prefix + F("/equitherm_n_factor/config")).c_str(), measureJson(doc), true); client.beginPublish((F("homeassistant/number/") + _prefix + F("/equitherm_n_factor/config")).c_str(), measureJson(doc), true);
//BufferingPrint bufferedClient(client, 32); //BufferingPrint bufferedClient(client, 32);
@@ -639,6 +1050,7 @@ public:
doc[F("min")] = 0; doc[F("min")] = 0;
doc[F("max")] = 10; doc[F("max")] = 10;
doc[F("step")] = 0.01; doc[F("step")] = 0.01;
doc[F("mode")] = "box";
client.beginPublish((F("homeassistant/number/") + _prefix + F("/equitherm_k_factor/config")).c_str(), measureJson(doc), true); client.beginPublish((F("homeassistant/number/") + _prefix + F("/equitherm_k_factor/config")).c_str(), measureJson(doc), true);
//BufferingPrint bufferedClient(client, 32); //BufferingPrint bufferedClient(client, 32);
@@ -672,6 +1084,7 @@ public:
doc[F("min")] = 0; doc[F("min")] = 0;
doc[F("max")] = 10; doc[F("max")] = 10;
doc[F("step")] = 0.01; doc[F("step")] = 0.01;
doc[F("mode")] = "box";
client.beginPublish((F("homeassistant/number/") + _prefix + F("/equitherm_t_factor/config")).c_str(), measureJson(doc), true); client.beginPublish((F("homeassistant/number/") + _prefix + F("/equitherm_t_factor/config")).c_str(), measureJson(doc), true);
//BufferingPrint bufferedClient(client, 32); //BufferingPrint bufferedClient(client, 32);
@@ -979,7 +1392,7 @@ public:
return client.endPublish(); return client.endPublish();
} }
bool publishSensorRssi(bool enabledByDefault = false) { bool publishSensorRssi(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc; StaticJsonDocument<1536> doc;
doc["device"]["identifiers"][0] = _prefix; doc["device"]["identifiers"][0] = _prefix;
doc["device"]["sw_version"] = _deviceVersion; doc["device"]["sw_version"] = _deviceVersion;
@@ -1076,7 +1489,6 @@ public:
bool publishNumberIndoorTemp(bool enabledByDefault = true) { bool publishNumberIndoorTemp(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc; StaticJsonDocument<1536> doc;
//doc[F("availability")][F("topic")] = _prefix + F("/status");
doc[F("device")][F("identifiers")][0] = _prefix; doc[F("device")][F("identifiers")][0] = _prefix;
doc[F("device")][F("sw_version")] = _deviceVersion; doc[F("device")][F("sw_version")] = _deviceVersion;
doc[F("device")][F("manufacturer")] = _deviceManufacturer; doc[F("device")][F("manufacturer")] = _deviceManufacturer;
@@ -1097,8 +1509,10 @@ public:
doc[F("value_template")] = F("{{ value_json.temperatures.indoor|float(0)|round(1) }}"); doc[F("value_template")] = F("{{ value_json.temperatures.indoor|float(0)|round(1) }}");
doc[F("command_topic")] = _prefix + F("/state/set"); doc[F("command_topic")] = _prefix + F("/state/set");
doc[F("command_template")] = F("{\"temperatures\": {\"indoor\":{{ value }}}}"); doc[F("command_template")] = F("{\"temperatures\": {\"indoor\":{{ value }}}}");
doc[F("min")] = -70; doc[F("min")] = -99;
doc[F("max")] = 50; doc[F("max")] = 99;
doc[F("step")] = 0.01;
doc[F("mode")] = "box";
client.beginPublish((F("homeassistant/number/") + _prefix + F("/indoor_temp/config")).c_str(), measureJson(doc), true); client.beginPublish((F("homeassistant/number/") + _prefix + F("/indoor_temp/config")).c_str(), measureJson(doc), true);
//BufferingPrint bufferedClient(client, 32); //BufferingPrint bufferedClient(client, 32);
@@ -1108,6 +1522,38 @@ public:
return client.endPublish(); return client.endPublish();
} }
bool publishSensorIndoorTemp(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc;
doc[F("availability")][0][F("topic")] = _prefix + F("/status");
doc[F("availability_mode")] = F("any");
doc[F("device")][F("identifiers")][0] = _prefix;
doc[F("device")][F("sw_version")] = _deviceVersion;
doc[F("device")][F("manufacturer")] = _deviceManufacturer;
doc[F("device")][F("model")] = _deviceModel;
doc[F("device")][F("name")] = _deviceName;
if (_deviceConfigUrl) {
doc[F("device")][F("configuration_url")] = _deviceConfigUrl;
}
doc[F("enabled_by_default")] = enabledByDefault;
doc[F("unique_id")] = _prefix + F("_indoor_temp");
doc[F("object_id")] = _prefix + F("_indoor_temp");
doc[F("entity_category")] = F("diagnostic");
doc[F("device_class")] = F("temperature");
doc[F("state_class")] = F("measurement");
doc[F("unit_of_measurement")] = F("°C");
doc[F("name")] = F("Indoor temperature");
doc[F("icon")] = F("mdi:home-thermometer");
doc[F("state_topic")] = _prefix + F("/state");
doc[F("value_template")] = F("{{ value_json.temperatures.indoor|float(0)|round(1) }}");
client.beginPublish((F("homeassistant/sensor/") + _prefix + F("/indoor_temp/config")).c_str(), measureJson(doc), true);
//BufferingPrint bufferedClient(client, 32);
//serializeJson(doc, bufferedClient);
//bufferedClient.flush();
serializeJson(doc, client);
return client.endPublish();
}
bool publishNumberOutdoorTemp(bool enabledByDefault = true) { bool publishNumberOutdoorTemp(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc; StaticJsonDocument<1536> doc;
doc[F("device")][F("identifiers")][0] = _prefix; doc[F("device")][F("identifiers")][0] = _prefix;
@@ -1130,8 +1576,10 @@ public:
doc[F("value_template")] = F("{{ value_json.temperatures.outdoor|float(0)|round(1) }}"); doc[F("value_template")] = F("{{ value_json.temperatures.outdoor|float(0)|round(1) }}");
doc[F("command_topic")] = _prefix + F("/state/set"); doc[F("command_topic")] = _prefix + F("/state/set");
doc[F("command_template")] = F("{\"temperatures\": {\"outdoor\":{{ value }}}}"); doc[F("command_template")] = F("{\"temperatures\": {\"outdoor\":{{ value }}}}");
doc[F("min")] = -70; doc[F("min")] = -99;
doc[F("max")] = 50; doc[F("max")] = 99;
doc[F("step")] = 0.01;
doc[F("mode")] = "box";
client.beginPublish((F("homeassistant/number/") + _prefix + F("/outdoor_temp/config")).c_str(), measureJson(doc), true); client.beginPublish((F("homeassistant/number/") + _prefix + F("/outdoor_temp/config")).c_str(), measureJson(doc), true);
//BufferingPrint bufferedClient(client, 32); //BufferingPrint bufferedClient(client, 32);
@@ -1144,8 +1592,6 @@ public:
bool publishSensorOutdoorTemp(bool enabledByDefault = true) { bool publishSensorOutdoorTemp(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc; StaticJsonDocument<1536> doc;
doc[F("availability")][0][F("topic")] = _prefix + F("/status"); doc[F("availability")][0][F("topic")] = _prefix + F("/status");
doc[F("availability")][1][F("topic")] = _prefix + F("/settings");
doc[F("availability")][1][F("value_template")] = F("{{ iif(value_json.outdoorTempSource == 2, 'online', 'offline') }}");
doc[F("availability_mode")] = F("any"); doc[F("availability_mode")] = F("any");
doc[F("device")][F("identifiers")][0] = _prefix; doc[F("device")][F("identifiers")][0] = _prefix;
doc[F("device")][F("sw_version")] = _deviceVersion; doc[F("device")][F("sw_version")] = _deviceVersion;
@@ -1353,6 +1799,50 @@ public:
return client.publish((F("homeassistant/sensor/") + _prefix + F("/outdoor_temp/config")).c_str(), NULL, true); return client.publish((F("homeassistant/sensor/") + _prefix + F("/outdoor_temp/config")).c_str(), NULL, true);
} }
bool deleteNumberIndoorTemp() {
return client.publish((F("homeassistant/number/") + _prefix + F("/indoor_temp/config")).c_str(), NULL, true);
}
bool deleteSensorIndoorTemp() {
return client.publish((F("homeassistant/sensor/") + _prefix + F("/indoor_temp/config")).c_str(), NULL, true);
}
bool deleteSwitchDHW() {
return client.publish((F("homeassistant/switch/") + _prefix + F("/dhw/config")).c_str(), NULL, true);
}
bool deleteSensorCurrentDHWMinTemp() {
return client.publish((F("homeassistant/sensor/") + _prefix + F("/current_dhw_min_temp/config")).c_str(), NULL, true);
}
bool deleteSensorCurrentDHWMaxTemp() {
return client.publish((F("homeassistant/sensor/") + _prefix + F("/current_dhw_max_temp/config")).c_str(), NULL, true);
}
bool deleteNumberDHWMinTemp() {
return client.publish((F("homeassistant/number/") + _prefix + F("/dhw_min_temp/config")).c_str(), NULL, true);
}
bool deleteNumberDHWMaxTemp() {
return client.publish((F("homeassistant/number/") + _prefix + F("/dhw_max_temp/config")).c_str(), NULL, true);
}
bool deleteBinSensorDHW() {
return client.publish((F("homeassistant/binary_sensor/") + _prefix + F("/dhw/config")).c_str(), NULL, true);
}
bool deleteSensorDHWTemp() {
return client.publish((F("homeassistant/sensor/") + _prefix + F("/dhw_temp/config")).c_str(), NULL, true);
}
bool deleteNumberDHWTarget() {
return client.publish((F("homeassistant/number/") + _prefix + F("/dhw_target/config")).c_str(), NULL, true);
}
bool deleteClimateDHW() {
return client.publish((F("homeassistant/climate/") + _prefix + F("_dhw/config")).c_str(), NULL, true);
}
private: private:
String _prefix = "opentherm"; String _prefix = "opentherm";
String _deviceVersion = "1.0"; String _deviceVersion = "1.0";

View File

@@ -13,7 +13,6 @@ protected:
void setup() { void setup() {
pinMode(LED_STATUS_PIN, OUTPUT); pinMode(LED_STATUS_PIN, OUTPUT);
//pinMode(LED_OT_RX_PIN, OUTPUT);
} }
void loop() { void loop() {
@@ -49,12 +48,6 @@ protected:
} }
} }
if (!tSensors->isEnabled() && settings.outdoorTempSource == 2) {
tSensors->enable();
} else if (tSensors->isEnabled() && settings.outdoorTempSource != 2) {
tSensors->disable();
}
if (!tOt->isEnabled() && settings.opentherm.inPin > 0 && settings.opentherm.outPin > 0 && settings.opentherm.inPin != settings.opentherm.outPin) { if (!tOt->isEnabled() && settings.opentherm.inPin > 0 && settings.opentherm.outPin > 0 && settings.opentherm.inPin != settings.opentherm.outPin) {
tOt->enable(); tOt->enable();
} }

View File

@@ -19,7 +19,6 @@ protected:
void setup() { void setup() {
DEBUG("[MQTT] Started"); DEBUG("[MQTT] Started");
client.setServer(settings.mqtt.server, settings.mqtt.port);
client.setCallback(__callback); client.setCallback(__callback);
haHelper.setPrefix(settings.mqtt.prefix); haHelper.setPrefix(settings.mqtt.prefix);
haHelper.setDeviceVersion(OT_GATEWAY_VERSION); haHelper.setDeviceVersion(OT_GATEWAY_VERSION);
@@ -32,6 +31,7 @@ protected:
if (!client.connected() && millis() - lastReconnectAttempt >= MQTT_RECONNECT_INTERVAL) { if (!client.connected() && millis() - lastReconnectAttempt >= MQTT_RECONNECT_INTERVAL) {
INFO_F("Mqtt not connected, state: %i, connecting to server %s...\n", client.state(), settings.mqtt.server); INFO_F("Mqtt not connected, state: %i, connecting to server %s...\n", client.state(), settings.mqtt.server);
client.setServer(settings.mqtt.server, settings.mqtt.port);
if (client.connect(settings.hostname, settings.mqtt.user, settings.mqtt.password)) { if (client.connect(settings.hostname, settings.mqtt.user, settings.mqtt.password)) {
INFO("Connected to MQTT server"); INFO("Connected to MQTT server");
@@ -93,15 +93,6 @@ protected:
flag = true; flag = true;
} }
if (!doc["outdoorTempSource"].isNull() && doc["outdoorTempSource"].is<int>() && doc["outdoorTempSource"] >= 0 && doc["outdoorTempSource"] <= 2) {
settings.outdoorTempSource = doc["outdoorTempSource"];
flag = true;
}
if (!doc["mqtt"]["interval"].isNull() && doc["mqtt"]["interval"].is<int>() && doc["mqtt"]["interval"] >= 1000 && doc["mqtt"]["interval"] <= 120000) {
settings.mqtt.interval = doc["mqtt"]["interval"].as<unsigned int>();
flag = true;
}
// emergency // emergency
if (!doc["emergency"]["enable"].isNull() && doc["emergency"]["enable"].is<bool>()) { if (!doc["emergency"]["enable"].isNull() && doc["emergency"]["enable"].is<bool>()) {
@@ -109,9 +100,11 @@ protected:
flag = true; flag = true;
} }
if (!doc["emergency"]["target"].isNull() && (doc["emergency"]["target"].is<float>() || doc["emergency"]["target"].is<int>())) { if (!doc["emergency"]["target"].isNull() && doc["emergency"]["target"].is<float>()) {
settings.emergency.target = round(doc["emergency"]["target"].as<float>() * 10) / 10; if ( doc["emergency"]["target"].as<float>() > 0 && doc["emergency"]["target"].as<float>() < 100 ) {
flag = true; settings.emergency.target = round(doc["emergency"]["target"].as<float>() * 10) / 10;
flag = true;
}
} }
if (!doc["emergency"]["useEquitherm"].isNull() && doc["emergency"]["useEquitherm"].is<bool>()) { if (!doc["emergency"]["useEquitherm"].isNull() && doc["emergency"]["useEquitherm"].is<bool>()) {
@@ -119,6 +112,7 @@ protected:
flag = true; flag = true;
} }
// heating // heating
if (!doc["heating"]["enable"].isNull() && doc["heating"]["enable"].is<bool>()) { if (!doc["heating"]["enable"].isNull() && doc["heating"]["enable"].is<bool>()) {
settings.heating.enable = doc["heating"]["enable"].as<bool>(); settings.heating.enable = doc["heating"]["enable"].as<bool>();
@@ -130,46 +124,106 @@ protected:
flag = true; flag = true;
} }
if (!doc["heating"]["target"].isNull() && (doc["heating"]["target"].is<float>() || doc["heating"]["target"].is<int>())) { if (!doc["heating"]["target"].isNull() && doc["heating"]["target"].is<float>()) {
settings.heating.target = round(doc["heating"]["target"].as<float>() * 10) / 10; if ( doc["heating"]["target"].as<float>() > 0 && doc["heating"]["target"].as<float>() < 100 ) {
flag = true; settings.heating.target = round(doc["heating"]["target"].as<float>() * 10) / 10;
flag = true;
}
} }
if (!doc["heating"]["hysteresis"].isNull() && (doc["heating"]["hysteresis"].is<float>() || doc["heating"]["hysteresis"].is<int>())) { if (!doc["heating"]["hysteresis"].isNull() && doc["heating"]["hysteresis"].is<float>()) {
settings.heating.hysteresis = round(doc["heating"]["hysteresis"].as<float>() * 10) / 10; if ( doc["heating"]["hysteresis"].as<float>() >= 0 && doc["heating"]["hysteresis"].as<float>() <= 5 ) {
flag = true; settings.heating.hysteresis = round(doc["heating"]["hysteresis"].as<float>() * 10) / 10;
flag = true;
}
} }
if (!doc["heating"]["maxTemp"].isNull() && doc["heating"]["maxTemp"].is<unsigned char>()) {
if ( doc["heating"]["maxTemp"].as<unsigned char>() > 0 && doc["heating"]["maxTemp"].as<unsigned char>() <= 100 && doc["heating"]["maxTemp"].as<unsigned char>() > settings.heating.minTemp ) {
settings.heating.maxTemp = doc["heating"]["maxTemp"].as<unsigned char>();
vars.parameters.heatingMaxTemp = settings.heating.maxTemp;
flag = true;
}
}
if (!doc["heating"]["minTemp"].isNull() && doc["heating"]["minTemp"].is<unsigned char>()) {
if ( doc["heating"]["minTemp"].as<unsigned char>() >= 0 && doc["heating"]["minTemp"].as<unsigned char>() < 100 && doc["heating"]["minTemp"].as<unsigned char>() < settings.heating.maxTemp ) {
settings.heating.minTemp = doc["heating"]["minTemp"].as<unsigned char>();
vars.parameters.heatingMinTemp = settings.heating.minTemp;
flag = true;
}
}
// dhw // dhw
if (!doc["dhw"]["enable"].isNull() && doc["dhw"]["enable"].is<bool>()) { if (!doc["dhw"]["enable"].isNull() && doc["dhw"]["enable"].is<bool>()) {
settings.dhw.enable = doc["dhw"]["enable"].as<bool>(); settings.dhw.enable = doc["dhw"]["enable"].as<bool>();
flag = true; flag = true;
} }
if (!doc["dhw"]["target"].isNull() && doc["dhw"]["target"].is<int>()) { if (!doc["dhw"]["target"].isNull() && doc["dhw"]["target"].is<unsigned char>()) {
settings.dhw.target = doc["dhw"]["target"].as<int>(); if ( doc["dhw"]["target"].as<unsigned char>() >= 0 && doc["dhw"]["target"].as<unsigned char>() < 100 ) {
flag = true; settings.dhw.target = doc["dhw"]["target"].as<unsigned char>();
flag = true;
}
} }
if (!doc["dhw"]["maxTemp"].isNull() && doc["dhw"]["maxTemp"].is<unsigned char>()) {
if ( doc["dhw"]["maxTemp"].as<unsigned char>() > 0 && doc["dhw"]["maxTemp"].as<unsigned char>() <= 100 && doc["dhw"]["maxTemp"].as<unsigned char>() > settings.dhw.minTemp ) {
settings.dhw.maxTemp = doc["dhw"]["maxTemp"].as<unsigned char>();
vars.parameters.dhwMaxTemp = settings.dhw.maxTemp;
flag = true;
}
}
if (!doc["dhw"]["minTemp"].isNull() && doc["dhw"]["minTemp"].is<unsigned char>()) {
if ( doc["dhw"]["minTemp"].as<unsigned char>() >= 0 && doc["dhw"]["minTemp"].as<unsigned char>() < 100 && doc["dhw"]["minTemp"].as<unsigned char>() < settings.dhw.maxTemp ) {
settings.dhw.minTemp = doc["dhw"]["minTemp"].as<unsigned char>();
vars.parameters.dhwMinTemp = settings.dhw.minTemp;
flag = true;
}
}
// pid // pid
if (!doc["pid"]["enable"].isNull() && doc["pid"]["enable"].is<bool>()) { if (!doc["pid"]["enable"].isNull() && doc["pid"]["enable"].is<bool>()) {
settings.pid.enable = doc["pid"]["enable"].as<bool>(); settings.pid.enable = doc["pid"]["enable"].as<bool>();
flag = true; flag = true;
} }
if (!doc["pid"]["p_factor"].isNull() && (doc["pid"]["p_factor"].is<float>() || doc["pid"]["p_factor"].is<int>())) { if (!doc["pid"]["p_factor"].isNull() && doc["pid"]["p_factor"].is<float>()) {
settings.pid.p_factor = round(doc["pid"]["p_factor"].as<float>() * 1000) / 1000; if ( doc["pid"]["p_factor"].as<float>() >= 0 && doc["pid"]["p_factor"].as<float>() <= 20 ) {
flag = true; settings.pid.p_factor = round(doc["pid"]["p_factor"].as<float>() * 1000) / 1000;
flag = true;
}
} }
if (!doc["pid"]["i_factor"].isNull() && (doc["pid"]["i_factor"].is<float>() || doc["pid"]["i_factor"].is<int>())) { if (!doc["pid"]["i_factor"].isNull() && doc["pid"]["i_factor"].is<float>()) {
settings.pid.i_factor = round(doc["pid"]["i_factor"].as<float>() * 1000) / 1000; if ( doc["pid"]["i_factor"].as<float>() >= 0 && doc["pid"]["i_factor"].as<float>() <= 20 ) {
flag = true; settings.pid.i_factor = round(doc["pid"]["i_factor"].as<float>() * 1000) / 1000;
flag = true;
}
} }
if (!doc["pid"]["d_factor"].isNull() && (doc["pid"]["d_factor"].is<float>() || doc["pid"]["d_factor"].is<int>())) { if (!doc["pid"]["d_factor"].isNull() && doc["pid"]["d_factor"].is<float>()) {
settings.pid.d_factor = round(doc["pid"]["d_factor"].as<float>() * 1000) / 1000; if ( doc["pid"]["d_factor"].as<float>() >= 0 && doc["pid"]["d_factor"].as<float>() <= 20 ) {
flag = true; settings.pid.d_factor = round(doc["pid"]["d_factor"].as<float>() * 1000) / 1000;
flag = true;
}
}
if (!doc["pid"]["maxTemp"].isNull() && doc["pid"]["maxTemp"].is<unsigned char>()) {
if ( doc["pid"]["maxTemp"].as<unsigned char>() > 0 && doc["pid"]["maxTemp"].as<unsigned char>() <= 100 && doc["pid"]["maxTemp"].as<unsigned char>() > settings.pid.minTemp ) {
settings.pid.maxTemp = doc["pid"]["maxTemp"].as<unsigned char>();
flag = true;
}
}
if (!doc["pid"]["minTemp"].isNull() && doc["pid"]["minTemp"].is<unsigned char>()) {
if ( doc["pid"]["minTemp"].as<unsigned char>() >= 0 && doc["pid"]["minTemp"].as<unsigned char>() < 100 && doc["pid"]["minTemp"].as<unsigned char>() < settings.pid.maxTemp ) {
settings.pid.minTemp = doc["pid"]["minTemp"].as<unsigned char>();
flag = true;
}
} }
// equitherm // equitherm
@@ -178,21 +232,58 @@ protected:
flag = true; flag = true;
} }
if (!doc["equitherm"]["n_factor"].isNull() && (doc["equitherm"]["n_factor"].is<float>() || doc["equitherm"]["n_factor"].is<int>())) { if (!doc["equitherm"]["n_factor"].isNull() && doc["equitherm"]["n_factor"].is<float>()) {
settings.equitherm.n_factor = round(doc["equitherm"]["n_factor"].as<float>() * 1000) / 1000; if ( doc["equitherm"]["n_factor"].as<float>() >= 0 && doc["equitherm"]["n_factor"].as<float>() <= 20 ) {
flag = true; settings.equitherm.n_factor = round(doc["equitherm"]["n_factor"].as<float>() * 1000) / 1000;
flag = true;
}
} }
if (!doc["equitherm"]["k_factor"].isNull() && (doc["equitherm"]["k_factor"].is<float>() || doc["equitherm"]["k_factor"].is<int>())) { if (!doc["equitherm"]["k_factor"].isNull() && doc["equitherm"]["k_factor"].is<float>()) {
settings.equitherm.k_factor = round(doc["equitherm"]["k_factor"].as<float>() * 1000) / 1000; if ( doc["equitherm"]["k_factor"].as<float>() >= 0 && doc["equitherm"]["k_factor"].as<float>() <= 20 ) {
flag = true; settings.equitherm.k_factor = round(doc["equitherm"]["k_factor"].as<float>() * 1000) / 1000;
flag = true;
}
} }
if (!doc["equitherm"]["t_factor"].isNull() && (doc["equitherm"]["t_factor"].is<float>() || doc["equitherm"]["t_factor"].is<int>())) { if (!doc["equitherm"]["t_factor"].isNull() && doc["equitherm"]["t_factor"].is<float>()) {
settings.equitherm.t_factor = round(doc["equitherm"]["t_factor"].as<float>() * 1000) / 1000; if ( doc["equitherm"]["t_factor"].as<float>() >= 0 && doc["equitherm"]["t_factor"].as<float>() <= 20 ) {
flag = true; settings.equitherm.t_factor = round(doc["equitherm"]["t_factor"].as<float>() * 1000) / 1000;
flag = true;
}
} }
// sensors
if (!doc["sensors"]["outdoor"]["type"].isNull() && doc["sensors"]["outdoor"]["type"].is<unsigned char>()) {
if ( doc["sensors"]["outdoor"]["type"].as<unsigned char>() >= 0 && doc["sensors"]["outdoor"]["type"].as<unsigned char>() <= 2 ) {
settings.sensors.outdoor.type = doc["sensors"]["outdoor"]["type"].as<unsigned char>();
flag = true;
}
}
if (!doc["sensors"]["outdoor"]["offset"].isNull() && doc["sensors"]["outdoor"]["offset"].is<float>()) {
if ( doc["sensors"]["outdoor"]["offset"].as<float>() >= -10 && doc["sensors"]["outdoor"]["offset"].as<float>() <= 10 ) {
settings.sensors.outdoor.offset = round(doc["sensors"]["outdoor"]["offset"].as<float>() * 1000) / 1000;
flag = true;
}
}
if (!doc["sensors"]["indoor"]["type"].isNull() && doc["sensors"]["indoor"]["type"].is<unsigned char>()) {
if ( doc["sensors"]["indoor"]["type"].as<unsigned char>() >= 1 && doc["sensors"]["indoor"]["type"].as<unsigned char>() <= 2 ) {
settings.sensors.indoor.type = doc["sensors"]["indoor"]["type"].as<unsigned char>();
flag = true;
}
}
if (!doc["sensors"]["indoor"]["offset"].isNull() && doc["sensors"]["indoor"]["offset"].is<float>()) {
if ( doc["sensors"]["indoor"]["offset"].as<float>() >= -10 && doc["sensors"]["indoor"]["offset"].as<float>() <= 10 ) {
settings.sensors.indoor.offset = round(doc["sensors"]["indoor"]["offset"].as<float>() * 1000) / 1000;
flag = true;
}
}
if (flag) { if (flag) {
eeSettings.update(); eeSettings.update();
publish(true); publish(true);
@@ -215,27 +306,33 @@ protected:
flag = true; flag = true;
} }
if (!doc["tuning"]["regulator"].isNull() && doc["tuning"]["regulator"].is<int>() && doc["tuning"]["regulator"] >= 0 && doc["tuning"]["regulator"] <= 1) { if (!doc["tuning"]["regulator"].isNull() && doc["tuning"]["regulator"].is<unsigned char>()) {
vars.tuning.regulator = doc["tuning"]["regulator"]; if (doc["tuning"]["regulator"].as<unsigned char>() >= 0 && doc["tuning"]["regulator"].as<unsigned char>() <= 1) {
flag = true; vars.tuning.regulator = doc["tuning"]["regulator"].as<unsigned char>();
flag = true;
}
} }
if (!doc["temperatures"]["indoor"].isNull() && (doc["temperatures"]["indoor"].is<float>() || doc["temperatures"]["indoor"].is<int>())) { if (!doc["temperatures"]["indoor"].isNull() && doc["temperatures"]["indoor"].is<float>()) {
vars.temperatures.indoor = round(doc["temperatures"]["indoor"].as<float>() * 100) / 100; if ( settings.sensors.indoor.type == 1 && doc["temperatures"]["indoor"].as<float>() > -100 && doc["temperatures"]["indoor"].as<float>() < 100 ) {
flag = true; vars.temperatures.indoor = round(doc["temperatures"]["indoor"].as<float>() * 100) / 100;
flag = true;
}
} }
if (!doc["temperatures"]["outdoor"].isNull() && (doc["temperatures"]["outdoor"].is<float>() || doc["temperatures"]["outdoor"].is<int>()) && settings.outdoorTempSource == 1) { if (!doc["temperatures"]["outdoor"].isNull() && doc["temperatures"]["outdoor"].is<float>()) {
vars.temperatures.outdoor = round(doc["temperatures"]["outdoor"].as<float>() * 100) / 100; if ( settings.sensors.outdoor.type == 1 && doc["temperatures"]["outdoor"].as<float>() > -100 && doc["temperatures"]["outdoor"].as<float>() < 100 ) {
flag = true; vars.temperatures.outdoor = round(doc["temperatures"]["outdoor"].as<float>() * 100) / 100;
flag = true;
}
} }
if (!doc["restart"].isNull() && doc["restart"].is<bool>() && doc["restart"]) { if (!doc["restart"].isNull() && doc["restart"].is<bool>() && doc["restart"].as<bool>()) {
DEBUG("Received restart message..."); DEBUG("Received restart message...");
eeSettings.updateNow();
Scheduler.delay(10000); Scheduler.delay(10000);
DEBUG("Restart..."); DEBUG("Restart...");
eeSettings.updateNow();
ESP.restart(); ESP.restart();
} }
@@ -275,7 +372,10 @@ protected:
static void publishHaEntities() { static void publishHaEntities() {
// main // main
haHelper.publishSelectOutdoorTempSource(); haHelper.publishSelectOutdoorSensorType();
haHelper.publishSelectIndoorSensorType();
haHelper.publishNumberOutdoorSensorOffset(false);
haHelper.publishNumberIndoorSensorOffset(false);
haHelper.publishSwitchDebug(false); haHelper.publishSwitchDebug(false);
// emergency // emergency
@@ -289,16 +389,18 @@ protected:
//haHelper.publishNumberHeatingTarget(false); //haHelper.publishNumberHeatingTarget(false);
haHelper.publishNumberHeatingHysteresis(); haHelper.publishNumberHeatingHysteresis();
haHelper.publishSensorHeatingSetpoint(false); haHelper.publishSensorHeatingSetpoint(false);
haHelper.publishSensorCurrentHeatingMinTemp(false);
// dhw haHelper.publishSensorCurrentHeatingMaxTemp(false);
haHelper.publishSwitchDHW(false); haHelper.publishNumberHeatingMinTemp(false);
//haHelper.publishNumberDHWTarget(false); haHelper.publishNumberHeatingMaxTemp(false);
// pid // pid
haHelper.publishSwitchPID(); haHelper.publishSwitchPID();
haHelper.publishNumberPIDFactorP(); haHelper.publishNumberPIDFactorP();
haHelper.publishNumberPIDFactorI(); haHelper.publishNumberPIDFactorI();
haHelper.publishNumberPIDFactorD(); haHelper.publishNumberPIDFactorD();
haHelper.publishNumberPIDMinTemp(false);
haHelper.publishNumberPIDMaxTemp(false);
// equitherm // equitherm
haHelper.publishSwitchEquitherm(); haHelper.publishSwitchEquitherm();
@@ -314,12 +416,11 @@ protected:
haHelper.publishBinSensorStatus(); haHelper.publishBinSensorStatus();
haHelper.publishBinSensorOtStatus(); haHelper.publishBinSensorOtStatus();
haHelper.publishBinSensorHeating(); haHelper.publishBinSensorHeating();
haHelper.publishBinSensorDHW();
haHelper.publishBinSensorFlame(); haHelper.publishBinSensorFlame();
haHelper.publishBinSensorFault(); haHelper.publishBinSensorFault();
haHelper.publishBinSensorDiagnostic(); haHelper.publishBinSensorDiagnostic();
haHelper.publishSensorFaultCode(); haHelper.publishSensorFaultCode();
haHelper.publishSensorRssi(); haHelper.publishSensorRssi(false);
// sensors // sensors
haHelper.publishSensorModulation(false); haHelper.publishSensorModulation(false);
@@ -329,22 +430,45 @@ protected:
haHelper.publishNumberIndoorTemp(); haHelper.publishNumberIndoorTemp();
//haHelper.publishNumberOutdoorTemp(); //haHelper.publishNumberOutdoorTemp();
haHelper.publishSensorHeatingTemp(); haHelper.publishSensorHeatingTemp();
haHelper.publishSensorDHWTemp();
} }
static bool publishNonStaticHaEntities(bool force = false) { static bool publishNonStaticHaEntities(bool force = false) {
static byte _heatingMinTemp; static byte _heatingMinTemp, _heatingMaxTemp, _dhwMinTemp, _dhwMaxTemp;
static byte _heatingMaxTemp; static bool _editableOutdoorTemp, _editableIndoorTemp, _dhwPresent;
static byte _dhwMinTemp;
static byte _dhwMaxTemp;
static bool _editableOutdoorTemp;
bool published = false; bool published = false;
bool isStupidMode = !settings.pid.enable && !settings.equitherm.enable; bool isStupidMode = !settings.pid.enable && !settings.equitherm.enable;
byte heatingMinTemp = isStupidMode ? vars.parameters.heatingMinTemp : 10; byte heatingMinTemp = isStupidMode ? vars.parameters.heatingMinTemp : 10;
byte heatingMaxTemp = isStupidMode ? vars.parameters.heatingMaxTemp : 30; byte heatingMaxTemp = isStupidMode ? vars.parameters.heatingMaxTemp : 30;
bool editableOutdoorTemp = settings.outdoorTempSource == 1; bool editableOutdoorTemp = settings.sensors.outdoor.type == 1;
bool editableIndoorTemp = settings.sensors.indoor.type == 1;
if (force || _dhwPresent != settings.opentherm.dhwPresent) {
_dhwPresent = settings.opentherm.dhwPresent;
if (_dhwPresent) {
haHelper.publishSwitchDHW(false);
haHelper.publishSensorCurrentDHWMinTemp(false);
haHelper.publishSensorCurrentDHWMaxTemp(false);
haHelper.publishNumberDHWMinTemp(false);
haHelper.publishNumberDHWMaxTemp(false);
haHelper.publishBinSensorDHW();
haHelper.publishSensorDHWTemp();
} else {
haHelper.deleteSwitchDHW();
haHelper.deleteSensorCurrentDHWMinTemp();
haHelper.deleteSensorCurrentDHWMaxTemp();
haHelper.deleteNumberDHWMinTemp();
haHelper.deleteNumberDHWMaxTemp();
haHelper.deleteBinSensorDHW();
haHelper.deleteSensorDHWTemp();
haHelper.deleteNumberDHWTarget();
haHelper.deleteClimateDHW();
}
published = true;
}
if (force || _heatingMinTemp != heatingMinTemp || _heatingMaxTemp != heatingMaxTemp) { if (force || _heatingMinTemp != heatingMinTemp || _heatingMaxTemp != heatingMaxTemp) {
if (settings.heating.target < heatingMinTemp || settings.heating.target > heatingMaxTemp) { if (settings.heating.target < heatingMinTemp || settings.heating.target > heatingMaxTemp) {
@@ -360,7 +484,7 @@ protected:
published = true; published = true;
} }
if (force || _dhwMinTemp != vars.parameters.dhwMinTemp || _dhwMaxTemp != vars.parameters.dhwMaxTemp) { if (_dhwPresent && (force || _dhwMinTemp != vars.parameters.dhwMinTemp || _dhwMaxTemp != vars.parameters.dhwMaxTemp)) {
_dhwMinTemp = vars.parameters.dhwMinTemp; _dhwMinTemp = vars.parameters.dhwMinTemp;
_dhwMaxTemp = vars.parameters.dhwMaxTemp; _dhwMaxTemp = vars.parameters.dhwMaxTemp;
@@ -384,6 +508,20 @@ protected:
published = true; published = true;
} }
if (force || _editableIndoorTemp != editableIndoorTemp) {
_editableIndoorTemp = editableIndoorTemp;
if (editableIndoorTemp) {
haHelper.deleteSensorIndoorTemp();
haHelper.publishNumberIndoorTemp();
} else {
haHelper.deleteNumberIndoorTemp();
haHelper.publishSensorIndoorTemp();
}
published = true;
}
return published; return published;
} }
@@ -391,7 +529,6 @@ protected:
StaticJsonDocument<2048> doc; StaticJsonDocument<2048> doc;
doc["debug"] = settings.debug; doc["debug"] = settings.debug;
doc["outdoorTempSource"] = settings.outdoorTempSource;
doc["emergency"]["enable"] = settings.emergency.enable; doc["emergency"]["enable"] = settings.emergency.enable;
doc["emergency"]["target"] = settings.emergency.target; doc["emergency"]["target"] = settings.emergency.target;
@@ -401,20 +538,32 @@ protected:
doc["heating"]["turbo"] = settings.heating.turbo; doc["heating"]["turbo"] = settings.heating.turbo;
doc["heating"]["target"] = settings.heating.target; doc["heating"]["target"] = settings.heating.target;
doc["heating"]["hysteresis"] = settings.heating.hysteresis; doc["heating"]["hysteresis"] = settings.heating.hysteresis;
doc["heating"]["minTemp"] = settings.heating.minTemp;
doc["heating"]["maxTemp"] = settings.heating.maxTemp;
doc["dhw"]["enable"] = settings.dhw.enable; doc["dhw"]["enable"] = settings.dhw.enable;
doc["dhw"]["target"] = settings.dhw.target; doc["dhw"]["target"] = settings.dhw.target;
doc["dhw"]["minTemp"] = settings.dhw.minTemp;
doc["dhw"]["maxTemp"] = settings.dhw.maxTemp;
doc["pid"]["enable"] = settings.pid.enable; doc["pid"]["enable"] = settings.pid.enable;
doc["pid"]["p_factor"] = settings.pid.p_factor; doc["pid"]["p_factor"] = settings.pid.p_factor;
doc["pid"]["i_factor"] = settings.pid.i_factor; doc["pid"]["i_factor"] = settings.pid.i_factor;
doc["pid"]["d_factor"] = settings.pid.d_factor; doc["pid"]["d_factor"] = settings.pid.d_factor;
doc["pid"]["minTemp"] = settings.pid.minTemp;
doc["pid"]["maxTemp"] = settings.pid.maxTemp;
doc["equitherm"]["enable"] = settings.equitherm.enable; doc["equitherm"]["enable"] = settings.equitherm.enable;
doc["equitherm"]["n_factor"] = settings.equitherm.n_factor; doc["equitherm"]["n_factor"] = settings.equitherm.n_factor;
doc["equitherm"]["k_factor"] = settings.equitherm.k_factor; doc["equitherm"]["k_factor"] = settings.equitherm.k_factor;
doc["equitherm"]["t_factor"] = settings.equitherm.t_factor; doc["equitherm"]["t_factor"] = settings.equitherm.t_factor;
doc["sensors"]["outdoor"]["type"] = settings.sensors.outdoor.type;
doc["sensors"]["outdoor"]["offset"] = settings.sensors.outdoor.offset;
doc["sensors"]["indoor"]["type"] = settings.sensors.indoor.type;
doc["sensors"]["indoor"]["offset"] = settings.sensors.indoor.offset;
client.beginPublish(topic, measureJson(doc), false); client.beginPublish(topic, measureJson(doc), false);
//BufferingPrint bufferedClient(client, 32); //BufferingPrint bufferedClient(client, 32);
//serializeJson(doc, bufferedClient); //serializeJson(doc, bufferedClient);
@@ -446,6 +595,7 @@ protected:
doc["temperatures"]["heating"] = vars.temperatures.heating; doc["temperatures"]["heating"] = vars.temperatures.heating;
doc["temperatures"]["dhw"] = vars.temperatures.dhw; doc["temperatures"]["dhw"] = vars.temperatures.dhw;
doc["parameters"]["heatingEnabled"] = vars.parameters.heatingEnabled;
doc["parameters"]["heatingMinTemp"] = vars.parameters.heatingMinTemp; doc["parameters"]["heatingMinTemp"] = vars.parameters.heatingMinTemp;
doc["parameters"]["heatingMaxTemp"] = vars.parameters.heatingMaxTemp; doc["parameters"]["heatingMaxTemp"] = vars.parameters.heatingMaxTemp;
doc["parameters"]["heatingSetpoint"] = vars.parameters.heatingSetpoint; doc["parameters"]["heatingSetpoint"] = vars.parameters.heatingSetpoint;

View File

@@ -9,6 +9,11 @@ public:
protected: protected:
void setup() { 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 = new CustomOpenTherm(settings.opentherm.inPin, settings.opentherm.outPin);
ot->begin(handleInterrupt, responseCallback); ot->begin(handleInterrupt, responseCallback);
@@ -31,25 +36,30 @@ protected:
WARN("Slave member id failed"); WARN("Slave member id failed");
} }
bool heatingEnable = (vars.states.emergency || settings.heating.enable) && pump && isReady(); bool heatingEnabled = (vars.states.emergency || settings.heating.enable) && pump && isReady();
localResponse = ot->setBoilerStatus( localResponse = ot->setBoilerStatus(
heatingEnable, heatingEnabled,
settings.dhw.enable, settings.opentherm.dhwPresent && settings.dhw.enable,
false, false, true, false, false false, false, true, false, false
); );
if (!ot->isValidResponse(localResponse)) { if (!ot->isValidResponse(localResponse)) {
WARN_F("Invalid response after setBoilerStatus: %s\r\n", ot->statusToString(ot->getLastResponseStatus())); WARN_F("Invalid response after setBoilerStatus: %s\r\n", ot->statusToString(ot->getLastResponseStatus()));
return; return;
} }
INFO_F("Heating enabled: %d\r\n", heatingEnable); if ( vars.parameters.heatingEnabled != heatingEnabled ) {
setMaxModulationLevel(heatingEnable ? 100 : 0); vars.parameters.heatingEnabled = heatingEnabled;
INFO_F("Heating enabled: %s\r\n", heatingEnabled ? "on\0" : "off\0");
}
vars.states.heating = ot->isCentralHeatingActive(localResponse); vars.states.heating = ot->isCentralHeatingActive(localResponse);
vars.states.dhw = ot->isHotWaterActive(localResponse); vars.states.dhw = settings.opentherm.dhwPresent ? ot->isHotWaterActive(localResponse) : false;
vars.states.flame = ot->isFlameOn(localResponse); vars.states.flame = ot->isFlameOn(localResponse);
vars.states.fault = ot->isFault(localResponse); vars.states.fault = ot->isFault(localResponse);
vars.states.diagnostic = ot->isDiagnostic(localResponse); vars.states.diagnostic = ot->isDiagnostic(localResponse);
setMaxModulationLevel(heatingEnabled ? 100 : 0);
yield(); yield();
// Команды чтения данных котла // Команды чтения данных котла
@@ -60,10 +70,12 @@ protected:
DEBUG_F("Master type: %u, version: %u\r\n", vars.parameters.masterType, vars.parameters.masterVersion); DEBUG_F("Master type: %u, version: %u\r\n", vars.parameters.masterType, vars.parameters.masterVersion);
DEBUG_F("Slave type: %u, version: %u\r\n", vars.parameters.slaveType, vars.parameters.slaveVersion); DEBUG_F("Slave type: %u, version: %u\r\n", vars.parameters.slaveType, vars.parameters.slaveVersion);
updateMinMaxDhwTemp(); if ( settings.opentherm.dhwPresent ) {
updateMinMaxDhwTemp();
}
updateMinMaxHeatingTemp(); updateMinMaxHeatingTemp();
if (settings.outdoorTempSource == 0) { if (settings.sensors.outdoor.type == 0) {
updateOutsideTemp(); updateOutsideTemp();
} }
if (vars.states.fault) { if (vars.states.fault) {
@@ -80,28 +92,28 @@ protected:
} }
updatePressure(); updatePressure();
if ( settings.dhw.enable || settings.heating.enable || heatingEnable ) { if ((settings.opentherm.dhwPresent && settings.dhw.enable) || settings.heating.enable || heatingEnabled ) {
updateModulationLevel(); updateModulationLevel();
} }
yield();
if ( settings.dhw.enable ) { if ( settings.opentherm.dhwPresent && settings.dhw.enable ) {
updateDHWTemp(); updateDHWTemp();
} else { } else {
vars.temperatures.dhw = 0; vars.temperatures.dhw = 0;
} }
if ( settings.heating.enable || heatingEnable ) { //if ( settings.heating.enable || heatingEnabled ) {
updateHeatingTemp(); updateHeatingTemp();
} else { //} else {
vars.temperatures.heating = 0; // vars.temperatures.heating = 0;
} //}
yield(); yield();
// //
// Температура ГВС // Температура ГВС
byte newDHWTemp = settings.dhw.target; byte newDHWTemp = settings.dhw.target;
if (settings.dhw.enable && newDHWTemp != currentDHWTemp) { if (settings.opentherm.dhwPresent && settings.dhw.enable && newDHWTemp != currentDHWTemp) {
if (newDHWTemp < vars.parameters.dhwMinTemp || newDHWTemp > vars.parameters.dhwMaxTemp) { if (newDHWTemp < vars.parameters.dhwMinTemp || newDHWTemp > vars.parameters.dhwMaxTemp) {
newDHWTemp = constrain(newDHWTemp, vars.parameters.dhwMinTemp, vars.parameters.dhwMaxTemp); newDHWTemp = constrain(newDHWTemp, vars.parameters.dhwMinTemp, vars.parameters.dhwMaxTemp);
} }
@@ -119,7 +131,7 @@ protected:
// //
// Температура отопления // Температура отопления
if (heatingEnable && fabs(vars.parameters.heatingSetpoint - currentHeatingTemp) > 0.0001) { if (heatingEnabled && fabs(vars.parameters.heatingSetpoint - currentHeatingTemp) > 0.0001) {
INFO_F("Setting heating temp = %u \n", vars.parameters.heatingSetpoint); INFO_F("Setting heating temp = %u \n", vars.parameters.heatingSetpoint);
// Записываем заданную температуру // Записываем заданную температуру
@@ -219,23 +231,23 @@ protected:
//======================================================================================= //=======================================================================================
unsigned long response = ot->sendRequest(ot->buildRequest(OpenThermRequestType::READ, OpenThermMessageID::SConfigSMemberIDcode, 0)); // 0xFFFF unsigned long response = ot->sendRequest(ot->buildRequest(OpenThermRequestType::READ, OpenThermMessageID::SConfigSMemberIDcode, 0)); // 0xFFFF
/*uint8_t flags = (response & 0xFFFF) >> 8;
DEBUG_F(
"MasterMemberIdCode:\r\n DHW present: %u\r\n Control type: %u\r\n Cooling configuration: %u\r\n DHW configuration: %u\r\n Pump control: %u\r\n CH2 present: %u\r\n Remote water filling function: %u\r\n Heat/cool mode control: %u\r\n Slave MemberID Code: %u\r\n",
flags & 0x01,
flags & 0x02,
flags & 0x04,
flags & 0x08,
flags & 0x10,
flags & 0x20,
flags & 0x40,
flags & 0x80,
response & 0xFF
);*/
if (ot->isValidResponse(response)) { if (ot->isValidResponse(response)) {
vars.parameters.slaveMemberIdCode = response & 0xFF; vars.parameters.slaveMemberIdCode = response & 0xFF;
/*uint8_t flags = (response & 0xFFFF) >> 8;
DEBUG_F(
"MasterMemberIdCode:\r\n DHW present: %u\r\n Control type: %u\r\n Cooling configuration: %u\r\n DHW configuration: %u\r\n Pump control: %u\r\n CH2 present: %u\r\n Remote water filling function: %u\r\n Heat/cool mode control: %u\r\n Slave MemberID Code: %u\r\n",
flags & 0x01,
flags & 0x02,
flags & 0x04,
flags & 0x08,
flags & 0x10,
flags & 0x20,
flags & 0x40,
flags & 0x80,
response & 0xFF
);*/
} else if ( settings.opentherm.memberIdCode <= 0 ) { } else if ( settings.opentherm.memberIdCode <= 0 ) {
return false; return false;
} }
@@ -306,8 +318,8 @@ protected:
byte maxTemp = (response & 0xFFFF) >> 8; byte maxTemp = (response & 0xFFFF) >> 8;
if (minTemp >= 0 && maxTemp > 0 && maxTemp > minTemp) { if (minTemp >= 0 && maxTemp > 0 && maxTemp > minTemp) {
vars.parameters.dhwMinTemp = minTemp; vars.parameters.dhwMinTemp = minTemp < settings.dhw.minTemp ? settings.dhw.minTemp : minTemp;
vars.parameters.dhwMaxTemp = maxTemp; vars.parameters.dhwMaxTemp = maxTemp > settings.dhw.maxTemp ? settings.dhw.maxTemp : maxTemp;
return true; return true;
} }
@@ -325,8 +337,8 @@ protected:
byte maxTemp = (response & 0xFFFF) >> 8; byte maxTemp = (response & 0xFFFF) >> 8;
if (minTemp >= 0 && maxTemp > 0 && maxTemp > minTemp) { if (minTemp >= 0 && maxTemp > 0 && maxTemp > minTemp) {
vars.parameters.heatingMinTemp = minTemp; vars.parameters.heatingMinTemp = minTemp < settings.heating.minTemp ? settings.heating.minTemp : minTemp;
vars.parameters.heatingMaxTemp = maxTemp; vars.parameters.heatingMaxTemp = maxTemp > settings.heating.maxTemp ? settings.heating.maxTemp : maxTemp;
return true; return true;
} }
@@ -340,7 +352,7 @@ protected:
return false; return false;
} }
vars.temperatures.outdoor = ot->getFloat(response); vars.temperatures.outdoor = ot->getFloat(response) + settings.sensors.outdoor.offset;
return true; return true;
} }

View File

@@ -3,7 +3,7 @@
#include <PIDtuner.h> #include <PIDtuner.h>
Equitherm etRegulator; Equitherm etRegulator;
GyverPID pidRegulator(0, 0, 0, 10000); GyverPID pidRegulator(0, 0, 0);
PIDtuner pidTuner; PIDtuner pidTuner;
class RegulatorTask: public LeanTask { class RegulatorTask: public LeanTask {
@@ -73,7 +73,7 @@ protected:
float newTemp = 0; float newTemp = 0;
// if use equitherm // if use equitherm
if (settings.emergency.useEquitherm && settings.outdoorTempSource != 1) { if (settings.emergency.useEquitherm && settings.sensors.outdoor.type != 1) {
float etResult = getEquithermTemp(vars.parameters.heatingMinTemp, vars.parameters.heatingMaxTemp); float etResult = getEquithermTemp(vars.parameters.heatingMinTemp, vars.parameters.heatingMaxTemp);
if (fabs(prevEtResult - etResult) + 0.0001 >= 0.5) { if (fabs(prevEtResult - etResult) + 0.0001 >= 0.5) {
@@ -123,21 +123,24 @@ protected:
} }
// if use pid // if use pid
if (settings.pid.enable) { if (settings.pid.enable && vars.parameters.heatingEnabled) {
float pidResult = getPidTemp( float pidResult = getPidTemp(
settings.equitherm.enable ? -30 : vars.parameters.heatingMinTemp, settings.equitherm.enable ? (settings.pid.maxTemp * -1) : settings.pid.minTemp,
settings.equitherm.enable ? 30 : vars.parameters.heatingMaxTemp settings.equitherm.enable ? settings.pid.maxTemp : settings.pid.maxTemp
); );
if (fabs(prevPidResult - pidResult) + 0.0001 >= 0.5) { if (fabs(prevPidResult - pidResult) + 0.0001 >= 0.5) {
prevPidResult = pidResult; prevPidResult = pidResult;
newTemp += pidResult; newTemp += pidResult;
INFO_F("[REGULATOR][PID] New result: %u (%f) \n", (int)round(pidResult), pidResult); INFO_F("[REGULATOR][PID] New result: %d (%f) \n", (int)round(pidResult), pidResult);
} else { } else {
newTemp += prevPidResult; newTemp += prevPidResult;
} }
} else if ( settings.pid.enable && !vars.parameters.heatingEnabled && prevPidResult != 0 ) {
newTemp += prevPidResult;
} }
// default temp, manual mode // default temp, manual mode
@@ -145,7 +148,9 @@ protected:
newTemp = settings.heating.target; newTemp = settings.heating.target;
} }
return round(newTemp); newTemp = round(newTemp);
newTemp = constrain(newTemp, 0, 100);
return newTemp;
} }
byte getTuningModeTemp() { byte getTuningModeTemp() {
@@ -269,7 +274,7 @@ protected:
pidRegulator.input = vars.temperatures.indoor; pidRegulator.input = vars.temperatures.indoor;
pidRegulator.setpoint = settings.heating.target; pidRegulator.setpoint = settings.heating.target;
return pidRegulator.getResultTimer(); return pidRegulator.getResultNow();
} }
float tuneEquithermN(float ratio, float currentTemp, float setTemp, unsigned int dirtyInterval = 60, unsigned int accurateInterval = 1800, float accurateStep = 0.01, float accurateStepAfter = 1) { float tuneEquithermN(float ratio, float currentTemp, float setTemp, unsigned int dirtyInterval = 60, unsigned int accurateInterval = 1800, float accurateStep = 0.01, float accurateStepAfter = 1) {

View File

@@ -1,45 +1,149 @@
#include <microDS18B20.h> #include <OneWire.h>
#include <DallasTemperature.h>
MicroDS18B20<DS18B20_PIN> outdoorSensor;
class SensorsTask: public LeanTask { class SensorsTask: public LeanTask {
public: public:
SensorsTask(bool _enabled = false, unsigned long _interval = 0): LeanTask(_enabled, _interval) {} SensorsTask(bool _enabled = false, unsigned long _interval = 0): LeanTask(_enabled, _interval) {}
protected: protected:
OneWire* oneWireOutdoorSensor;
OneWire* oneWireIndoorSensor;
DallasTemperature* outdoorSensor;
DallasTemperature* indoorSensor;
bool initOutdoorSensor = false;
unsigned long startConversionTime = 0;
float filteredOutdoorTemp = 0; float filteredOutdoorTemp = 0;
bool emptyOutdoorTemp = true; bool emptyOutdoorTemp = true;
bool initIndoorSensor = false;
float filteredIndoorTemp = 0;
bool emptyIndoorTemp = true;
void setup() {} void setup() {}
void loop() { void loop() {
// DS18B20 sensor if ( settings.sensors.outdoor.type == 2 ) {
if (outdoorSensor.online()) { outdoorTemperatureSensor();
if (outdoorSensor.readTemp()) { }
float rawTemp = outdoorSensor.getTemp();
DEBUG_F("[SENSORS][DS18B20] Raw temp: %f \n", rawTemp);
if (emptyOutdoorTemp) { if ( settings.sensors.indoor.type == 2 ) {
filteredOutdoorTemp = rawTemp; indoorTemperatureSensor();
emptyOutdoorTemp = false;
} else {
filteredOutdoorTemp += (rawTemp - filteredOutdoorTemp) * OUTDOOR_SENSOR_FILTER_K;
}
filteredOutdoorTemp = floor(filteredOutdoorTemp * 100) / 100;
if (fabs(vars.temperatures.outdoor - filteredOutdoorTemp) > 0.099) {
vars.temperatures.outdoor = filteredOutdoorTemp;
INFO_F("[SENSORS][DS18B20] New temp: %f \n", filteredOutdoorTemp);
}
} else {
ERROR("[SENSORS][DS18B20] Invalid data from sensor");
}
outdoorSensor.requestTemp();
} else {
ERROR("[SENSORS][DS18B20] Failed to connect to sensor");
} }
} }
void outdoorTemperatureSensor() {
if ( !initOutdoorSensor ) {
oneWireOutdoorSensor = new OneWire(settings.sensors.outdoor.pin);
outdoorSensor = new DallasTemperature(oneWireOutdoorSensor);
outdoorSensor->begin();
outdoorSensor->setResolution(12);
outdoorSensor->setWaitForConversion(false);
outdoorSensor->requestTemperatures();
startConversionTime = millis();
initOutdoorSensor = true;
}
unsigned long estimateConversionTime = millis() - startConversionTime;
if ( estimateConversionTime < outdoorSensor->millisToWaitForConversion() ) {
return;
}
bool completed = outdoorSensor->isConversionComplete();
if ( !completed && estimateConversionTime >= 1000 ) {
// fail, retry
outdoorSensor->requestTemperatures();
startConversionTime = millis();
ERROR("[SENSORS][OUTDOOR] Could not read temperature data (no response)");
}
if ( !completed ) {
return;
}
float rawTemp = outdoorSensor->getTempCByIndex(0);
if (rawTemp == DEVICE_DISCONNECTED_C) {
ERROR("[SENSORS][OUTDOOR] Could not read temperature data (not connected)");
} else {
DEBUG_F("[SENSORS][OUTDOOR] Raw temp: %f \n", rawTemp);
if (emptyOutdoorTemp) {
filteredOutdoorTemp = rawTemp;
emptyOutdoorTemp = false;
} else {
filteredOutdoorTemp += (rawTemp - filteredOutdoorTemp) * EXT_SENSORS_FILTER_K;
}
filteredOutdoorTemp = floor(filteredOutdoorTemp * 100) / 100;
if (fabs(vars.temperatures.outdoor - filteredOutdoorTemp) > 0.099) {
vars.temperatures.outdoor = filteredOutdoorTemp + settings.sensors.outdoor.offset;
INFO_F("[SENSORS][OUTDOOR] New temp: %f \n", filteredOutdoorTemp);
}
}
outdoorSensor->requestTemperatures();
startConversionTime = millis();
}
void indoorTemperatureSensor() {
if ( !initIndoorSensor ) {
oneWireIndoorSensor = new OneWire(settings.sensors.indoor.pin);
indoorSensor = new DallasTemperature(oneWireIndoorSensor);
indoorSensor->begin();
indoorSensor->setResolution(12);
indoorSensor->setWaitForConversion(false);
indoorSensor->requestTemperatures();
startConversionTime = millis();
initIndoorSensor = true;
}
unsigned long estimateConversionTime = millis() - startConversionTime;
if ( estimateConversionTime < indoorSensor->millisToWaitForConversion() ) {
return;
}
bool completed = indoorSensor->isConversionComplete();
if ( !completed && estimateConversionTime >= 1000 ) {
// fail, retry
indoorSensor->requestTemperatures();
startConversionTime = millis();
ERROR("[SENSORS][INDOOR] Could not read temperature data (no response)");
}
if ( !completed ) {
return;
}
float rawTemp = indoorSensor->getTempCByIndex(0);
if (rawTemp == DEVICE_DISCONNECTED_C) {
ERROR("[SENSORS][INDOOR] Could not read temperature data (not connected)");
} else {
DEBUG_F("[SENSORS][INDOOR] Raw temp: %f \n", rawTemp);
if (emptyIndoorTemp) {
filteredIndoorTemp = rawTemp;
emptyIndoorTemp = false;
} else {
filteredIndoorTemp += (rawTemp - filteredIndoorTemp) * EXT_SENSORS_FILTER_K;
}
filteredIndoorTemp = floor(filteredIndoorTemp * 100) / 100;
if (fabs(vars.temperatures.indoor - filteredIndoorTemp) > 0.099) {
vars.temperatures.indoor = filteredIndoorTemp + settings.sensors.indoor.offset;
INFO_F("[SENSORS][INDOOR] New temp: %f \n", filteredIndoorTemp);
}
}
indoorSensor->requestTemperatures();
startConversionTime = millis();
}
}; };

View File

@@ -1,13 +1,12 @@
struct Settings { struct Settings {
bool debug = false; bool debug = false;
// 0 - boiler, 1 - manual, 2 - ds18b20
byte outdoorTempSource = 0;
char hostname[80] = "opentherm"; char hostname[80] = "opentherm";
struct { struct {
byte inPin = 5; byte inPin = 4;
byte outPin = 4; byte outPin = 5;
unsigned int memberIdCode = 0; unsigned int memberIdCode = 0;
bool dhwPresent = true;
} opentherm; } opentherm;
struct { struct {
@@ -30,11 +29,15 @@ struct Settings {
bool turbo = false; bool turbo = false;
float target = 40.0f; float target = 40.0f;
float hysteresis = 0.5f; float hysteresis = 0.5f;
byte minTemp = 20.0f;
byte maxTemp = 90.0f;
} heating; } heating;
struct { struct {
bool enable = true; bool enable = true;
byte target = 40; byte target = 40;
byte minTemp = 30.0f;
byte maxTemp = 60.0f;
} dhw; } dhw;
struct { struct {
@@ -42,6 +45,8 @@ struct Settings {
float p_factor = 3; float p_factor = 3;
float i_factor = 0.2f; float i_factor = 0.2f;
float d_factor = 0; float d_factor = 0;
byte minTemp = 0.0f;
byte maxTemp = 90.0f;
} pid; } pid;
struct { struct {
@@ -51,6 +56,23 @@ struct Settings {
float t_factor = 2.0f; float t_factor = 2.0f;
} equitherm; } equitherm;
struct {
struct {
// 0 - boiler, 1 - manual, 2 - ds18b20
byte type = 0;
byte pin = 12;
float offset = 0.0f;
} outdoor;
struct {
// 1 - manual, 2 - ds18b20
byte type = 1;
byte pin = 14;
float offset = 0.0f;
} indoor;
} sensors;
char validationValue[8] = SETTINGS_VALID_VALUE;
} settings; } settings;
struct Variables { struct Variables {
@@ -84,11 +106,12 @@ struct Variables {
} temperatures; } temperatures;
struct { struct {
byte heatingMinTemp = 20; bool heatingEnabled = false;
byte heatingMaxTemp = 90; byte heatingMinTemp = 0;
byte heatingMaxTemp = 0;
byte heatingSetpoint = 0.0f; byte heatingSetpoint = 0.0f;
byte dhwMinTemp = 30; byte dhwMinTemp = 0;
byte dhwMaxTemp = 60; byte dhwMaxTemp = 0;
uint8_t slaveMemberIdCode; uint8_t slaveMemberIdCode;
uint8_t slaveType; uint8_t slaveType;
uint8_t slaveVersion; uint8_t slaveVersion;

View File

@@ -1,20 +1,28 @@
#include <WiFiManager.h> #include <WiFiManager.h>
#include <WiFiManagerParameters.h>
// Wifimanager // Wifimanager
WiFiManager wm; WiFiManager wm;
WiFiManagerParameter* wmHostname; WiFiManagerParameter* wmHostname;
WiFiManagerParameter* wmOtInPin;
WiFiManagerParameter* wmOtOutPin;
WiFiManagerParameter* wmOtMemberIdCode;
WiFiManagerParameter* wmMqttServer; WiFiManagerParameter* wmMqttServer;
WiFiManagerParameter* wmMqttPort; IntParameter* wmMqttPort;
WiFiManagerParameter* wmMqttUser; WiFiManagerParameter* wmMqttUser;
WiFiManagerParameter* wmMqttPassword; WiFiManagerParameter* wmMqttPassword;
WiFiManagerParameter* wmMqttPrefix; WiFiManagerParameter* wmMqttPrefix;
IntParameter* wmMqttPublishInterval;
IntParameter* wmOtInPin;
IntParameter* wmOtOutPin;
IntParameter* wmOtMemberIdCode;
CheckboxParameter* wmOtDHWPresent;
IntParameter* wmOutdoorSensorPin;
IntParameter* wmIndoorSensorPin;
class WifiManagerTask: public Task { SeparatorParameter* wmSep1;
SeparatorParameter* wmSep2;
class WifiManagerTask : public Task {
public: public:
WifiManagerTask(bool _enabled = false, unsigned long _interval = 0): Task(_enabled, _interval) {} WifiManagerTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {}
protected: protected:
void setup() { void setup() {
@@ -24,34 +32,48 @@ protected:
wmHostname = new WiFiManagerParameter("hostname", "Hostname", settings.hostname, 80); wmHostname = new WiFiManagerParameter("hostname", "Hostname", settings.hostname, 80);
wm.addParameter(wmHostname); wm.addParameter(wmHostname);
sprintf(buffer, "%d", settings.opentherm.inPin);
wmOtInPin = new WiFiManagerParameter("ot_in_pin", "Opentherm pin IN", buffer, 1);
wm.addParameter(wmOtInPin);
sprintf(buffer, "%d", settings.opentherm.outPin);
wmOtOutPin = new WiFiManagerParameter("ot_out_pin", "Opentherm pin OUT", buffer, 1);
wm.addParameter(wmOtOutPin);
sprintf(buffer, "%d", settings.opentherm.memberIdCode);
wmOtMemberIdCode = new WiFiManagerParameter("ot_member_id_code", "Opentherm member id code", buffer, 5);
wm.addParameter(wmOtMemberIdCode);
wmMqttServer = new WiFiManagerParameter("mqtt_server", "MQTT server", settings.mqtt.server, 80); wmMqttServer = new WiFiManagerParameter("mqtt_server", "MQTT server", settings.mqtt.server, 80);
wm.addParameter(wmMqttServer); wm.addParameter(wmMqttServer);
sprintf(buffer, "%d", settings.mqtt.port); wmMqttPort = new IntParameter("mqtt_port", "MQTT port", settings.mqtt.port, 6);
wmMqttPort = new WiFiManagerParameter("mqtt_port", "MQTT port", buffer, 6);
wm.addParameter(wmMqttPort); wm.addParameter(wmMqttPort);
wmMqttUser = new WiFiManagerParameter("mqtt_user", "MQTT username", settings.mqtt.user, 32); wmMqttUser = new WiFiManagerParameter("mqtt_user", "MQTT username", settings.mqtt.user, 32);
wm.addParameter(wmMqttUser); wm.addParameter(wmMqttUser);
wmMqttPassword = new WiFiManagerParameter("mqtt_password", "MQTT password", settings.mqtt.password, 32); wmMqttPassword = new WiFiManagerParameter("mqtt_password", "MQTT password", settings.mqtt.password, 32, "type=\"password\"");
wm.addParameter(wmMqttPassword); wm.addParameter(wmMqttPassword);
wmMqttPrefix = new WiFiManagerParameter("mqtt_prefix", "MQTT prefix", settings.mqtt.prefix, 32); wmMqttPrefix = new WiFiManagerParameter("mqtt_prefix", "MQTT prefix", settings.mqtt.prefix, 32);
wm.addParameter(wmMqttPrefix); wm.addParameter(wmMqttPrefix);
wmMqttPublishInterval = new IntParameter("mqtt_publish_interval", "MQTT publish interval", settings.mqtt.interval, 5);
wm.addParameter(wmMqttPublishInterval);
wmSep1 = new SeparatorParameter();
wm.addParameter(wmSep1);
wmOtInPin = new IntParameter("ot_in_pin", "Opentherm pin IN", settings.opentherm.inPin, 2);
wm.addParameter(wmOtInPin);
wmOtOutPin = new IntParameter("ot_out_pin", "Opentherm pin OUT", settings.opentherm.outPin, 2);
wm.addParameter(wmOtOutPin);
wmOtMemberIdCode = new IntParameter("ot_member_id_code", "Opentherm member id", settings.opentherm.memberIdCode, 5);
wm.addParameter(wmOtMemberIdCode);
wmOtDHWPresent = new CheckboxParameter("ot_dhw_present", "Opentherm DHW present", settings.opentherm.dhwPresent);
wm.addParameter(wmOtDHWPresent);
wmSep2 = new SeparatorParameter();
wm.addParameter(wmSep2);
wmOutdoorSensorPin = new IntParameter("outdoor_sensor_pin", "Outdoor sensor pin", settings.sensors.outdoor.pin, 2);
wm.addParameter(wmOutdoorSensorPin);
wmIndoorSensorPin = new IntParameter("indoor_sensor_pin", "Indoor sensor pin", settings.sensors.indoor.pin, 2);
wm.addParameter(wmIndoorSensorPin);
//wm.setCleanConnect(true); //wm.setCleanConnect(true);
wm.setRestorePersistent(false); wm.setRestorePersistent(false);
@@ -94,32 +116,46 @@ protected:
void static saveParamsCallback() { void static saveParamsCallback() {
strcpy(settings.hostname, wmHostname->getValue()); strcpy(settings.hostname, wmHostname->getValue());
settings.opentherm.inPin = atoi(wmOtInPin->getValue());
settings.opentherm.outPin = atoi(wmOtOutPin->getValue());
settings.opentherm.memberIdCode = atoi(wmOtMemberIdCode->getValue());
strcpy(settings.mqtt.server, wmMqttServer->getValue()); strcpy(settings.mqtt.server, wmMqttServer->getValue());
settings.mqtt.port = atoi(wmMqttPort->getValue()); settings.mqtt.port = wmMqttPort->getValue();
strcpy(settings.mqtt.user, wmMqttUser->getValue()); strcpy(settings.mqtt.user, wmMqttUser->getValue());
strcpy(settings.mqtt.password, wmMqttPassword->getValue()); strcpy(settings.mqtt.password, wmMqttPassword->getValue());
strcpy(settings.mqtt.prefix, wmMqttPrefix->getValue()); strcpy(settings.mqtt.prefix, wmMqttPrefix->getValue());
settings.mqtt.interval = wmMqttPublishInterval->getValue();
settings.opentherm.inPin = wmOtInPin->getValue();
settings.opentherm.outPin = wmOtOutPin->getValue();
settings.opentherm.memberIdCode = wmOtMemberIdCode->getValue();
settings.opentherm.dhwPresent = wmOtDHWPresent->getCheckboxValue();
settings.sensors.outdoor.pin = wmOutdoorSensorPin->getValue();
settings.sensors.indoor.pin = wmIndoorSensorPin->getValue();
INFO_F( INFO_F(
"New settings:\r\n" "New settings:\r\n"
" Hostname: %s\r\n" " Hostname: %s\r\n"
" OT in pin: %d"
" OT out pin: %d"
" OT member id code: %d"
" Mqtt server: %s:%d\r\n" " Mqtt server: %s:%d\r\n"
" Mqtt user: %s\r\n" " Mqtt user: %s\r\n"
" Mqtt pass: %s\r\n", " Mqtt pass: %s\r\n"
" Mqtt prefix: %s\r\n"
" Mqtt publish interval: %d\r\n"
" OT in pin: %d\r\n"
" OT out pin: %d\r\n"
" OT member id code: %d\r\n"
" OT DHW present: %d\r\n"
" Outdoor sensor pin: %d\r\n"
" Indoor sensor pin: %d\r\n",
settings.hostname, settings.hostname,
settings.opentherm.inPin,
settings.opentherm.outPin,
settings.opentherm.memberIdCode,
settings.mqtt.server, settings.mqtt.server,
settings.mqtt.port, settings.mqtt.port,
settings.mqtt.user, settings.mqtt.user,
settings.mqtt.password settings.mqtt.password,
settings.mqtt.prefix,
settings.mqtt.interval,
settings.opentherm.inPin,
settings.opentherm.outPin,
settings.opentherm.memberIdCode,
settings.opentherm.dhwPresent,
settings.sensors.outdoor.pin,
settings.sensors.indoor.pin
); );
eeSettings.updateNow(); eeSettings.updateNow();
INFO(F("Settings saved")); INFO(F("Settings saved"));

View File

@@ -1,4 +1,4 @@
#define OT_GATEWAY_VERSION "1.2.1" #define OT_GATEWAY_VERSION "1.3.1"
#define AP_SSID "OpenTherm Gateway" #define AP_SSID "OpenTherm Gateway"
#define AP_PASSWORD "otgateway123456" #define AP_PASSWORD "otgateway123456"
#define USE_TELNET #define USE_TELNET
@@ -9,9 +9,8 @@
#define OPENTHERM_OFFLINE_TRESHOLD 10 #define OPENTHERM_OFFLINE_TRESHOLD 10
#define DS18B20_PIN 2 #define EXT_SENSORS_INTERVAL 5000
#define DS18B20_INTERVAL 5000 #define EXT_SENSORS_FILTER_K 0.15
#define OUTDOOR_SENSOR_FILTER_K 0.15
#define DS_CHECK_CRC true #define DS_CHECK_CRC true
#define DS_CRC_USE_TABLE true #define DS_CRC_USE_TABLE true
@@ -19,6 +18,7 @@
#define LED_OT_RX_PIN 15 #define LED_OT_RX_PIN 15
#define CONFIG_URL "http://%s/" #define CONFIG_URL "http://%s/"
#define SETTINGS_VALID_VALUE "stvalid" // only 8 chars!
#ifdef USE_TELNET #ifdef USE_TELNET

View File

@@ -29,7 +29,7 @@ MainTask* tMain;
void setup() { void setup() {
#ifdef USE_TELNET #ifdef USE_TELNET
TelnetStream.begin(); TelnetStream.begin();
delay(5000); delay(1000);
#else #else
Serial.begin(115200); Serial.begin(115200);
Serial.println("\n\n"); Serial.println("\n\n");
@@ -40,6 +40,13 @@ void setup() {
if (eeSettingsResult == 0) { if (eeSettingsResult == 0) {
INFO("Settings loaded"); INFO("Settings loaded");
if ( strcmp(SETTINGS_VALID_VALUE, settings.validationValue) != 0 ) {
INFO("Settings not valid, reset and restart...");
eeSettings.reset();
delay(1000);
ESP.restart();
}
} else if (eeSettingsResult == 1) { } else if (eeSettingsResult == 1) {
INFO("Settings NOT loaded, first start"); INFO("Settings NOT loaded, first start");
@@ -56,7 +63,7 @@ void setup() {
tOt = new OpenThermTask(false); tOt = new OpenThermTask(false);
Scheduler.start(tOt); Scheduler.start(tOt);
tSensors = new SensorsTask(false, DS18B20_INTERVAL); tSensors = new SensorsTask(true, EXT_SENSORS_INTERVAL);
Scheduler.start(tSensors); Scheduler.start(tSensors);
tRegulator = new RegulatorTask(true, 10000); tRegulator = new RegulatorTask(true, 10000);