diff --git a/README.md b/README.md index db38ff1..d2183cc 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ To save money, 2 levels are ordered as one board. After manufacturing, the board - [TelnetStream](https://github.com/jandrassy/TelnetStream) - [EEManager](https://github.com/GyverLibs/EEManager) - [GyverPID](https://github.com/GyverLibs/GyverPID) -- [microDS18B20](https://github.com/GyverLibs/microDS18B20) +- [DallasTemperature](https://github.com/milesburton/Arduino-Temperature-Control-Library) - [WiFiManager](https://github.com/tzapu/WiFiManager) diff --git a/platformio.ini b/platformio.ini index 7a46eef..848ea4f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -21,7 +21,7 @@ lib_deps = jandrassy/TelnetStream@^1.2.4 gyverlibs/EEManager@^2.0 gyverlibs/GyverPID@^3.3 - gyverlibs/microDS18B20@^3.10 + milesburton/DallasTemperature@^3.11.0 https://github.com/tzapu/WiFiManager.git#v2.0.16-rc.2 build_flags = -D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH upload_speed = 921600 diff --git a/src/HomeAssistantHelper.h b/src/HomeAssistantHelper.h index a3d6263..d843a29 100644 --- a/src/HomeAssistantHelper.h +++ b/src/HomeAssistantHelper.h @@ -27,10 +27,10 @@ public: _deviceConfigUrl = value; } - bool publishSelectOutdoorTempSource(bool enabledByDefault = true) { + bool publishSelectOutdoorSensorType(bool enabledByDefault = true) { StaticJsonDocument<1536> doc; 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("sw_version")] = _deviceVersion; doc[F("device")][F("manufacturer")] = _deviceManufacturer; @@ -40,17 +40,17 @@ public: doc[F("device")][F("configuration_url")] = _deviceConfigUrl; } doc[F("enabled_by_default")] = enabledByDefault; - doc[F("unique_id")] = _prefix + F("_outdoorTempSource"); - doc[F("object_id")] = _prefix + F("_outdoorTempSource"); + doc[F("unique_id")] = _prefix + F("_outdoor_sensor_type"); + doc[F("object_id")] = _prefix + F("_outdoor_sensor_type"); doc[F("entity_category")] = F("config"); doc[F("name")] = F("Outdoor temperature source"); 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")][1] = F("Manual"); 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); //serializeJson(doc, bufferedClient); //bufferedClient.flush(); @@ -58,6 +58,105 @@ public: 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) { StaticJsonDocument<1536> doc; doc[F("device")][F("identifiers")][0] = _prefix; @@ -148,6 +247,7 @@ public: doc[F("min")] = 5; doc[F("max")] = 50; doc[F("step")] = 0.5; + doc[F("mode")] = "box"; client.beginPublish((F("homeassistant/number/") + _prefix + F("/emergency_target/config")).c_str(), measureJson(doc), true); //BufferingPrint bufferedClient(client, 32); @@ -160,7 +260,7 @@ public: bool publishSwitchEmergencyUseEquitherm(bool enabledByDefault = true) { StaticJsonDocument<1536> doc; 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("sw_version")] = _deviceVersion; doc[F("device")][F("manufacturer")] = _deviceManufacturer; @@ -282,8 +382,9 @@ public: doc[F("command_topic")] = _prefix + F("/settings/set"); doc[F("command_template")] = F("{\"heating\": {\"target\" : {{ value }}}}"); doc[F("min")] = minTemp; - doc[F("max")] = maxTemp; + doc[F("max")] = maxTemp <= minTemp ? maxTemp : maxTemp; doc[F("step")] = 0.5; + doc[F("mode")] = "box"; client.beginPublish((F("homeassistant/number/") + _prefix + F("/heating_target/config")).c_str(), measureJson(doc), true); //BufferingPrint bufferedClient(client, 32); @@ -318,6 +419,7 @@ public: doc[F("min")] = 0; doc[F("max")] = 5; doc[F("step")] = 0.1; + doc[F("mode")] = "box"; client.beginPublish((F("homeassistant/number/") + _prefix + F("/heating_hysteresis/config")).c_str(), measureJson(doc), true); //BufferingPrint bufferedClient(client, 32); @@ -358,6 +460,126 @@ public: 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) { StaticJsonDocument<1536> doc; @@ -416,8 +638,9 @@ public: doc[F("command_topic")] = _prefix + F("/settings/set"); doc[F("command_template")] = F("{\"dhw\": {\"target\" : {{ value|int(0) }}}}"); doc[F("min")] = minTemp; - doc[F("max")] = maxTemp; + doc[F("max")] = maxTemp <= minTemp ? maxTemp : maxTemp; doc[F("step")] = 1; + doc[F("mode")] = "box"; client.beginPublish((F("homeassistant/number/") + _prefix + F("/dhw_target/config")).c_str(), measureJson(doc), true); //BufferingPrint bufferedClient(client, 32); @@ -427,6 +650,126 @@ public: 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) { StaticJsonDocument<1536> doc; @@ -482,6 +825,7 @@ public: doc[F("min")] = 0.001; doc[F("max")] = 10; 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); //BufferingPrint bufferedClient(client, 32); @@ -513,6 +857,7 @@ public: doc[F("min")] = 0; doc[F("max")] = 10; 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); //BufferingPrint bufferedClient(client, 32); @@ -544,6 +889,7 @@ public: doc[F("min")] = 0; doc[F("max")] = 10; 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); //BufferingPrint bufferedClient(client, 32); @@ -553,6 +899,70 @@ public: 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) { StaticJsonDocument<1536> doc; @@ -608,6 +1018,7 @@ public: doc[F("min")] = 0.001; doc[F("max")] = 5; 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); //BufferingPrint bufferedClient(client, 32); @@ -639,6 +1050,7 @@ public: doc[F("min")] = 0; doc[F("max")] = 10; 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); //BufferingPrint bufferedClient(client, 32); @@ -672,6 +1084,7 @@ public: doc[F("min")] = 0; doc[F("max")] = 10; 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); //BufferingPrint bufferedClient(client, 32); @@ -979,7 +1392,7 @@ public: return client.endPublish(); } - bool publishSensorRssi(bool enabledByDefault = false) { + bool publishSensorRssi(bool enabledByDefault = true) { StaticJsonDocument<1536> doc; doc["device"]["identifiers"][0] = _prefix; doc["device"]["sw_version"] = _deviceVersion; @@ -1076,7 +1489,6 @@ public: bool publishNumberIndoorTemp(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; @@ -1097,8 +1509,10 @@ public: doc[F("value_template")] = F("{{ value_json.temperatures.indoor|float(0)|round(1) }}"); doc[F("command_topic")] = _prefix + F("/state/set"); doc[F("command_template")] = F("{\"temperatures\": {\"indoor\":{{ value }}}}"); - doc[F("min")] = -70; - doc[F("max")] = 50; + doc[F("min")] = -99; + 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); //BufferingPrint bufferedClient(client, 32); @@ -1108,6 +1522,38 @@ public: 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) { StaticJsonDocument<1536> doc; 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("command_topic")] = _prefix + F("/state/set"); doc[F("command_template")] = F("{\"temperatures\": {\"outdoor\":{{ value }}}}"); - doc[F("min")] = -70; - doc[F("max")] = 50; + doc[F("min")] = -99; + 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); //BufferingPrint bufferedClient(client, 32); @@ -1144,8 +1592,6 @@ public: bool publishSensorOutdoorTemp(bool enabledByDefault = true) { StaticJsonDocument<1536> doc; 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("device")][F("identifiers")][0] = _prefix; doc[F("device")][F("sw_version")] = _deviceVersion; @@ -1353,6 +1799,14 @@ public: 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); + } + private: String _prefix = "opentherm"; String _deviceVersion = "1.0"; diff --git a/src/MainTask.h b/src/MainTask.h index 26e8fc2..7ea74cc 100644 --- a/src/MainTask.h +++ b/src/MainTask.h @@ -13,7 +13,6 @@ protected: void setup() { pinMode(LED_STATUS_PIN, OUTPUT); - //pinMode(LED_OT_RX_PIN, OUTPUT); } 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) { tOt->enable(); } diff --git a/src/MqttTask.h b/src/MqttTask.h index 3114f3f..5f6d595 100644 --- a/src/MqttTask.h +++ b/src/MqttTask.h @@ -19,7 +19,6 @@ protected: void setup() { DEBUG("[MQTT] Started"); - client.setServer(settings.mqtt.server, settings.mqtt.port); client.setCallback(__callback); haHelper.setPrefix(settings.mqtt.prefix); haHelper.setDeviceVersion(OT_GATEWAY_VERSION); @@ -32,6 +31,7 @@ protected: 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); + client.setServer(settings.mqtt.server, settings.mqtt.port); if (client.connect(settings.hostname, settings.mqtt.user, settings.mqtt.password)) { INFO("Connected to MQTT server"); @@ -93,15 +93,6 @@ protected: flag = true; } - if (!doc["outdoorTempSource"].isNull() && doc["outdoorTempSource"].is() && doc["outdoorTempSource"] >= 0 && doc["outdoorTempSource"] <= 2) { - settings.outdoorTempSource = doc["outdoorTempSource"]; - flag = true; - } - - if (!doc["mqtt"]["interval"].isNull() && doc["mqtt"]["interval"].is() && doc["mqtt"]["interval"] >= 1000 && doc["mqtt"]["interval"] <= 120000) { - settings.mqtt.interval = doc["mqtt"]["interval"].as(); - flag = true; - } // emergency if (!doc["emergency"]["enable"].isNull() && doc["emergency"]["enable"].is()) { @@ -109,9 +100,11 @@ protected: flag = true; } - if (!doc["emergency"]["target"].isNull() && (doc["emergency"]["target"].is() || doc["emergency"]["target"].is())) { - settings.emergency.target = round(doc["emergency"]["target"].as() * 10) / 10; - flag = true; + if (!doc["emergency"]["target"].isNull() && doc["emergency"]["target"].is()) { + if ( doc["emergency"]["target"].as() > 0 && doc["emergency"]["target"].as() < 100 ) { + settings.emergency.target = round(doc["emergency"]["target"].as() * 10) / 10; + flag = true; + } } if (!doc["emergency"]["useEquitherm"].isNull() && doc["emergency"]["useEquitherm"].is()) { @@ -119,6 +112,7 @@ protected: flag = true; } + // heating if (!doc["heating"]["enable"].isNull() && doc["heating"]["enable"].is()) { settings.heating.enable = doc["heating"]["enable"].as(); @@ -130,46 +124,106 @@ protected: flag = true; } - if (!doc["heating"]["target"].isNull() && (doc["heating"]["target"].is() || doc["heating"]["target"].is())) { - settings.heating.target = round(doc["heating"]["target"].as() * 10) / 10; - flag = true; + if (!doc["heating"]["target"].isNull() && doc["heating"]["target"].is()) { + if ( doc["heating"]["target"].as() > 0 && doc["heating"]["target"].as() < 100 ) { + settings.heating.target = round(doc["heating"]["target"].as() * 10) / 10; + flag = true; + } } - if (!doc["heating"]["hysteresis"].isNull() && (doc["heating"]["hysteresis"].is() || doc["heating"]["hysteresis"].is())) { - settings.heating.hysteresis = round(doc["heating"]["hysteresis"].as() * 10) / 10; - flag = true; + if (!doc["heating"]["hysteresis"].isNull() && doc["heating"]["hysteresis"].is()) { + if ( doc["heating"]["hysteresis"].as() > 0 && doc["heating"]["hysteresis"].as() <= 5 ) { + settings.heating.hysteresis = round(doc["heating"]["hysteresis"].as() * 10) / 10; + flag = true; + } } + if (!doc["heating"]["maxTemp"].isNull() && doc["heating"]["maxTemp"].is()) { + if ( doc["heating"]["maxTemp"].as() > 0 && doc["heating"]["maxTemp"].as() <= 100 && doc["heating"]["maxTemp"].as() > settings.heating.minTemp ) { + settings.heating.maxTemp = doc["heating"]["maxTemp"].as(); + vars.parameters.heatingMaxTemp = settings.heating.maxTemp; + flag = true; + } + } + + if (!doc["heating"]["minTemp"].isNull() && doc["heating"]["minTemp"].is()) { + if ( doc["heating"]["minTemp"].as() >= 0 && doc["heating"]["minTemp"].as() < 100 && doc["heating"]["minTemp"].as() < settings.heating.maxTemp ) { + settings.heating.minTemp = doc["heating"]["minTemp"].as(); + vars.parameters.heatingMinTemp = settings.heating.minTemp; + flag = true; + } + } + + // dhw if (!doc["dhw"]["enable"].isNull() && doc["dhw"]["enable"].is()) { settings.dhw.enable = doc["dhw"]["enable"].as(); flag = true; } - if (!doc["dhw"]["target"].isNull() && doc["dhw"]["target"].is()) { - settings.dhw.target = doc["dhw"]["target"].as(); - flag = true; + if (!doc["dhw"]["target"].isNull() && doc["dhw"]["target"].is()) { + if ( doc["dhw"]["target"].as() >= 0 && doc["dhw"]["target"].as() < 100 ) { + settings.dhw.target = doc["dhw"]["target"].as(); + flag = true; + } } + if (!doc["dhw"]["maxTemp"].isNull() && doc["dhw"]["maxTemp"].is()) { + if ( doc["dhw"]["maxTemp"].as() > 0 && doc["dhw"]["maxTemp"].as() <= 100 && doc["dhw"]["maxTemp"].as() > settings.dhw.minTemp ) { + settings.dhw.maxTemp = doc["dhw"]["maxTemp"].as(); + vars.parameters.dhwMaxTemp = settings.dhw.maxTemp; + flag = true; + } + } + + if (!doc["dhw"]["minTemp"].isNull() && doc["dhw"]["minTemp"].is()) { + if ( doc["dhw"]["minTemp"].as() >= 0 && doc["dhw"]["minTemp"].as() < 100 && doc["dhw"]["minTemp"].as() < settings.dhw.maxTemp ) { + settings.dhw.minTemp = doc["dhw"]["minTemp"].as(); + vars.parameters.dhwMinTemp = settings.dhw.minTemp; + flag = true; + } + } + + // pid if (!doc["pid"]["enable"].isNull() && doc["pid"]["enable"].is()) { settings.pid.enable = doc["pid"]["enable"].as(); flag = true; } - if (!doc["pid"]["p_factor"].isNull() && (doc["pid"]["p_factor"].is() || doc["pid"]["p_factor"].is())) { - settings.pid.p_factor = round(doc["pid"]["p_factor"].as() * 1000) / 1000; - flag = true; + if (!doc["pid"]["p_factor"].isNull() && doc["pid"]["p_factor"].is()) { + if ( doc["pid"]["p_factor"].as() >= 0 && doc["pid"]["p_factor"].as() <= 20 ) { + settings.pid.p_factor = round(doc["pid"]["p_factor"].as() * 1000) / 1000; + flag = true; + } } - if (!doc["pid"]["i_factor"].isNull() && (doc["pid"]["i_factor"].is() || doc["pid"]["i_factor"].is())) { - settings.pid.i_factor = round(doc["pid"]["i_factor"].as() * 1000) / 1000; - flag = true; + if (!doc["pid"]["i_factor"].isNull() && doc["pid"]["i_factor"].is()) { + if ( doc["pid"]["i_factor"].as() >= 0 && doc["pid"]["i_factor"].as() <= 20 ) { + settings.pid.i_factor = round(doc["pid"]["i_factor"].as() * 1000) / 1000; + flag = true; + } } - if (!doc["pid"]["d_factor"].isNull() && (doc["pid"]["d_factor"].is() || doc["pid"]["d_factor"].is())) { - settings.pid.d_factor = round(doc["pid"]["d_factor"].as() * 1000) / 1000; - flag = true; + if (!doc["pid"]["d_factor"].isNull() && doc["pid"]["d_factor"].is()) { + if ( doc["pid"]["d_factor"].as() >= 0 && doc["pid"]["d_factor"].as() <= 20 ) { + settings.pid.d_factor = round(doc["pid"]["d_factor"].as() * 1000) / 1000; + flag = true; + } + } + + if (!doc["pid"]["maxTemp"].isNull() && doc["pid"]["maxTemp"].is()) { + if ( doc["pid"]["maxTemp"].as() > 0 && doc["pid"]["maxTemp"].as() <= 100 && doc["pid"]["maxTemp"].as() > settings.pid.minTemp ) { + settings.pid.maxTemp = doc["pid"]["maxTemp"].as(); + flag = true; + } + } + + if (!doc["pid"]["minTemp"].isNull() && doc["pid"]["minTemp"].is()) { + if ( doc["pid"]["minTemp"].as() >= 0 && doc["pid"]["minTemp"].as() < 100 && doc["pid"]["minTemp"].as() < settings.pid.maxTemp ) { + settings.pid.minTemp = doc["pid"]["minTemp"].as(); + flag = true; + } } // equitherm @@ -178,21 +232,58 @@ protected: flag = true; } - if (!doc["equitherm"]["n_factor"].isNull() && (doc["equitherm"]["n_factor"].is() || doc["equitherm"]["n_factor"].is())) { - settings.equitherm.n_factor = round(doc["equitherm"]["n_factor"].as() * 1000) / 1000; - flag = true; + if (!doc["equitherm"]["n_factor"].isNull() && doc["equitherm"]["n_factor"].is()) { + if ( doc["equitherm"]["n_factor"].as() >= 0 && doc["equitherm"]["n_factor"].as() <= 20 ) { + settings.equitherm.n_factor = round(doc["equitherm"]["n_factor"].as() * 1000) / 1000; + flag = true; + } } - if (!doc["equitherm"]["k_factor"].isNull() && (doc["equitherm"]["k_factor"].is() || doc["equitherm"]["k_factor"].is())) { - settings.equitherm.k_factor = round(doc["equitherm"]["k_factor"].as() * 1000) / 1000; - flag = true; + if (!doc["equitherm"]["k_factor"].isNull() && doc["equitherm"]["k_factor"].is()) { + if ( doc["equitherm"]["k_factor"].as() >= 0 && doc["equitherm"]["k_factor"].as() <= 20 ) { + settings.equitherm.k_factor = round(doc["equitherm"]["k_factor"].as() * 1000) / 1000; + flag = true; + } } - if (!doc["equitherm"]["t_factor"].isNull() && (doc["equitherm"]["t_factor"].is() || doc["equitherm"]["t_factor"].is())) { - settings.equitherm.t_factor = round(doc["equitherm"]["t_factor"].as() * 1000) / 1000; - flag = true; + if (!doc["equitherm"]["t_factor"].isNull() && doc["equitherm"]["t_factor"].is()) { + if ( doc["equitherm"]["t_factor"].as() >= 0 && doc["equitherm"]["t_factor"].as() <= 20 ) { + settings.equitherm.t_factor = round(doc["equitherm"]["t_factor"].as() * 1000) / 1000; + flag = true; + } } + + // sensors + if (!doc["sensors"]["outdoor"]["type"].isNull() && doc["sensors"]["outdoor"]["type"].is()) { + if ( doc["sensors"]["outdoor"]["type"].as() >= 0 && doc["sensors"]["outdoor"]["type"].as() <= 2 ) { + settings.sensors.outdoor.type = doc["sensors"]["outdoor"]["type"].as(); + flag = true; + } + } + + if (!doc["sensors"]["outdoor"]["offset"].isNull() && doc["sensors"]["outdoor"]["offset"].is()) { + if ( doc["sensors"]["outdoor"]["offset"].as() >= -10 && doc["sensors"]["outdoor"]["offset"].as() <= 10 ) { + settings.sensors.outdoor.offset = round(doc["sensors"]["outdoor"]["offset"].as() * 1000) / 1000; + flag = true; + } + } + + if (!doc["sensors"]["indoor"]["type"].isNull() && doc["sensors"]["indoor"]["type"].is()) { + if ( doc["sensors"]["indoor"]["type"].as() >= 1 && doc["sensors"]["indoor"]["type"].as() <= 2 ) { + settings.sensors.indoor.type = doc["sensors"]["indoor"]["type"].as(); + flag = true; + } + } + + if (!doc["sensors"]["indoor"]["offset"].isNull() && doc["sensors"]["indoor"]["offset"].is()) { + if ( doc["sensors"]["indoor"]["offset"].as() >= -10 && doc["sensors"]["indoor"]["offset"].as() <= 10 ) { + settings.sensors.indoor.offset = round(doc["sensors"]["indoor"]["offset"].as() * 1000) / 1000; + flag = true; + } + } + + if (flag) { eeSettings.update(); publish(true); @@ -215,27 +306,33 @@ protected: flag = true; } - if (!doc["tuning"]["regulator"].isNull() && doc["tuning"]["regulator"].is() && doc["tuning"]["regulator"] >= 0 && doc["tuning"]["regulator"] <= 1) { - vars.tuning.regulator = doc["tuning"]["regulator"]; - flag = true; + if (!doc["tuning"]["regulator"].isNull() && doc["tuning"]["regulator"].is()) { + if (doc["tuning"]["regulator"].as() >= 0 && doc["tuning"]["regulator"].as() <= 1) { + vars.tuning.regulator = doc["tuning"]["regulator"].as(); + flag = true; + } } - if (!doc["temperatures"]["indoor"].isNull() && (doc["temperatures"]["indoor"].is() || doc["temperatures"]["indoor"].is())) { - vars.temperatures.indoor = round(doc["temperatures"]["indoor"].as() * 100) / 100; - flag = true; + if (!doc["temperatures"]["indoor"].isNull() && doc["temperatures"]["indoor"].is()) { + if ( settings.sensors.indoor.type == 1 && doc["temperatures"]["indoor"].as() > -100 && doc["temperatures"]["indoor"].as() < 100 ) { + vars.temperatures.indoor = round(doc["temperatures"]["indoor"].as() * 100) / 100; + flag = true; + } } - if (!doc["temperatures"]["outdoor"].isNull() && (doc["temperatures"]["outdoor"].is() || doc["temperatures"]["outdoor"].is()) && settings.outdoorTempSource == 1) { - vars.temperatures.outdoor = round(doc["temperatures"]["outdoor"].as() * 100) / 100; - flag = true; + if (!doc["temperatures"]["outdoor"].isNull() && doc["temperatures"]["outdoor"].is()) { + if ( settings.sensors.outdoor.type == 1 && doc["temperatures"]["outdoor"].as() > -100 && doc["temperatures"]["outdoor"].as() < 100 ) { + vars.temperatures.outdoor = round(doc["temperatures"]["outdoor"].as() * 100) / 100; + flag = true; + } } - if (!doc["restart"].isNull() && doc["restart"].is() && doc["restart"]) { + if (!doc["restart"].isNull() && doc["restart"].is() && doc["restart"].as()) { DEBUG("Received restart message..."); + eeSettings.updateNow(); Scheduler.delay(10000); DEBUG("Restart..."); - eeSettings.updateNow(); ESP.restart(); } @@ -275,7 +372,10 @@ protected: static void publishHaEntities() { // main - haHelper.publishSelectOutdoorTempSource(); + haHelper.publishSelectOutdoorSensorType(); + haHelper.publishSelectIndoorSensorType(); + haHelper.publishNumberOutdoorSensorOffset(false); + haHelper.publishNumberIndoorSensorOffset(false); haHelper.publishSwitchDebug(false); // emergency @@ -289,16 +389,26 @@ protected: //haHelper.publishNumberHeatingTarget(false); haHelper.publishNumberHeatingHysteresis(); haHelper.publishSensorHeatingSetpoint(false); + haHelper.publishSensorCurrentHeatingMinTemp(false); + haHelper.publishSensorCurrentHeatingMaxTemp(false); + haHelper.publishNumberHeatingMinTemp(false); + haHelper.publishNumberHeatingMaxTemp(false); // dhw haHelper.publishSwitchDHW(false); //haHelper.publishNumberDHWTarget(false); + haHelper.publishSensorCurrentDHWMinTemp(false); + haHelper.publishSensorCurrentDHWMaxTemp(false); + haHelper.publishNumberDHWMinTemp(false); + haHelper.publishNumberDHWMaxTemp(false); // pid haHelper.publishSwitchPID(); haHelper.publishNumberPIDFactorP(); haHelper.publishNumberPIDFactorI(); haHelper.publishNumberPIDFactorD(); + haHelper.publishNumberPIDMinTemp(false); + haHelper.publishNumberPIDMaxTemp(false); // equitherm haHelper.publishSwitchEquitherm(); @@ -319,7 +429,7 @@ protected: haHelper.publishBinSensorFault(); haHelper.publishBinSensorDiagnostic(); haHelper.publishSensorFaultCode(); - haHelper.publishSensorRssi(); + haHelper.publishSensorRssi(false); // sensors haHelper.publishSensorModulation(false); @@ -333,18 +443,15 @@ protected: } static bool publishNonStaticHaEntities(bool force = false) { - static byte _heatingMinTemp; - static byte _heatingMaxTemp; - static byte _dhwMinTemp; - static byte _dhwMaxTemp; - static bool _editableOutdoorTemp; + static byte _heatingMinTemp, _heatingMaxTemp, _dhwMinTemp, _dhwMaxTemp; + static bool _editableOutdoorTemp, _editableIndoorTemp; bool published = false; bool isStupidMode = !settings.pid.enable && !settings.equitherm.enable; byte heatingMinTemp = isStupidMode ? vars.parameters.heatingMinTemp : 10; byte heatingMaxTemp = isStupidMode ? vars.parameters.heatingMaxTemp : 30; - bool editableOutdoorTemp = settings.outdoorTempSource == 1; - + bool editableOutdoorTemp = settings.sensors.outdoor.type == 1; + bool editableIndoorTemp = settings.sensors.indoor.type == 1; if (force || _heatingMinTemp != heatingMinTemp || _heatingMaxTemp != heatingMaxTemp) { if (settings.heating.target < heatingMinTemp || settings.heating.target > heatingMaxTemp) { @@ -384,6 +491,20 @@ protected: published = true; } + if (force || _editableIndoorTemp != editableIndoorTemp) { + _editableIndoorTemp = editableIndoorTemp; + + if (editableIndoorTemp) { + haHelper.deleteSensorIndoorTemp(); + haHelper.publishNumberIndoorTemp(); + } else { + haHelper.deleteNumberIndoorTemp(); + haHelper.publishSensorIndoorTemp(); + } + + published = true; + } + return published; } @@ -391,7 +512,6 @@ protected: StaticJsonDocument<2048> doc; doc["debug"] = settings.debug; - doc["outdoorTempSource"] = settings.outdoorTempSource; doc["emergency"]["enable"] = settings.emergency.enable; doc["emergency"]["target"] = settings.emergency.target; @@ -401,20 +521,32 @@ protected: doc["heating"]["turbo"] = settings.heating.turbo; doc["heating"]["target"] = settings.heating.target; 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"]["target"] = settings.dhw.target; + doc["dhw"]["minTemp"] = settings.dhw.minTemp; + doc["dhw"]["maxTemp"] = settings.dhw.maxTemp; doc["pid"]["enable"] = settings.pid.enable; doc["pid"]["p_factor"] = settings.pid.p_factor; doc["pid"]["i_factor"] = settings.pid.i_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"]["n_factor"] = settings.equitherm.n_factor; doc["equitherm"]["k_factor"] = settings.equitherm.k_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); //BufferingPrint bufferedClient(client, 32); //serializeJson(doc, bufferedClient); diff --git a/src/OpenThermTask.h b/src/OpenThermTask.h index 32fa8dc..f7c7eb0 100644 --- a/src/OpenThermTask.h +++ b/src/OpenThermTask.h @@ -9,6 +9,11 @@ public: protected: void setup() { + vars.parameters.heatingMinTemp = settings.heating.minTemp; + vars.parameters.heatingMaxTemp = settings.heating.maxTemp; + vars.parameters.dhwMinTemp = settings.dhw.minTemp; + vars.parameters.dhwMaxTemp = settings.dhw.maxTemp; + ot = new CustomOpenTherm(settings.opentherm.inPin, settings.opentherm.outPin); ot->begin(handleInterrupt, responseCallback); @@ -63,7 +68,7 @@ protected: updateMinMaxDhwTemp(); updateMinMaxHeatingTemp(); - if (settings.outdoorTempSource == 0) { + if (settings.sensors.outdoor.type == 0) { updateOutsideTemp(); } if (vars.states.fault) { @@ -83,6 +88,7 @@ protected: if ( settings.dhw.enable || settings.heating.enable || heatingEnable ) { updateModulationLevel(); } + yield(); if ( settings.dhw.enable ) { updateDHWTemp(); @@ -95,7 +101,6 @@ protected: } else { vars.temperatures.heating = 0; } - yield(); // @@ -306,8 +311,8 @@ protected: byte maxTemp = (response & 0xFFFF) >> 8; if (minTemp >= 0 && maxTemp > 0 && maxTemp > minTemp) { - vars.parameters.dhwMinTemp = minTemp; - vars.parameters.dhwMaxTemp = maxTemp; + vars.parameters.dhwMinTemp = minTemp < settings.dhw.minTemp ? settings.dhw.minTemp : minTemp; + vars.parameters.dhwMaxTemp = maxTemp > settings.dhw.maxTemp ? settings.dhw.maxTemp : maxTemp; return true; } @@ -325,8 +330,8 @@ protected: byte maxTemp = (response & 0xFFFF) >> 8; if (minTemp >= 0 && maxTemp > 0 && maxTemp > minTemp) { - vars.parameters.heatingMinTemp = minTemp; - vars.parameters.heatingMaxTemp = maxTemp; + vars.parameters.heatingMinTemp = minTemp < settings.heating.minTemp ? settings.heating.minTemp : minTemp; + vars.parameters.heatingMaxTemp = maxTemp > settings.heating.maxTemp ? settings.heating.maxTemp : maxTemp; return true; } @@ -340,7 +345,7 @@ protected: return false; } - vars.temperatures.outdoor = ot->getFloat(response); + vars.temperatures.outdoor = ot->getFloat(response) + settings.sensors.outdoor.offset; return true; } diff --git a/src/RegulatorTask.h b/src/RegulatorTask.h index 8095967..2589296 100644 --- a/src/RegulatorTask.h +++ b/src/RegulatorTask.h @@ -3,7 +3,7 @@ #include Equitherm etRegulator; -GyverPID pidRegulator(0, 0, 0, 10000); +GyverPID pidRegulator(0, 0, 0); PIDtuner pidTuner; class RegulatorTask: public LeanTask { @@ -73,7 +73,7 @@ protected: float newTemp = 0; // 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); if (fabs(prevEtResult - etResult) + 0.0001 >= 0.5) { @@ -123,21 +123,24 @@ protected: } // if use pid - if (settings.pid.enable) { + if (settings.pid.enable && vars.states.heating) { float pidResult = getPidTemp( - settings.equitherm.enable ? -30 : vars.parameters.heatingMinTemp, - settings.equitherm.enable ? 30 : vars.parameters.heatingMaxTemp + settings.equitherm.enable ? (settings.pid.maxTemp * -1) : settings.pid.minTemp, + settings.equitherm.enable ? settings.pid.maxTemp : settings.pid.maxTemp ); - if (fabs(prevPidResult - pidResult) + 0.0001 >= 0.5) { + if (1 || fabs(prevPidResult - pidResult) + 0.0001 >= 0.5) { prevPidResult = 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 { newTemp += prevPidResult; } + + } else if ( settings.pid.enable && !vars.states.heating && prevPidResult != 0 ) { + newTemp += prevPidResult; } // default temp, manual mode @@ -145,7 +148,9 @@ protected: newTemp = settings.heating.target; } - return round(newTemp); + newTemp = round(newTemp); + newTemp = constrain(newTemp, 0, 100); + return newTemp; } byte getTuningModeTemp() { @@ -269,7 +274,7 @@ protected: pidRegulator.input = vars.temperatures.indoor; 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) { diff --git a/src/SensorsTask.h b/src/SensorsTask.h index fc565fe..a1ca424 100644 --- a/src/SensorsTask.h +++ b/src/SensorsTask.h @@ -1,45 +1,149 @@ -#include - -MicroDS18B20 outdoorSensor; +#include +#include class SensorsTask: public LeanTask { public: SensorsTask(bool _enabled = false, unsigned long _interval = 0): LeanTask(_enabled, _interval) {} protected: + OneWire* oneWireOutdoorSensor; + OneWire* oneWireIndoorSensor; + + DallasTemperature* outdoorSensor; + DallasTemperature* indoorSensor; + + bool initOutdoorSensor = false; + unsigned long startConversionTime = 0; float filteredOutdoorTemp = 0; bool emptyOutdoorTemp = true; + + bool initIndoorSensor = false; + float filteredIndoorTemp = 0; + bool emptyIndoorTemp = true; + + void setup() {} - void loop() { - // DS18B20 sensor - if (outdoorSensor.online()) { - if (outdoorSensor.readTemp()) { - float rawTemp = outdoorSensor.getTemp(); - DEBUG_F("[SENSORS][DS18B20] Raw temp: %f \n", rawTemp); + if ( settings.sensors.outdoor.type == 2 ) { + outdoorTemperatureSensor(); + } - if (emptyOutdoorTemp) { - filteredOutdoorTemp = rawTemp; - 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"); + if ( settings.sensors.indoor.type == 2 ) { + indoorTemperatureSensor(); } } + + 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(); + } }; \ No newline at end of file diff --git a/src/Settings.h b/src/Settings.h index 424be12..1b3de40 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -1,12 +1,10 @@ struct Settings { bool debug = false; - // 0 - boiler, 1 - manual, 2 - ds18b20 - byte outdoorTempSource = 0; char hostname[80] = "opentherm"; struct { - byte inPin = 5; - byte outPin = 4; + byte inPin = 4; + byte outPin = 5; unsigned int memberIdCode = 0; } opentherm; @@ -30,11 +28,15 @@ struct Settings { bool turbo = false; float target = 40.0f; float hysteresis = 0.5f; + byte minTemp = 20.0f; + byte maxTemp = 90.0f; } heating; struct { bool enable = true; byte target = 40; + byte minTemp = 30.0f; + byte maxTemp = 60.0f; } dhw; struct { @@ -42,6 +44,8 @@ struct Settings { float p_factor = 3; float i_factor = 0.2f; float d_factor = 0; + byte minTemp = 0.0f; + byte maxTemp = 90.0f; } pid; struct { @@ -51,6 +55,23 @@ struct Settings { float t_factor = 2.0f; } 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; struct Variables { @@ -84,11 +105,11 @@ struct Variables { } temperatures; struct { - byte heatingMinTemp = 20; - byte heatingMaxTemp = 90; + byte heatingMinTemp = 0; + byte heatingMaxTemp = 0; byte heatingSetpoint = 0.0f; - byte dhwMinTemp = 30; - byte dhwMaxTemp = 60; + byte dhwMinTemp = 0; + byte dhwMaxTemp = 0; uint8_t slaveMemberIdCode; uint8_t slaveType; uint8_t slaveVersion; diff --git a/src/WifiManagerTask.h b/src/WifiManagerTask.h index 8b00ee0..950853e 100644 --- a/src/WifiManagerTask.h +++ b/src/WifiManagerTask.h @@ -3,14 +3,17 @@ // Wifimanager WiFiManager wm; WiFiManagerParameter* wmHostname; -WiFiManagerParameter* wmOtInPin; -WiFiManagerParameter* wmOtOutPin; -WiFiManagerParameter* wmOtMemberIdCode; WiFiManagerParameter* wmMqttServer; WiFiManagerParameter* wmMqttPort; WiFiManagerParameter* wmMqttUser; WiFiManagerParameter* wmMqttPassword; WiFiManagerParameter* wmMqttPrefix; +WiFiManagerParameter* wmMqttPublishInterval; +WiFiManagerParameter* wmOtInPin; +WiFiManagerParameter* wmOtOutPin; +WiFiManagerParameter* wmOtMemberIdCode; +WiFiManagerParameter* wmOutdoorSensorPin; +WiFiManagerParameter* wmIndoorSensorPin; class WifiManagerTask: public Task { public: @@ -24,18 +27,6 @@ protected: wmHostname = new WiFiManagerParameter("hostname", "Hostname", settings.hostname, 80); 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); wm.addParameter(wmMqttServer); @@ -52,6 +43,30 @@ protected: wmMqttPrefix = new WiFiManagerParameter("mqtt_prefix", "MQTT prefix", settings.mqtt.prefix, 32); wm.addParameter(wmMqttPrefix); + sprintf(buffer, "%d", settings.mqtt.interval); + wmMqttPublishInterval = new WiFiManagerParameter("mqtt_publish_interval", "MQTT publish interval", buffer, 5); + wm.addParameter(wmMqttPublishInterval); + + sprintf(buffer, "%d", settings.opentherm.inPin); + wmOtInPin = new WiFiManagerParameter("ot_in_pin", "Opentherm pin IN", buffer, 2); + wm.addParameter(wmOtInPin); + + sprintf(buffer, "%d", settings.opentherm.outPin); + wmOtOutPin = new WiFiManagerParameter("ot_out_pin", "Opentherm pin OUT", buffer, 2); + wm.addParameter(wmOtOutPin); + + sprintf(buffer, "%d", settings.opentherm.memberIdCode); + wmOtMemberIdCode = new WiFiManagerParameter("ot_member_id_code", "Opentherm member id", buffer, 5); + wm.addParameter(wmOtMemberIdCode); + + sprintf(buffer, "%d", settings.sensors.outdoor.pin); + wmOutdoorSensorPin = new WiFiManagerParameter("outdoor_sensor_pin", "Outdoor sensor pin", buffer, 2); + wm.addParameter(wmOutdoorSensorPin); + + sprintf(buffer, "%d", settings.sensors.indoor.pin); + wmIndoorSensorPin = new WiFiManagerParameter("indoor_sensor_pin", "Indoor sensor pin", buffer, 2); + wm.addParameter(wmIndoorSensorPin); + //wm.setCleanConnect(true); wm.setRestorePersistent(false); @@ -94,32 +109,43 @@ protected: void static saveParamsCallback() { 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()); settings.mqtt.port = atoi(wmMqttPort->getValue()); strcpy(settings.mqtt.user, wmMqttUser->getValue()); strcpy(settings.mqtt.password, wmMqttPassword->getValue()); strcpy(settings.mqtt.prefix, wmMqttPrefix->getValue()); + settings.mqtt.interval = atoi(wmMqttPublishInterval->getValue()); + settings.opentherm.inPin = atoi(wmOtInPin->getValue()); + settings.opentherm.outPin = atoi(wmOtOutPin->getValue()); + settings.opentherm.memberIdCode = atoi(wmOtMemberIdCode->getValue()); + settings.sensors.outdoor.pin = atoi(wmOutdoorSensorPin->getValue()); + settings.sensors.indoor.pin = atoi(wmIndoorSensorPin->getValue()); INFO_F( "New settings:\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 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" + " Outdoor sensor pin: %d\r\n" + " Indoor sensor pin: %d\r\n", settings.hostname, - settings.opentherm.inPin, - settings.opentherm.outPin, - settings.opentherm.memberIdCode, settings.mqtt.server, settings.mqtt.port, 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.sensors.outdoor.pin, + settings.sensors.indoor.pin ); eeSettings.updateNow(); INFO(F("Settings saved")); diff --git a/src/defines.h b/src/defines.h index 536e7e4..6b0ddad 100644 --- a/src/defines.h +++ b/src/defines.h @@ -1,4 +1,4 @@ -#define OT_GATEWAY_VERSION "1.2.1" +#define OT_GATEWAY_VERSION "1.3.0" #define AP_SSID "OpenTherm Gateway" #define AP_PASSWORD "otgateway123456" #define USE_TELNET @@ -9,9 +9,8 @@ #define OPENTHERM_OFFLINE_TRESHOLD 10 -#define DS18B20_PIN 2 -#define DS18B20_INTERVAL 5000 -#define OUTDOOR_SENSOR_FILTER_K 0.15 +#define EXT_SENSORS_INTERVAL 5000 +#define EXT_SENSORS_FILTER_K 0.15 #define DS_CHECK_CRC true #define DS_CRC_USE_TABLE true @@ -19,6 +18,7 @@ #define LED_OT_RX_PIN 15 #define CONFIG_URL "http://%s/" +#define SETTINGS_VALID_VALUE "stvalid" // only 8 chars! #ifdef USE_TELNET diff --git a/src/main.cpp b/src/main.cpp index 673eda5..1820997 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,7 +29,7 @@ MainTask* tMain; void setup() { #ifdef USE_TELNET TelnetStream.begin(); - delay(5000); + delay(1000); #else Serial.begin(115200); Serial.println("\n\n"); @@ -40,6 +40,13 @@ void setup() { if (eeSettingsResult == 0) { 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) { INFO("Settings NOT loaded, first start"); @@ -56,7 +63,7 @@ void setup() { tOt = new OpenThermTask(false); Scheduler.start(tOt); - tSensors = new SensorsTask(false, DS18B20_INTERVAL); + tSensors = new SensorsTask(true, EXT_SENSORS_INTERVAL); Scheduler.start(tSensors); tRegulator = new RegulatorTask(true, 10000);