diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index b6408f82..2b554e69 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -12,6 +12,7 @@ - WM10 switch telegrams - boiler information (#633) - maintenance message and command +- thermostat program, reducemode, controlmode ### Fixed - mixer IPM pumpstatus diff --git a/interface/src/mqtt/MqttSettingsForm.tsx b/interface/src/mqtt/MqttSettingsForm.tsx index 4dcf9728..d3a4011d 100644 --- a/interface/src/mqtt/MqttSettingsForm.tsx +++ b/interface/src/mqtt/MqttSettingsForm.tsx @@ -106,17 +106,6 @@ class MqttSettingsForm extends React.Component { onChange={handleValueChange('max_topic_length')} margin="normal" /> - - Single - Nested - Home Assistant - { label="Retain Flag" />

+ + Formatting + + + by Sensor ID + by Number + + + } + label="Home Assistant MQTT Discovery" + /> + { data.ha_enabled && + + use Current temperature (default) + use Setpoint temperature + Fix to 0 + + } +

Publish Intervals diff --git a/interface/src/mqtt/types.ts b/interface/src/mqtt/types.ts index 7558c3a1..3270c9ab 100644 --- a/interface/src/mqtt/types.ts +++ b/interface/src/mqtt/types.ts @@ -33,7 +33,9 @@ export interface MqttSettings { publish_time_mixer: number; publish_time_other: number; publish_time_sensor: number; - mqtt_format: number; + dallas_format: number; mqtt_qos: number; mqtt_retain: boolean; + ha_enabled: boolean; + ha_climate_format: number; } diff --git a/interface/src/project/EMSESP.tsx b/interface/src/project/EMSESPDashboard.tsx similarity index 100% rename from interface/src/project/EMSESP.tsx rename to interface/src/project/EMSESPDashboard.tsx diff --git a/interface/src/project/EMSESPDevicesForm.tsx b/interface/src/project/EMSESPDevicesForm.tsx index bed023de..da8e6b85 100644 --- a/interface/src/project/EMSESPDevicesForm.tsx +++ b/interface/src/project/EMSESPDevicesForm.tsx @@ -342,13 +342,13 @@ class EMSESPDevicesForm extends Component< {deviceData.data.map((item, i) => { - if (i % 2) { + if (i % 3) { return null; } else { return ( - {deviceData.data[i]} - {deviceData.data[i + 1]} + {deviceData.data[i+2]} + {deviceData.data[i]}{deviceData.data[i + 1]} ); } diff --git a/interface/src/project/EMSESPStatusForm.tsx b/interface/src/project/EMSESPStatusForm.tsx index 966e109b..8c68ad95 100644 --- a/interface/src/project/EMSESPStatusForm.tsx +++ b/interface/src/project/EMSESPStatusForm.tsx @@ -60,30 +60,16 @@ class EMSESPStatusForm extends Component { - Received telegrams + # Telegrams Received - {formatNumber(data.rx_received)} + {formatNumber(data.rx_received)} ({data.rx_quality}%) - Rx line quality - - {data.rx_quality} % - - - - - Sent telegrams + # Telegrams Sent - {formatNumber(data.tx_sent)} - - - - - Tx line quality - - {data.tx_quality} % + {formatNumber(data.tx_sent)} ({data.tx_quality}%) diff --git a/interface/src/project/ProjectRouting.tsx b/interface/src/project/ProjectRouting.tsx index b5cb21a2..489642da 100644 --- a/interface/src/project/ProjectRouting.tsx +++ b/interface/src/project/ProjectRouting.tsx @@ -3,7 +3,7 @@ import { Redirect, Switch } from 'react-router'; import { AuthenticatedRoute } from '../authentication'; -import EMSESP from './EMSESP'; +import EMSESPDashboard from './EMSESPDashboard'; import EMSESPSettings from './EMSESPSettings'; class ProjectRouting extends Component { @@ -11,9 +11,9 @@ class ProjectRouting extends Component { render() { return ( - + - + { /* * The redirect below caters for the default project route and redirecting invalid paths. diff --git a/lib/framework/MqttSettingsService.cpp b/lib/framework/MqttSettingsService.cpp index 41fd7675..0fceef01 100644 --- a/lib/framework/MqttSettingsService.cpp +++ b/lib/framework/MqttSettingsService.cpp @@ -188,9 +188,11 @@ void MqttSettings::read(MqttSettings & settings, JsonObject & root) { root["publish_time_mixer"] = settings.publish_time_mixer; root["publish_time_other"] = settings.publish_time_other; root["publish_time_sensor"] = settings.publish_time_sensor; - root["mqtt_format"] = settings.mqtt_format; root["mqtt_qos"] = settings.mqtt_qos; root["mqtt_retain"] = settings.mqtt_retain; + root["dallas_format"] = settings.dallas_format; + root["ha_climate_format"] = settings.ha_climate_format; + root["ha_enabled"] = settings.ha_enabled; } StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & settings) { @@ -205,6 +207,8 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting newSettings.keepAlive = root["keep_alive"] | FACTORY_MQTT_KEEP_ALIVE; newSettings.cleanSession = root["clean_session"] | FACTORY_MQTT_CLEAN_SESSION; newSettings.maxTopicLength = root["max_topic_length"] | FACTORY_MQTT_MAX_TOPIC_LENGTH; + newSettings.mqtt_qos = root["mqtt_qos"] | EMSESP_DEFAULT_MQTT_QOS; + newSettings.mqtt_retain = root["mqtt_retain"] | EMSESP_DEFAULT_MQTT_RETAIN; newSettings.publish_time_boiler = root["publish_time_boiler"] | EMSESP_DEFAULT_PUBLISH_TIME; newSettings.publish_time_thermostat = root["publish_time_thermostat"] | EMSESP_DEFAULT_PUBLISH_TIME; @@ -212,16 +216,25 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting newSettings.publish_time_mixer = root["publish_time_mixer"] | EMSESP_DEFAULT_PUBLISH_TIME; newSettings.publish_time_other = root["publish_time_other"] | EMSESP_DEFAULT_PUBLISH_TIME; newSettings.publish_time_sensor = root["publish_time_sensor"] | EMSESP_DEFAULT_PUBLISH_TIME; - newSettings.mqtt_format = root["mqtt_format"] | EMSESP_DEFAULT_MQTT_FORMAT; - newSettings.mqtt_qos = root["mqtt_qos"] | EMSESP_DEFAULT_MQTT_QOS; - newSettings.mqtt_retain = root["mqtt_retain"] | EMSESP_DEFAULT_MQTT_RETAIN; + + newSettings.dallas_format = root["dallas_format"] | EMSESP_DEFAULT_DALLAS_FORMAT; + newSettings.ha_climate_format = root["ha_climate_format"] | EMSESP_DEFAULT_HA_CLIMATE_FORMAT; + newSettings.ha_enabled = root["ha_enabled"] | EMSESP_DEFAULT_HA_ENABLED; if (newSettings.mqtt_qos != settings.mqtt_qos) { emsesp::EMSESP::mqtt_.set_qos(newSettings.mqtt_qos); } - if (newSettings.mqtt_format != settings.mqtt_format) { - emsesp::EMSESP::mqtt_.set_format(newSettings.mqtt_format); + if (newSettings.dallas_format != settings.dallas_format) { + emsesp::EMSESP::mqtt_.dallas_format(newSettings.dallas_format); + } + + if (newSettings.ha_climate_format != settings.ha_climate_format) { + emsesp::EMSESP::mqtt_.ha_climate_format(newSettings.ha_climate_format); + } + + if (newSettings.ha_enabled != settings.ha_enabled) { + emsesp::EMSESP::mqtt_.ha_enabled(newSettings.ha_enabled); } if (newSettings.mqtt_retain != settings.mqtt_retain) { diff --git a/lib/framework/MqttSettingsService.h b/lib/framework/MqttSettingsService.h index 25368176..f2a5ef92 100644 --- a/lib/framework/MqttSettingsService.h +++ b/lib/framework/MqttSettingsService.h @@ -60,9 +60,11 @@ static String generateClientId() { #define FACTORY_MQTT_MAX_TOPIC_LENGTH 128 #endif -#define EMSESP_DEFAULT_MQTT_FORMAT 2 // nested +#define EMSESP_DEFAULT_DALLAS_FORMAT 1 // sensorid +#define EMSESP_DEFAULT_HA_CLIMATE_FORMAT 1 // current temp #define EMSESP_DEFAULT_MQTT_QOS 0 #define EMSESP_DEFAULT_MQTT_RETAIN false +#define EMSESP_DEFAULT_HA_ENABLED false #define EMSESP_DEFAULT_PUBLISH_TIME 10 class MqttSettings { @@ -91,9 +93,11 @@ class MqttSettings { uint16_t publish_time_mixer; uint16_t publish_time_other; uint16_t publish_time_sensor; - uint8_t mqtt_format; // 1=single, 2=nested, 3=ha, 4=custom uint8_t mqtt_qos; bool mqtt_retain; + uint8_t dallas_format; + uint8_t ha_climate_format; + bool ha_enabled; static void read(MqttSettings & settings, JsonObject & root); static StateUpdateResult update(JsonObject & root, MqttSettings & settings); diff --git a/lib/uuid-common/src/compare_flash_string.cpp b/lib/uuid-common/src/compare_flash_string.cpp new file mode 100644 index 00000000..9e3ecc7c --- /dev/null +++ b/lib/uuid-common/src/compare_flash_string.cpp @@ -0,0 +1,71 @@ +/* + * EMS-ESP - https://github.com/proddy/EMS-ESP + * Copyright 2020 Paul Derbyshire + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +// #ifdef ESP8266 +// #include +// #else +// #include +// #endif + +#include + +namespace uuid { + +// On ESP8266, pgm_read_byte() already takes care of 4-byte alignment, and +// memcpy_P(s, p, 4) makes 4 calls to pgm_read_byte() anyway, so don't bother +// optimizing for 4-byte alignment here. + +// class __FlashStringHelper; + +int compare_flash_string(const __FlashStringHelper * a, const __FlashStringHelper * b) { + const char * aa = reinterpret_cast(a); + const char * bb = reinterpret_cast(b); + + while (true) { + uint8_t ca = pgm_read_byte(aa); + uint8_t cb = pgm_read_byte(bb); + if (ca != cb) + return (int)ca - (int)cb; + if (ca == 0) + return 0; + aa++; + bb++; + } +} + +int compare_flash_string(const __FlashStringHelper * a, const __FlashStringHelper * b, size_t n) { + const char * aa = reinterpret_cast(a); + const char * bb = reinterpret_cast(b); + + while (n > 0) { + uint8_t ca = pgm_read_byte(aa); + uint8_t cb = pgm_read_byte(bb); + if (ca != cb) + return (int)ca - (int)cb; + if (ca == 0) + return 0; + aa++; + bb++; + n--; + } + return 0; +} + +} // namespace uuid diff --git a/lib/uuid-common/src/uuid/common.h b/lib/uuid-common/src/uuid/common.h index 4665c950..1a1ae18e 100644 --- a/lib/uuid-common/src/uuid/common.h +++ b/lib/uuid-common/src/uuid/common.h @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +// compare_flash_string added by Proddy + #ifndef UUID_COMMON_H_ #define UUID_COMMON_H_ @@ -32,6 +34,21 @@ */ namespace uuid { +/** + * String compare two flash strings + * + * The flash string must be stored with appropriate alignment for + * reading it on the platform. + * + * @param[in] a Pointer to string stored in flash. + * @param[in] b Pointer to string stored in flash. + * @param[in] n optional max length + * @return 0 for match, otherwise diff + * @since 1.0.0 + */ +int compare_flash_string(const __FlashStringHelper * a, const __FlashStringHelper * b); +int compare_flash_string(const __FlashStringHelper * a, const __FlashStringHelper * b, size_t n); + /** * Read a string from flash and convert it to a std::string. * diff --git a/lib_standalone/ESP8266React.h b/lib_standalone/ESP8266React.h index e6dbdf52..8a546fee 100644 --- a/lib_standalone/ESP8266React.h +++ b/lib_standalone/ESP8266React.h @@ -13,22 +13,27 @@ class DummySettings { public: - uint8_t tx_mode = 1; - uint8_t ems_bus_id = 0x0B; - bool syslog_enabled = false; - int8_t syslog_level = 3; // uuid::log::Level - uint32_t syslog_mark_interval = 0; - String syslog_host = "192.168.1.4"; - uint8_t master_thermostat = 0; - bool shower_timer = false; - bool shower_alert = false; - bool hide_led = false; - bool api_enabled = true; - uint16_t publish_time = 10; // seconds - uint8_t mqtt_format = 3; // 1=single, 2=nested, 3=ha, 4=custom - uint8_t mqtt_qos = 0; - bool mqtt_retain = false; - bool enabled = true; // MQTT + uint8_t tx_mode = 1; + uint8_t ems_bus_id = 0x0B; + bool syslog_enabled = false; + int8_t syslog_level = 3; // uuid::log::Level + uint32_t syslog_mark_interval = 0; + String syslog_host = "192.168.1.4"; + uint8_t master_thermostat = 0; + bool shower_timer = true; + bool shower_alert = false; + bool hide_led = false; + bool api_enabled = true; + + // MQTT + uint16_t publish_time = 10; // seconds + uint8_t mqtt_qos = 0; + bool mqtt_retain = false; + bool enabled = true; + uint8_t dallas_format = 1; + uint8_t ha_climate_format = 1; + bool ha_enabled = false; + String hostname = "ems-esp"; String jwtSecret = "ems-esp"; String ssid = "ems-esp"; diff --git a/src/WebAPIService.cpp b/src/WebAPIService.cpp index 77d13de9..f0e93932 100644 --- a/src/WebAPIService.cpp +++ b/src/WebAPIService.cpp @@ -28,7 +28,7 @@ WebAPIService::WebAPIService(AsyncWebServer * server) { server->on(EMSESP_API_SERVICE_PATH, HTTP_GET, std::bind(&WebAPIService::webAPIService, this, std::placeholders::_1)); } -// http://ems-esp/api?device=boiler&cmd=wwtemp&data=20&id=1 +// e.g. http://ems-esp/api?device=boiler&cmd=wwtemp&data=20&id=1 void WebAPIService::webAPIService(AsyncWebServerRequest * request) { // see if the API is enabled bool api_enabled; @@ -71,9 +71,9 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request) { id = "-1"; } - DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_MEDIUM_DYN); - JsonObject json = doc.to(); - bool ok = false; + DynamicJsonDocument doc(EMSESP_JSON_SIZE_LARGE_DYN); + JsonObject json = doc.to(); + bool ok = false; // execute the command if (data.isEmpty()) { @@ -82,39 +82,20 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request) { if (api_enabled) { // we only allow commands with parameters if the API is enabled ok = Command::call(device_type, cmd.c_str(), data.c_str(), id.toInt(), json); // has cmd, data and id + if (ok && json.size()) { + // send json output back to web + std::string buffer; + serializeJsonPretty(doc, buffer); + request->send(200, "text/plain", buffer.c_str()); + return; + } } else { request->send(401, "text/plain", F("Unauthorized")); return; } } -// debug -#if defined(EMSESP_DEBUG) - std::string debug(200, '\0'); - snprintf_P(&debug[0], - debug.capacity() + 1, - PSTR("[DEBUG] API: device=%s cmd=%s data=%s id=%s [%s]"), - device.c_str(), - cmd.c_str(), - data.c_str(), - id.c_str(), - ok ? PSTR("OK") : PSTR("Invalid")); - EMSESP::logger().debug(debug.c_str()); - if (json.size()) { - std::string buffer2; - serializeJson(doc, buffer2); - EMSESP::logger().debug("json (max 255 chars): %s", buffer2.c_str()); - } -#endif - - // if we have returned data in JSON format, send this to the WEB - if (json.size()) { - std::string buffer; - serializeJsonPretty(doc, buffer); - request->send(200, "text/plain", buffer.c_str()); - } else { - request->send(200, "text/plain", ok ? F("OK") : F("Invalid")); - } + request->send(200, "text/plain", ok ? F("OK") : F("Invalid")); } } // namespace emsesp \ No newline at end of file diff --git a/src/WebDevicesService.cpp b/src/WebDevicesService.cpp index e3410f5f..9b5c0336 100644 --- a/src/WebDevicesService.cpp +++ b/src/WebDevicesService.cpp @@ -45,7 +45,7 @@ void WebDevicesService::scan_devices(AsyncWebServerRequest * request) { } void WebDevicesService::all_devices(AsyncWebServerRequest * request) { - AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_MAX_JSON_SIZE_LARGE_DYN); + AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_LARGE_DYN); JsonObject root = response->getRoot(); JsonArray devices = root.createNestedArray("devices"); @@ -78,19 +78,28 @@ void WebDevicesService::all_devices(AsyncWebServerRequest * request) { request->send(response); } +// The unique_id is the unique record ID from the Web table to identify which device to load void WebDevicesService::device_data(AsyncWebServerRequest * request, JsonVariant & json) { if (json.is()) { - AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_MAX_JSON_SIZE_MAX_DYN); + AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_XLARGE_DYN); + for (const auto & emsdevice : EMSESP::emsdevices) { + if (emsdevice) { + if (emsdevice->unique_id() == json["id"]) { #ifndef EMSESP_STANDALONE - uint8_t id = json["id"]; // get id from selected table row - EMSESP::device_info_web(id, (JsonObject &)response->getRoot()); + JsonObject root = response->getRoot(); + emsdevice->generate_values_json_web(root); #endif - response->setLength(); - request->send(response); - } else { - AsyncWebServerResponse * response = request->beginResponse(200); - request->send(response); + response->setLength(); + request->send(response); + return; + } + } + } } + + // invalid + AsyncWebServerResponse * response = request->beginResponse(200); + request->send(response); } } // namespace emsesp \ No newline at end of file diff --git a/src/WebStatusService.cpp b/src/WebStatusService.cpp index cb1ed697..9fa8caa3 100644 --- a/src/WebStatusService.cpp +++ b/src/WebStatusService.cpp @@ -44,7 +44,7 @@ void WebStatusService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInf } void WebStatusService::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) { EMSESP::logger().info(F("WiFi Connected with IP=%s, hostname=%s"), WiFi.localIP().toString().c_str(), WiFi.getHostname()); - EMSESP::system_.send_heartbeat(); // send out heartbeat MQTT as soon as we have a connection + EMSESP::system_.reset_system_check(); // send out heartbeat MQTT as soon as we have a connection } #elif defined(ESP8266) void WebStatusService::onStationModeDisconnected(const WiFiEventStationModeDisconnected & event) { @@ -52,12 +52,12 @@ void WebStatusService::onStationModeDisconnected(const WiFiEventStationModeDisco } void WebStatusService::onStationModeGotIP(const WiFiEventStationModeGotIP & event) { EMSESP::logger().info(F("WiFi Connected with IP=%s, hostname=%s"), event.ip.toString().c_str(), WiFi.hostname().c_str()); - EMSESP::system_.send_heartbeat(); // send out heartbeat MQTT as soon as we have a connection + EMSESP::system_.reset_system_check(); // send out heartbeat MQTT as soon as we have a connection } #endif void WebStatusService::webStatusService(AsyncWebServerRequest * request) { - AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_MAX_JSON_SIZE_MEDIUM_DYN); + AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_MEDIUM_DYN); JsonObject root = response->getRoot(); root["status"] = EMSESP::bus_status(); // 0, 1 or 2 diff --git a/src/command.cpp b/src/command.cpp index 54186b78..44eb5887 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -26,7 +26,7 @@ uuid::log::Logger Command::logger_{F_(command), uuid::log::Facility::DAEMON}; std::vector Command::cmdfunctions_; -// calls a command, context is the device_type +// calls a command // id may be used to represent a heating circuit for example // returns false if error or not found bool Command::call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id) { @@ -49,7 +49,7 @@ bool Command::call(const uint8_t device_type, const char * cmd, const char * val return ((cf->cmdfunction_)(value, id)); } -// calls a command, context is the device_type. Takes a json object for output. +// calls a command. Takes a json object for output. // id may be used to represent a heating circuit for example // returns false if error or not found bool Command::call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id, JsonObject & json) { @@ -92,7 +92,7 @@ void Command::add(const uint8_t device_type, const uint8_t device_id, const __Fl // see if we need to subscribe if (Mqtt::enabled()) { - Mqtt::register_command(device_type, device_id, cmd, cb); + Mqtt::register_command(device_type, cmd, cb); } } diff --git a/src/console.cpp b/src/console.cpp index f2b54c69..107ef484 100644 --- a/src/console.cpp +++ b/src/console.cpp @@ -387,7 +387,7 @@ void EMSESPShell::add_console_commands() { return; } - DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_MEDIUM_DYN); + DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN); JsonObject json = doc.to(); bool ok = false; diff --git a/src/dallassensor.cpp b/src/dallassensor.cpp index 3beef959..bafb40a5 100644 --- a/src/dallassensor.cpp +++ b/src/dallassensor.cpp @@ -54,7 +54,7 @@ void DallasSensor::reload() { parasite_ = settings.dallas_parasite; }); - if (Mqtt::mqtt_format() == Mqtt::Format::HA) { + if (Mqtt::ha_enabled()) { for (uint8_t i = 0; i < MAX_SENSORS; registered_ha_[i++] = false) ; } @@ -282,14 +282,10 @@ bool DallasSensor::updated_values() { return false; } -bool DallasSensor::command_info(const char * value, const int8_t id, JsonObject & json) { - return (export_values(json)); -} - // creates JSON doc from values // returns false if empty // e.g. dallassensor_data = {"sensor1":{"id":"28-EA41-9497-0E03-5F","temp":23.30},"sensor2":{"id":"28-233D-9497-0C03-8B","temp":24.0}} -bool DallasSensor::export_values(JsonObject & json) { +bool DallasSensor::command_info(const char * value, const int8_t id, JsonObject & json) { if (sensors_.size() == 0) { return false; } @@ -317,18 +313,20 @@ void DallasSensor::publish_values(const bool force) { } DynamicJsonDocument doc(100 * num_sensors); - uint8_t sensor_no = 1; - uint8_t mqtt_format_ = Mqtt::mqtt_format(); + uint8_t sensor_no = 1; + + // dallas format is overriden when using Home Assistant + uint8_t dallas_format = Mqtt::ha_enabled() ? Mqtt::Dallas_Format::SENSORID : Mqtt::dallas_format(); for (const auto & sensor : sensors_) { char sensorID[10]; // sensor{1-n} snprintf_P(sensorID, 10, PSTR("sensor%d"), sensor_no); - if (mqtt_format_ == Mqtt::Format::SINGLE) { + if (dallas_format == Mqtt::Dallas_Format::NUMBER) { // e.g. dallassensor_data = {"28-EA41-9497-0E03":23.3,"28-233D-9497-0C03":24.0} if (Helpers::hasValue(sensor.temperature_c)) { doc[sensor.to_string()] = (float)(sensor.temperature_c) / 10; } - } else { + } else if (dallas_format == Mqtt::Dallas_Format::SENSORID) { // e.g. dallassensor_data = {"sensor1":{"id":"28-EA41-9497-0E03","temp":23.3},"sensor2":{"id":"28-233D-9497-0C03","temp":24.0}} JsonObject dataSensor = doc.createNestedObject(sensorID); dataSensor["id"] = sensor.to_string(); @@ -339,9 +337,9 @@ void DallasSensor::publish_values(const bool force) { // create the HA MQTT config // to e.g. homeassistant/sensor/ems-esp/dallas_28-233D-9497-0C03/config - if (mqtt_format_ == Mqtt::Format::HA) { + if (Mqtt::ha_enabled()) { if (!(registered_ha_[sensor_no - 1]) || force) { - StaticJsonDocument config; + StaticJsonDocument config; config["dev_cla"] = FJSON("temperature"); char stat_t[50]; @@ -365,8 +363,8 @@ void DallasSensor::publish_values(const bool force) { JsonArray ids = dev.createNestedArray("ids"); ids.add("ems-esp"); - std::string topic(100, '\0'); - snprintf_P(&topic[0], 100, PSTR("homeassistant/sensor/ems-esp/dallas_%s/config"), sensor.to_string().c_str()); + char topic[100]; + snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/dallas_%s/config"), System::hostname().c_str(), sensor.to_string().c_str()); Mqtt::publish_ha(topic, config.as()); registered_ha_[sensor_no - 1] = true; diff --git a/src/dallassensor.h b/src/dallassensor.h index 0f4ebddb..c186a80f 100644 --- a/src/dallassensor.h +++ b/src/dallassensor.h @@ -102,7 +102,6 @@ class DallasSensor { uint64_t get_id(const uint8_t addr[]); bool command_info(const char * value, const int8_t id, JsonObject & json); - bool export_values(JsonObject & doc); uint32_t last_activity_ = uuid::get_uptime(); uint32_t last_publish_ = uuid::get_uptime(); diff --git a/src/device_library.h b/src/device_library.h index 95631e34..8d8050b2 100644 --- a/src/device_library.h +++ b/src/device_library.h @@ -63,7 +63,7 @@ {202, DeviceType::THERMOSTAT, F("Logamatic TC100/Moduline Easy"), DeviceFlags::EMS_DEVICE_FLAG_EASY | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write {203, DeviceType::THERMOSTAT, F("EasyControl CT200"), DeviceFlags::EMS_DEVICE_FLAG_EASY | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write -// Thermostat - Buderus/Nefit/Bosch specific - 0x17 / 0x10 / 0x18 / 0x19 +// Thermostat - Buderus/Nefit/Bosch specific - 0x17 / 0x10 / 0x18 / 0x19 / 0x38 { 67, DeviceType::THERMOSTAT, F("RC30"), DeviceFlags::EMS_DEVICE_FLAG_RC30_1},// 0x10 - based on RC35 { 77, DeviceType::THERMOSTAT, F("RC20/Moduline 300"), DeviceFlags::EMS_DEVICE_FLAG_RC20},// 0x17 { 78, DeviceType::THERMOSTAT, F("Moduline 400"), DeviceFlags::EMS_DEVICE_FLAG_RC30}, // 0x10 @@ -91,7 +91,7 @@ {191, DeviceType::THERMOSTAT, F("FR120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_OLD}, // older model {192, DeviceType::THERMOSTAT, F("FW120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS}, -// Solar Modules - 0x30 +// Solar Modules - 0x30, 0x2A (for ww) { 73, DeviceType::SOLAR, F("SM10"), DeviceFlags::EMS_DEVICE_FLAG_SM10}, {101, DeviceType::SOLAR, F("ISM1"), DeviceFlags::EMS_DEVICE_FLAG_ISM}, {162, DeviceType::SOLAR, F("SM50"), DeviceFlags::EMS_DEVICE_FLAG_SM100}, diff --git a/src/devices/boiler.cpp b/src/devices/boiler.cpp index 73e319a8..f87ea496 100644 --- a/src/devices/boiler.cpp +++ b/src/devices/boiler.cpp @@ -72,31 +72,265 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const register_mqtt_cmd(F("boilhystoff"), [&](const char * value, const int8_t id) { return set_hyst_off(value, id); }); register_mqtt_cmd(F("burnperiod"), [&](const char * value, const int8_t id) { return set_burn_period(value, id); }); register_mqtt_cmd(F("pumpdelay"), [&](const char * value, const int8_t id) { return set_pump_delay(value, id); }); - register_mqtt_cmd(F("reset"), [&](const char * value, const int8_t id) { return set_reset(value, id); }); + // register_mqtt_cmd(F("reset"), [&](const char * value, const int8_t id) { return set_reset(value, id); }); + register_mqtt_cmd(F("maintenance"), [&](const char * value, const int8_t id) { return set_maintenance(value, id); }); EMSESP::send_read_request(0x10, device_id); // read last errorcode on start (only published on errors) EMSESP::send_read_request(0x11, device_id); // read last errorcode on start (only published on errors) EMSESP::send_read_request(0x15, device_id); // read maintenace data on start (only published on change) -} -// create the config topics for Home Assistant MQTT Discovery -// for each of the main elements -void Boiler::register_mqtt_ha_config() { - if (!Mqtt::connected()) { - return; - } + // add values + // tags + std::string boiler_data_ww = "boiler_data_ww"; + std::string boiler_data = "boiler_data"; + std::string boiler_data_info = "boiler_data_info"; - // Create the Master device - StaticJsonDocument doc; - doc["name"] = FJSON("Service Code"); - doc["uniq_id"] = FJSON("boiler"); - doc["ic"] = FJSON("mdi:home-thermometer-outline"); + // ww + register_device_value(boiler_data_ww, &wWSelTemp_, DeviceValueType::UINT, {}, F("wWSelTemp"), F("Warm Water selected temperature"), DeviceValueUOM::DEGREES); + register_device_value(boiler_data_ww, &wWSetTemp_, DeviceValueType::UINT, {}, F("wWSetTemp"), F("Warm water set temperature"), DeviceValueUOM::DEGREES); + register_device_value(boiler_data_ww, + &wWType_, + DeviceValueType::ENUM, + flash_string_vector{F("off"), F("flow"), F("buffered flow"), F("buffer"), F("layered buffer")}, + F("wWType"), + F("Warm water type"), + DeviceValueUOM::NONE); + register_device_value(boiler_data_ww, + &wWComfort_, + DeviceValueType::ENUM, + flash_string_vector{F("hot"), F("eco"), F("intelligent")}, + F("wWComfort"), + F("Warm water comfort"), + DeviceValueUOM::NONE); + register_device_value(boiler_data_ww, &wWCircPump_, DeviceValueType::BOOL, {}, F("wWCircPump"), F("Warm water circulation pump available"), DeviceValueUOM::NONE); + register_device_value(boiler_data_ww, + &wWChargeType_, + DeviceValueType::BOOL, + flash_string_vector{F("3-way valve"), F("charge pump")}, + F("wWChargeType"), + F("Warm Water charging type"), + DeviceValueUOM::NONE); + register_device_value(boiler_data_ww, + &wWDisinfectionTemp_, + DeviceValueType::UINT, + {}, + F("wWDisinfectionTemp"), + F("Warm Water disinfection temperature"), + DeviceValueUOM::DEGREES); + register_device_value(boiler_data_ww, + &wWCircPumpMode_, + DeviceValueType::ENUM, + flash_string_vector{F("1x3min"), F("2x3min"), F("3x3min"), F("4x3min"), F("5x3min"), F("6x3min"), F("continuos")}, + F("wWCircPumpMode"), + F("Warm water circulation pump freq"), + DeviceValueUOM::NONE); + register_device_value(boiler_data_ww, &wWCirc_, DeviceValueType::BOOL, {}, F("wWCirc"), F("Warm Water circulation active"), DeviceValueUOM::NONE); + register_device_value( + boiler_data_ww, &wWCurTemp_, DeviceValueType::USHORT, {F("10")}, F("wWCurTemp"), F("Warm Water current temperature (intern)"), DeviceValueUOM::DEGREES); + register_device_value(boiler_data_ww, + &wWCurTemp2_, + DeviceValueType::USHORT, + flash_string_vector{F("10")}, + F("wWCurTemp2"), + F("Warm Water current temperature (extern)"), + DeviceValueUOM::DEGREES); + register_device_value(boiler_data_ww, &wWCurFlow_, DeviceValueType::UINT, {F("10")}, F("wWCurFlow"), F("Warm Water current tap water flow"), DeviceValueUOM::LMIN); + register_device_value(boiler_data_ww, + &wwStorageTemp1_, + DeviceValueType::USHORT, + flash_string_vector{F("10")}, + F("wwStorageTemp1"), + F("Warm water storage temperature (intern)"), + DeviceValueUOM::DEGREES); + register_device_value(boiler_data_ww, + &wwStorageTemp2_, + DeviceValueType::USHORT, + flash_string_vector{F("10")}, + F("wwStorageTemp2"), + F("Warm water storage temperature (extern)"), + DeviceValueUOM::DEGREES); + register_device_value(boiler_data_ww, &wWActivated_, DeviceValueType::BOOL, {}, F("wWActivated"), F("Warm Water activated"), DeviceValueUOM::NONE); + register_device_value(boiler_data_ww, &wWOneTime_, DeviceValueType::BOOL, {}, F("wWOneTime"), F("Warm Water one time charging"), DeviceValueUOM::NONE); + register_device_value(boiler_data_ww, &wWDisinfecting_, DeviceValueType::BOOL, {}, F("wWDisinfecting"), F("Warm Water disinfecting"), DeviceValueUOM::NONE); + register_device_value(boiler_data_ww, &wWCharging_, DeviceValueType::BOOL, {}, F("wWCharging"), F("Warm Water charging"), DeviceValueUOM::NONE); + register_device_value(boiler_data_ww, &wWRecharging_, DeviceValueType::BOOL, {}, F("wWRecharging"), F("Warm Water recharging"), DeviceValueUOM::NONE); + register_device_value(boiler_data_ww, &wWTempOK_, DeviceValueType::BOOL, {}, F("wWTempOK"), F("Warm Water temperature ok"), DeviceValueUOM::NONE); + register_device_value(boiler_data_ww, &wWActive_, DeviceValueType::BOOL, {}, F("wWActive"), F("Warm Water active"), DeviceValueUOM::NONE); + register_device_value(boiler_data_ww, &wWHeat_, DeviceValueType::BOOL, {}, F("wWHeat"), F("Warm Water heating"), DeviceValueUOM::NONE); + register_device_value(boiler_data_ww, &wWSetPumpPower_, DeviceValueType::UINT, {}, F("wWSetPumpPower"), F("Warm Water pump set power"), DeviceValueUOM::PERCENT); + register_device_value( + boiler_data_ww, &wwMixTemperature_, DeviceValueType::USHORT, {}, F("wwMixTemperature"), F("Warm Water mix temperature"), DeviceValueUOM::DEGREES); + register_device_value(boiler_data_ww, + &wwBufferTemperature_, + DeviceValueType::USHORT, + {}, + F("wwBufferTemperature"), + F("Warm Water buffer boiler temperature"), + DeviceValueUOM::DEGREES); + register_device_value(boiler_data_ww, &wWStarts_, DeviceValueType::ULONG, {}, F("wWStarts"), F("Warm Water # starts"), DeviceValueUOM::NONE); + register_device_value(boiler_data_ww, &wWStarts2_, DeviceValueType::ULONG, {}, F("wWStarts2"), F("Warm Water # starts (control)"), DeviceValueUOM::NONE); + register_device_value(boiler_data_ww, &wWWorkM_, DeviceValueType::TIME, {}, F("wWWorkM"), F("Warm Water active time"), DeviceValueUOM::MINUTES); + + // main + register_device_value(boiler_data, &heatingActive_, DeviceValueType::BOOL, {}, F("heatingActive"), F("Heating active"), DeviceValueUOM::NONE); + register_device_value(boiler_data, &tapwaterActive_, DeviceValueType::BOOL, {}, F("tapwaterActive"), F("Warm water/DHW active"), DeviceValueUOM::NONE); + register_device_value(boiler_data, &selFlowTemp_, DeviceValueType::UINT, {}, F("selFlowTemp"), F("Selected flow temperature"), DeviceValueUOM::DEGREES); + register_device_value(boiler_data, &selBurnPow_, DeviceValueType::UINT, {}, F("selBurnPow"), F("Burner selected max power"), DeviceValueUOM::PERCENT); + register_device_value(boiler_data, &pumpMod_, DeviceValueType::UINT, {}, F("pumpMod"), F("Pump modulation"), DeviceValueUOM::PERCENT); + register_device_value(boiler_data, &pumpMod2_, DeviceValueType::UINT, {}, F("pumpMod2"), F("Heat pump modulation"), DeviceValueUOM::PERCENT); + register_device_value( + boiler_data, &outdoorTemp_, DeviceValueType::SHORT, flash_string_vector{F("10")}, F("outdoorTemp"), F("Outside temperature"), DeviceValueUOM::DEGREES); + register_device_value(boiler_data, + &curFlowTemp_, + DeviceValueType::USHORT, + flash_string_vector{F("10")}, + F("curFlowTemp"), + F("Current flow temperature"), + DeviceValueUOM::DEGREES); + register_device_value(boiler_data, &retTemp_, DeviceValueType::USHORT, flash_string_vector{F("10")}, F("retTemp"), F("Return temperature"), DeviceValueUOM::DEGREES); + register_device_value(boiler_data, + &switchTemp_, + DeviceValueType::USHORT, + flash_string_vector{F("10")}, + F("switchTemp"), + F("Mixing switch temperature"), + DeviceValueUOM::DEGREES); + register_device_value(boiler_data, &sysPress_, DeviceValueType::UINT, flash_string_vector{F("10")}, F("sysPress"), F("System pressure"), DeviceValueUOM::BAR); + register_device_value( + boiler_data, &boilTemp_, DeviceValueType::USHORT, flash_string_vector{F("10")}, F("boilTemp"), F("Max boiler temperature"), DeviceValueUOM::DEGREES); + register_device_value( + boiler_data, &exhaustTemp_, DeviceValueType::USHORT, flash_string_vector{F("10")}, F("exhaustTemp"), F("Exhaust temperature"), DeviceValueUOM::DEGREES); + register_device_value(boiler_data, &burnGas_, DeviceValueType::BOOL, {}, F("burnGas"), F("Gas"), DeviceValueUOM::NONE); + register_device_value(boiler_data, &flameCurr_, DeviceValueType::USHORT, {F("10")}, F("flameCurr"), F("Flame current"), DeviceValueUOM::UA); + register_device_value(boiler_data, &heatPump_, DeviceValueType::BOOL, {}, F("heatPump"), F("Heat pump"), DeviceValueUOM::NONE); + register_device_value(boiler_data, &fanWork_, DeviceValueType::BOOL, {}, F("fanWork"), F("Fan"), DeviceValueUOM::NONE); + register_device_value(boiler_data, &ignWork_, DeviceValueType::BOOL, {}, F("ignWork"), F("Ignition"), DeviceValueUOM::NONE); + register_device_value(boiler_data, &heatingActivated_, DeviceValueType::BOOL, {}, F("heatingActivated"), F("Heating activated"), DeviceValueUOM::NONE); + register_device_value(boiler_data, &heatingTemp_, DeviceValueType::UINT, {}, F("heatingTemp"), F("Heating temperature"), DeviceValueUOM::DEGREES); + register_device_value(boiler_data, &pumpModMax_, DeviceValueType::UINT, {}, F("pumpModMax"), F("Burner pump max power"), DeviceValueUOM::PERCENT); + register_device_value(boiler_data, &pumpModMin_, DeviceValueType::UINT, {}, F("pumpModMin"), F("Burner pump min power"), DeviceValueUOM::PERCENT); + register_device_value(boiler_data, &pumpDelay_, DeviceValueType::UINT, {}, F("pumpDelay"), F("Pump delay"), DeviceValueUOM::MINUTES); + register_device_value(boiler_data, &burnMinPeriod_, DeviceValueType::UINT, {}, F("burnMinPeriod"), F("Burner min period"), DeviceValueUOM::MINUTES); + register_device_value(boiler_data, &burnMinPower_, DeviceValueType::UINT, {}, F("burnMinPower"), F("Burner min power"), DeviceValueUOM::PERCENT); + register_device_value(boiler_data, &burnMaxPower_, DeviceValueType::UINT, {}, F("burnMaxPower"), F("Burner max power"), DeviceValueUOM::PERCENT); + register_device_value(boiler_data, &boilHystOn_, DeviceValueType::INT, {}, F("boilHystOn"), F("Hysteresis on temperature"), DeviceValueUOM::DEGREES); + register_device_value(boiler_data, &boilHystOff_, DeviceValueType::INT, {}, F("boilHystOff"), F("Hysteresis off temperature"), DeviceValueUOM::DEGREES); + register_device_value(boiler_data, &setFlowTemp_, DeviceValueType::UINT, {}, F("setFlowTemp"), F("Set flow temperature"), DeviceValueUOM::DEGREES); + register_device_value(boiler_data, &setBurnPow_, DeviceValueType::UINT, {}, F("setBurnPow"), F("Burner set power"), DeviceValueUOM::PERCENT); + register_device_value(boiler_data, &curBurnPow_, DeviceValueType::UINT, {}, F("curBurnPow"), F("Burner current power"), DeviceValueUOM::PERCENT); + register_device_value(boiler_data, &burnStarts_, DeviceValueType::ULONG, {}, F("burnStarts"), F("Burner # starts"), DeviceValueUOM::NONE); + register_device_value(boiler_data, &burnWorkMin_, DeviceValueType::TIME, {}, F("burnWorkMin"), F("Total burner operating time"), DeviceValueUOM::MINUTES); + register_device_value(boiler_data, &heatWorkMin_, DeviceValueType::TIME, {}, F("heatWorkMin"), F("Total heat operating time"), DeviceValueUOM::MINUTES); + register_device_value(boiler_data, &UBAuptime_, DeviceValueType::TIME, {}, F("UBAuptime"), F("Total UBA operating time"), DeviceValueUOM::MINUTES); + register_device_value(boiler_data, &lastCode_, DeviceValueType::TEXT, {}, F("lastCode"), F("Last error code"), DeviceValueUOM::NONE); + register_device_value(boiler_data, &serviceCode_, DeviceValueType::TEXT, {}, F("serviceCode"), F("Service code"), DeviceValueUOM::NONE); + register_device_value(boiler_data, &serviceCodeNumber_, DeviceValueType::USHORT, {}, F("serviceCodeNumber"), F("Service code number"), DeviceValueUOM::NONE); + + // info + register_device_value(boiler_data_info, + &upTimeControl_, + DeviceValueType::TIME, + flash_string_vector{F("60")}, + F("upTimeControl"), + F("Operating time total heat"), + DeviceValueUOM::MINUTES); + register_device_value(boiler_data_info, + &upTimeCompHeating_, + DeviceValueType::TIME, + flash_string_vector{F("60")}, + F("upTimeCompHeating"), + F("Operating time compressor heating"), + DeviceValueUOM::MINUTES); + register_device_value(boiler_data, + &upTimeCompCooling_, + DeviceValueType::TIME, + flash_string_vector{F("60")}, + F("upTimeCompCooling"), + F("Operating time compressor cooling"), + DeviceValueUOM::MINUTES); + register_device_value(boiler_data_info, + &upTimeCompWw_, + DeviceValueType::TIME, + flash_string_vector{F("60")}, + F("upTimeCompWw"), + F("Operating time compressor warm water"), + DeviceValueUOM::MINUTES); + register_device_value(boiler_data_info, &heatingStarts_, DeviceValueType::ULONG, {}, F("heatingStarts"), F("# heating starts (control)"), DeviceValueUOM::NONE); + register_device_value(boiler_data_info, &coolingStarts_, DeviceValueType::ULONG, {}, F("coolingStarts"), F("# cooling starts (control)"), DeviceValueUOM::NONE); + register_device_value(boiler_data_info, &nrgConsTotal_, DeviceValueType::ULONG, {}, F("nrgConsTotal"), F("Total energy consumption"), DeviceValueUOM::NONE); + register_device_value(boiler_data_info, + &nrgConsCompTotal_, + DeviceValueType::ULONG, + {}, + F("nrgConsCompTotal"), + F("Energy consumption compressor total"), + DeviceValueUOM::NONE); + register_device_value(boiler_data_info, + &nrgConsCompHeating_, + DeviceValueType::ULONG, + {}, + F("nrgConsCompHeating"), + F("Energy consumption compressor heating"), + DeviceValueUOM::NONE); + register_device_value( + boiler_data_info, &nrgConsCompWw_, DeviceValueType::ULONG, {}, F("nrgConsCompWw"), F("Energy consumption compressor warm water"), DeviceValueUOM::NONE); + register_device_value(boiler_data_info, + &nrgConsCompCooling_, + DeviceValueType::ULONG, + {}, + F("nrgConsCompCooling"), + F("Energy consumption compressor cooling"), + DeviceValueUOM::NONE); + register_device_value(boiler_data_info, &nrgSuppTotal_, DeviceValueType::ULONG, {}, F("nrgSuppTotal"), F("Total energy supplied"), DeviceValueUOM::NONE); + register_device_value( + boiler_data_info, &nrgSuppHeating_, DeviceValueType::ULONG, {}, F("nrgSuppHeating"), F("Total energy supplied heating"), DeviceValueUOM::NONE); + register_device_value(boiler_data_info, &nrgSuppWw_, DeviceValueType::ULONG, {}, F("nrgSuppWw"), F("Total energy warm supplied warm water"), DeviceValueUOM::NONE); + register_device_value( + boiler_data_info, &nrgSuppCooling_, DeviceValueType::ULONG, {}, F("nrgSuppCooling"), F("Total energy supplied cooling"), DeviceValueUOM::NONE); + register_device_value(boiler_data_info, + &auxElecHeatNrgConsTotal_, + DeviceValueType::ULONG, + {}, + F("auxElecHeatNrgConsTotal"), + F("Auxiliary electrical heater energy consumption total"), + DeviceValueUOM::NONE); + register_device_value(boiler_data_info, + &auxElecHeatNrgConsHeating_, + DeviceValueType::ULONG, + {}, + F("auxElecHeatNrgConsHeating"), + F("Auxiliary electrical heater energy consumption heating"), + DeviceValueUOM::NONE); + register_device_value(boiler_data_info, + &auxElecHeatNrgConsDHW_, + DeviceValueType::ULONG, + {}, + F("auxElecHeatNrgConsDHW"), + F("Auxiliary electrical heater energy consumption DHW"), + DeviceValueUOM::NONE); + register_device_value(boiler_data_info, &maintenanceMessage_, DeviceValueType::TEXT, {}, F("maintenanceMessage"), F("Maintenance message"), DeviceValueUOM::NONE); + register_device_value(boiler_data_info, &maintenanceDate_, DeviceValueType::TEXT, {}, F("maintenanceDate"), F("Maintenance set date"), DeviceValueUOM::NONE); + register_device_value(boiler_data_info, + &maintenanceType_, + DeviceValueType::ENUM, + flash_string_vector{F("off"), F("time"), F("date")}, + F("maintenanceType"), + F("Scheduled maintenance"), + DeviceValueUOM::NONE); + register_device_value(boiler_data_info, &maintenanceTime_, DeviceValueType::UINT, {}, F("maintenanceTime"), F("Maintenance set time"), DeviceValueUOM::NONE); +} + +// publish HA config +bool Boiler::publish_ha_config() { + StaticJsonDocument doc; + doc["uniq_id"] = F_(boiler); char stat_t[50]; snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/boiler_data"), System::hostname().c_str()); doc["stat_t"] = stat_t; + doc["name"] = FJSON("Service Code"); doc["val_tpl"] = FJSON("{{value_json.serviceCode}}"); JsonObject dev = doc.createNestedObject("dev"); dev["name"] = FJSON("EMS-ESP Boiler"); @@ -105,788 +339,21 @@ void Boiler::register_mqtt_ha_config() { dev["mdl"] = name(); JsonArray ids = dev.createNestedArray("ids"); ids.add("ems-esp-boiler"); - Mqtt::publish_ha(F("homeassistant/sensor/ems-esp/boiler/config"), doc.as()); // publish the config payload with retain flag - - Mqtt::register_mqtt_ha_binary_sensor(F_(tapwaterActive), device_type(), "tapwater_active"); - Mqtt::register_mqtt_ha_binary_sensor(F_(heatingActive), device_type(), "heating_active"); - - // main - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(serviceCodeNumber), device_type(), "serviceCodeNumber", nullptr, F_(iconpower)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(selFlowTemp), device_type(), "selFlowTemp", F_(degrees), F_(iconcruise)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(selBurnPow), device_type(), "selBurnPow", F_(percent), F_(iconpercent)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(curBurnPow), device_type(), "curBurnPow", F_(percent), F_(iconfire)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(pumpMod), device_type(), "pumpMod", F_(percent), F_(iconpercent)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(pumpMod2), device_type(), "pumpMod2", F_(percent), F_(iconpercent)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(outdoorTemp), device_type(), "outdoorTemp", F_(degrees), F_(iconexport)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(curFlowTemp), device_type(), "curFlowTemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(retTemp), device_type(), "retTemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(switchTemp), device_type(), "switchTemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(sysPress), device_type(), "sysPress", F_(bar), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(boilTemp), device_type(), "boilTemp", F_(degrees), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(burnGas), device_type(), "burnGas", nullptr, F_(iconfire)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(flameCurr), device_type(), "flameCurr", F_(uA), F_(iconflash)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(heatPump), device_type(), "heatPump", nullptr, F_(iconwaterpump)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(fanWork), device_type(), "fanWork", nullptr, F_(iconfan)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(ignWork), device_type(), "ignWork", nullptr, F_(iconflash)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(exhaustTemp), device_type(), "exhaustTemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(heatingActivated), device_type(), "heatingActivated", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(heatingTemp), device_type(), "heatingTemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(pumpModMax), device_type(), "pumpModMax", F_(percent), F_(iconpercent)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(pumpModMin), device_type(), "pumpModMin", F_(percent), F_(iconpercent)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(pumpDelay), device_type(), "pumpDelay", F_(min), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(burnMinPeriod), device_type(), "burnMinPeriod", F_(min), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(burnMinPower), device_type(), "burnMinPower", F_(percent), F_(iconpercent)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(burnMaxPower), device_type(), "burnMaxPower", F_(percent), F_(iconpercent)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(boilHystOn), device_type(), "boilHystOn", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(boilHystOff), device_type(), "boilHystOff", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(setFlowTemp), device_type(), "setFlowTemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(setBurnPow), device_type(), "setBurnPow", F_(percent), F_(iconpercent)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(burnStarts), device_type(), "burnStarts", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(burnWorkMin), device_type(), "burnWorkMin", F_(min), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(heatWorkMin), device_type(), "heatWorkMin", F_(min), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(UBAuptime), device_type(), "UBAuptime", F_(min), nullptr); - // information - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(upTimeControl), device_type(), "upTimeControl", F_(min), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(upTimeCompHeating), device_type(), "upTimeCompHeating", F_(min), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(upTimeCompCooling), device_type(), "upTimeCompCooling", F_(min), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(upTimeCompWw), device_type(), "upTimeCompWw", F_(min), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(heatingStarts), device_type(), "heatingStarts", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(coolingStarts), device_type(), "coolingStarts_", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(wWStarts2), device_type(), "wWStarts2", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(nrgConsTotal), device_type(), "nrgConsTotal", F_(kwh), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(auxElecHeatNrgConsTotal), device_type(), "auxElecHeatNrgConsTotal_", F_(kwh), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(auxElecHeatNrgConsHeating), device_type(), "auxElecHeatNrgConsHeating", F_(kwh), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(auxElecHeatNrgConsDHW), device_type(), "auxElecHeatNrgConsDHW", F_(kwh), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(nrgConsCompTotal), device_type(), "nrgConsCompTotal", F_(kwh), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(nrgConsCompHeating), device_type(), "nrgConsCompHeating", F_(kwh), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(nrgConsCompWw), device_type(), "nrgConsCompWw", F_(kwh), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(nrgConsCompCooling), device_type(), "nrgConsCompCooling", F_(kwh), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(nrgSuppTotal), device_type(), "nrgSuppTotal_", F_(kwh), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(nrgSuppHeating), device_type(), "nrgSuppHeating", F_(kwh), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(nrgSuppWw), device_type(), "nrgSuppWw", F_(kwh), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(nrgSuppCooling), device_type(), "nrgSuppCooling", F_(kwh), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(maintenanceMessage), device_type(), "maintenanceMessage", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(maintenance), device_type(), "maintenance", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(maintenanceTime), device_type(), "maintenanceTime", F_(hours), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(maintenanceDate), device_type(), "maintenanceDate", nullptr, nullptr); - mqtt_ha_config_ = true; // done -} - -// create the config topics for Home Assistant MQTT Discovery -// for each of the ww elements -void Boiler::register_mqtt_ha_config_ww() { - if (!Mqtt::connected()) { - return; - } - // ww - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWSelTemp), device_type(), "wWSelTemp", F_(degrees), F_(iconcruise)); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWSetTemp), device_type(), "wWSetTemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWDisinfectionTemp), device_type(), "wWDisinfectionTemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWType), device_type(), "wWType", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWChargeType), device_type(), "wWChargeType", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWCircPump), device_type(), "wWCircPump", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWCircPumpMode), device_type(), "wWCircPumpMode", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWCirc), device_type(), "wWCirc", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWCurTemp), device_type(), "wWCurTemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWCurTemp2), device_type(), "wWCurTemp2", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWCurFlow), device_type(), "wWCurFlow", F("l/min"), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWHeat), device_type(), "wWHeat", nullptr, F_(iconvalve)); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wwStorageTemp1), device_type(), "wwStorageTemp1", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wwStorageTemp2), device_type(), "wwStorageTemp2", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWActivated), device_type(), "wWActivated", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWOneTime), device_type(), "wWOneTime", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWDisinfecting), device_type(), "wWDisinfecting", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWCharging), device_type(), "wWCharging", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWRecharging), device_type(), "wWRecharging", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWTempOK), device_type(), "wWTempOK", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWActive), device_type(), "wWActive", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWSetPumpPower), device_type(), "wWSetPumpPower", F_(percent), F_(iconwaterpump)); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wwMixTemperature), device_type(), "wwMixTemperature", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wwBufferTemperature), device_type(), "wwBufferTemperature", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWStarts), device_type(), "wWStarts", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWWorkM), device_type(), "wWWorkM", F_(min), nullptr); - - mqtt_ha_config_ww_ = true; // done -} - -// send stuff to the Web UI -void Boiler::device_info_web(JsonArray & root) { - // fetch the values into a JSON document - StaticJsonDocument doc; - JsonObject json = doc.to(); - if (!export_values_main(json, true)) { - return; // empty - } + char topic[100]; + snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/boiler/config"), System::hostname().c_str()); + Mqtt::publish_ha(topic, doc.as()); // publish the config payload with retain flag - create_value_json(root, F("heatingActive"), nullptr, F_(heatingActive), nullptr, json); - create_value_json(root, F("tapwaterActive"), nullptr, F_(tapwaterActive), nullptr, json); - create_value_json(root, F("serviceCode"), nullptr, F_(serviceCode), nullptr, json); - create_value_json(root, F("serviceCodeNumber"), nullptr, F_(serviceCodeNumber), nullptr, json); - create_value_json(root, F("lastCode"), nullptr, F_(lastCode), nullptr, json); - create_value_json(root, F("selFlowTemp"), nullptr, F_(selFlowTemp), F_(degrees), json); - create_value_json(root, F("selBurnPow"), nullptr, F_(selBurnPow), F_(percent), json); - create_value_json(root, F("curBurnPow"), nullptr, F_(curBurnPow), F_(percent), json); - create_value_json(root, F("pumpMod"), nullptr, F_(pumpMod), F_(percent), json); - create_value_json(root, F("pumpMod2"), nullptr, F_(pumpMod2), F_(percent), json); - create_value_json(root, F("outdoorTemp"), nullptr, F_(outdoorTemp), F_(degrees), json); - create_value_json(root, F("curFlowTemp"), nullptr, F_(curFlowTemp), F_(degrees), json); - create_value_json(root, F("retTemp"), nullptr, F_(retTemp), F_(degrees), json); - create_value_json(root, F("switchTemp"), nullptr, F_(switchTemp), F_(degrees), json); - create_value_json(root, F("sysPress"), nullptr, F_(sysPress), nullptr, json); - create_value_json(root, F("boilTemp"), nullptr, F_(boilTemp), F_(degrees), json); - create_value_json(root, F("burnGas"), nullptr, F_(burnGas), nullptr, json); - create_value_json(root, F("flameCurr"), nullptr, F_(flameCurr), F_(uA), json); - create_value_json(root, F("heatPump"), nullptr, F_(heatPump), nullptr, json); - create_value_json(root, F("fanWork"), nullptr, F_(fanWork), nullptr, json); - create_value_json(root, F("ignWork"), nullptr, F_(ignWork), nullptr, json); - create_value_json(root, F("heatingActivated"), nullptr, F_(heatingActivated), nullptr, json); - create_value_json(root, F("heatingTemp"), nullptr, F_(heatingTemp), F_(degrees), json); - create_value_json(root, F("pumpModMax"), nullptr, F_(pumpModMax), F_(percent), json); - create_value_json(root, F("pumpModMin"), nullptr, F_(pumpModMin), F_(percent), json); - create_value_json(root, F("pumpDelay"), nullptr, F_(pumpDelay), F_(min), json); - create_value_json(root, F("burnMinPeriod"), nullptr, F_(burnMinPeriod), F_(min), json); - create_value_json(root, F("burnMinPower"), nullptr, F_(burnMinPower), F_(percent), json); - create_value_json(root, F("burnMaxPower"), nullptr, F_(burnMaxPower), F_(percent), json); - create_value_json(root, F("boilHystOn"), nullptr, F_(boilHystOn), F_(degrees), json); - create_value_json(root, F("boilHystOff"), nullptr, F_(boilHystOff), F_(degrees), json); - create_value_json(root, F("setFlowTemp"), nullptr, F_(setFlowTemp), F_(degrees), json); - create_value_json(root, F("setBurnPow"), nullptr, F_(setBurnPow), F_(percent), json); - create_value_json(root, F("burnStarts"), nullptr, F_(burnStarts), nullptr, json); - create_value_json(root, F("burnWorkMin"), nullptr, F_(burnWorkMin), nullptr, json); - create_value_json(root, F("heatWorkMin"), nullptr, F_(heatWorkMin), nullptr, json); - create_value_json(root, F("UBAuptime"), nullptr, F_(UBAuptime), nullptr, json); - - // information - create_value_json(root, F("upTimeControl"), nullptr, F_(upTimeControl), nullptr, json); - create_value_json(root, F("upTimeCompHeating"), nullptr, F_(upTimeCompHeating), nullptr, json); - create_value_json(root, F("upTimeCompCooling"), nullptr, F_(upTimeCompCooling), nullptr, json); - create_value_json(root, F("upTimeCompWw"), nullptr, F_(upTimeCompWw), nullptr, json); - create_value_json(root, F("heatingStarts"), nullptr, F_(heatingStarts), nullptr, json); - create_value_json(root, F("coolingStarts"), nullptr, F_(coolingStarts), nullptr, json); - create_value_json(root, F("wWStarts2"), nullptr, F_(wWStarts2), nullptr, json); - create_value_json(root, F("nrgConsTotal"), nullptr, F_(nrgConsTotal), F_(kwh), json); - create_value_json(root, F("auxElecHeatNrgConsTotal"), nullptr, F_(auxElecHeatNrgConsTotal), F_(kwh), json); - create_value_json(root, F("auxElecHeatNrgConsHeating"), nullptr, F_(auxElecHeatNrgConsHeating), F_(kwh), json); - create_value_json(root, F("auxElecHeatNrgConsDHW"), nullptr, F_(auxElecHeatNrgConsDHW), F_(kwh), json); - create_value_json(root, F("nrgConsCompTotal"), nullptr, F_(nrgConsCompTotal), F_(kwh), json); - create_value_json(root, F("nrgConsCompHeating"), nullptr, F_(nrgConsCompHeating), F_(kwh), json); - create_value_json(root, F("nrgConsCompWw"), nullptr, F_(nrgConsCompWw), F_(kwh), json); - create_value_json(root, F("nrgConsCoolingTotal"), nullptr, F_(nrgConsCompCooling), F_(kwh), json); - create_value_json(root, F("nrgSuppTotal"), nullptr, F_(nrgSuppTotal), F_(kwh), json); - create_value_json(root, F("nrgSuppHeating"), nullptr, F_(nrgSuppHeating), F_(kwh), json); - create_value_json(root, F("nrgSuppWw"), nullptr, F_(nrgSuppWw), F_(kwh), json); - create_value_json(root, F("nrgSuppCooling"), nullptr, F_(nrgSuppCooling), F_(kwh), json); - create_value_json(root, F("maintenanceMessage"), nullptr, F_(maintenanceMessage), nullptr, json); - create_value_json(root, F("maintenance"), nullptr, F_(maintenance), nullptr, json); - create_value_json(root, F("maintenanceTime"), nullptr, F_(maintenanceTime), F_(hours), json); - create_value_json(root, F("maintenanceDate"), nullptr, F_(maintenanceDate), nullptr, json); - - doc.clear(); - if (!export_values_ww(json, true)) { // append ww values - return; - } - - // ww - create_value_json(root, F("wWSelTemp"), nullptr, F_(wWSelTemp), F_(degrees), json); - create_value_json(root, F("wWSetTemp"), nullptr, F_(wWSetTemp), F_(degrees), json); - create_value_json(root, F("wWDisinfectionTemp"), nullptr, F_(wWDisinfectionTemp), F_(degrees), json); - create_value_json(root, F("wWType"), nullptr, F_(wWType), nullptr, json); - create_value_json(root, F("wWChargeType"), nullptr, F_(wWChargeType), nullptr, json); - create_value_json(root, F("wWCircPump"), nullptr, F_(wWCircPump), nullptr, json); - create_value_json(root, F("wWCircPumpMode"), nullptr, F_(wWCircPumpMode), nullptr, json); - create_value_json(root, F("wWCirc"), nullptr, F_(wWCirc), nullptr, json); - create_value_json(root, F("wWCurTemp"), nullptr, F_(wWCurTemp), F_(degrees), json); - create_value_json(root, F("wWCurTemp2"), nullptr, F_(wWCurTemp2), F_(degrees), json); - create_value_json(root, F("wWCurFlow"), nullptr, F_(wWCurFlow), F("l/min"), json); - create_value_json(root, F("wwStorageTemp1"), nullptr, F_(wwStorageTemp1), F_(degrees), json); - create_value_json(root, F("wwStorageTemp2"), nullptr, F_(wwStorageTemp2), F_(degrees), json); - create_value_json(root, F("exhaustTemp"), nullptr, F_(exhaustTemp), F_(degrees), json); - create_value_json(root, F("wWActivated"), nullptr, F_(wWActivated), nullptr, json); - create_value_json(root, F("wWOneTime"), nullptr, F_(wWOneTime), nullptr, json); - create_value_json(root, F("wWDisinfecting"), nullptr, F_(wWDisinfecting), nullptr, json); - create_value_json(root, F("wWCharging"), nullptr, F_(wWCharging), nullptr, json); - create_value_json(root, F("wWRecharging"), nullptr, F_(wWRecharging), nullptr, json); - create_value_json(root, F("wWTempOK"), nullptr, F_(wWTempOK), nullptr, json); - create_value_json(root, F("wWActive"), nullptr, F_(wWActive), nullptr, json); - create_value_json(root, F("wWHeat"), nullptr, F_(wWHeat), nullptr, json); - create_value_json(root, F("wWSetPumpPower"), nullptr, F_(wWSetPumpPower), F_(percent), json); - create_value_json(root, F("wwMixTemperature"), nullptr, F_(wwMixTemperature), F_(degrees), json); - create_value_json(root, F("wwBufferTemperature"), nullptr, F_(wwBufferTemperature), F_(degrees), json); - create_value_json(root, F("wWStarts"), nullptr, F_(wWStarts), nullptr, json); - create_value_json(root, F("wWWorkM"), nullptr, F_(wWWorkM), nullptr, json); -} - -bool Boiler::export_values(JsonObject & json) { - if (!export_values_main(json)) { - return false; - } - export_values_ww(json); // append ww values return true; } -// creates JSON doc from values -// returns false if empty -bool Boiler::export_values_ww(JsonObject & json, const bool textformat) { - char s[10]; // for formatting strings - - // Warm Water comfort setting - if (Helpers::hasValue(wWComfort_)) { - if (wWComfort_ == 0x00) { - json["wWComfort"] = FJSON("Hot"); - } else if (wWComfort_ == 0xD8) { - json["wWComfort"] = FJSON("Eco"); - } else if (wWComfort_ == 0xEC) { - json["wWComfort"] = FJSON("Intelligent"); - } - } - - // Warm Water selected temperature - if (Helpers::hasValue(wWSelTemp_)) { - json["wWSelTemp"] = wWSelTemp_; - } - - // Warm Water set temperature - if (Helpers::hasValue(wWSetTemp_)) { - json["wWSetTemp"] = wWSetTemp_; - } - - // Warm Water disinfection temperature - if (Helpers::hasValue(wWDisinfectionTemp_)) { - json["wWDisinfectionTemp"] = wWDisinfectionTemp_; - } - - // Warm Water type - if (wWType_ == 0) { // no json if not set - json["wWType"] = FJSON("off"); - } else if (wWType_ == 1) { - json["wWType"] = FJSON("flow"); - } else if (wWType_ == 2) { - json["wWType"] = FJSON("buffered flow"); - } else if (wWType_ == 3) { - json["wWType"] = FJSON("buffer"); - } else if (wWType_ == 4) { - json["wWType"] = FJSON("layered buffer"); - } - - // Warm Water charging type - if (Helpers::hasValue(wWChargeType_, EMS_VALUE_BOOL)) { - json["wWChargeType"] = wWChargeType_ ? FJSON("3-way valve") : FJSON("charge pump"); - } - - // Warm Water circulation pump available bool - if (Helpers::hasValue(wWCircPump_, EMS_VALUE_BOOL)) { - json["wWCircPump"] = Helpers::render_value(s, wWCircPump_, EMS_VALUE_BOOL); - } - - // Warm Water circulation pump freq - if (Helpers::hasValue(wWCircPumpMode_)) { - if (wWCircPumpMode_ == 7) { - json["wWCircPumpMode"] = FJSON("continuous"); - } else { - char s[7]; - char buffer[2]; - buffer[0] = (wWCircPumpMode_ % 10) + '0'; - buffer[1] = '\0'; - strlcpy(s, buffer, 7); - strlcat(s, "x3min", 7); - json["wWCircPumpMode"] = s; - } - } - - // Warm Water circulation active bool - if (Helpers::hasValue(wWCirc_, EMS_VALUE_BOOL)) { - json["wWCirc"] = Helpers::render_value(s, wWCirc_, EMS_VALUE_BOOL); - } - - // Warm Water current temperature (intern) - if (Helpers::hasValue(wWCurTemp_)) { - json["wWCurTemp"] = (float)wWCurTemp_ / 10; - } - - // Warm Water current temperature (extern) - if (Helpers::hasValue(wWCurTemp2_)) { - json["wWCurTemp2"] = (float)wWCurTemp2_ / 10; - } - - // Warm Water current tap water flow l/min - if (Helpers::hasValue(wWCurFlow_)) { - json["wWCurFlow"] = (float)wWCurFlow_ / 10; - } - - // Warm water storage temperature (intern) - if (Helpers::hasValue(wwStorageTemp1_)) { - json["wwStorageTemp1"] = (float)wwStorageTemp1_ / 10; - } - - // Warm water storage temperature (extern) - if (Helpers::hasValue(wwStorageTemp2_)) { - json["wwStorageTemp2"] = (float)wwStorageTemp2_ / 10; - } - - // Warm Water activated bool - if (Helpers::hasValue(wWActivated_, EMS_VALUE_BOOL)) { - json["wWActivated"] = Helpers::render_value(s, wWActivated_, EMS_VALUE_BOOL); - } - - // Warm Water one time charging bool - if (Helpers::hasValue(wWOneTime_, EMS_VALUE_BOOL)) { - json["wWOneTime"] = Helpers::render_value(s, wWOneTime_, EMS_VALUE_BOOL); - } - - // Warm Water disinfecting bool - if (Helpers::hasValue(wWDisinfecting_, EMS_VALUE_BOOL)) { - json["wWDisinfecting"] = Helpers::render_value(s, wWDisinfecting_, EMS_VALUE_BOOL); - } - - // Warm water charging bool - if (Helpers::hasValue(wWCharging_, EMS_VALUE_BOOL)) { - json["wWCharging"] = Helpers::render_value(s, wWCharging_, EMS_VALUE_BOOL); - } - - // Warm water recharge bool - if (Helpers::hasValue(wWRecharging_, EMS_VALUE_BOOL)) { - json["wWRecharging"] = Helpers::render_value(s, wWRecharging_, EMS_VALUE_BOOL); - } - - // Warm water temperature ok bool - if (Helpers::hasValue(wWTempOK_, EMS_VALUE_BOOL)) { - json["wWTempOK"] = Helpers::render_value(s, wWTempOK_, EMS_VALUE_BOOL); - } - - // Warm water active bool - if (Helpers::hasValue(wWActive_, EMS_VALUE_BOOL)) { - json["wWActive"] = Helpers::render_value(s, wWActive_, EMS_VALUE_BOOL); - } - - // Warm Water charging bool - if (Helpers::hasValue(wWHeat_, EMS_VALUE_BOOL)) { - json["wWHeat"] = Helpers::render_value(s, wWHeat_, EMS_VALUE_BOOL); - } - - // Warm Water pump set power % - if (Helpers::hasValue(wWSetPumpPower_)) { - json["wWSetPumpPower"] = wWSetPumpPower_; - } - - // Warm water mix temperature - if (Helpers::hasValue(wwMixTemperature_)) { - json["wwMixTemperature"] = wwMixTemperature_; - } - - // Warm water buffer boiler temperature - if (Helpers::hasValue(wwBufferTemperature_)) { - json["wwBufferTemperature"] = wwBufferTemperature_; - } - - // Warm Water # starts - if (Helpers::hasValue(wWStarts_)) { - json["wWStarts"] = wWStarts_; - } - - // Warm Water active time - if (Helpers::hasValue(wWWorkM_)) { - if (textformat) { - char slong[40]; - json["wWWorkM"] = Helpers::render_value(slong, wWWorkM_, EMS_VALUE_TIME); // Warm Water active time (full text) - } else { - json["wWWorkM"] = wWWorkM_; - } - } - - return (json.size()); -} - -// creates JSON doc from values -// returns false if empty -bool Boiler::export_values_main(JsonObject & json, const bool textformat) { - char s[10]; // for formatting strings - - // Hot tap water bool - if (Helpers::hasValue(heatingActive_, EMS_VALUE_BOOL)) { - json["heatingActive"] = Helpers::render_value(s, heatingActive_, EMS_VALUE_BOOL); - } - - // Central heating bool - if (Helpers::hasValue(tapwaterActive_, EMS_VALUE_BOOL)) { - json["tapwaterActive"] = Helpers::render_value(s, tapwaterActive_, EMS_VALUE_BOOL); - } - - // Selected flow temperature deg - if (Helpers::hasValue(selFlowTemp_)) { - json["selFlowTemp"] = selFlowTemp_; - } - - // Burner selected max power % - if (Helpers::hasValue(selBurnPow_)) { - json["selBurnPow"] = selBurnPow_; - } - - // Burner current power % - if (Helpers::hasValue(curBurnPow_)) { - json["curBurnPow"] = curBurnPow_; - } - - // Pump modulation % - if (Helpers::hasValue(pumpMod_)) { - json["pumpMod"] = pumpMod_; - } - - // Heat Pump modulation % - if (Helpers::hasValue(pumpMod2_)) { - json["pumpMod2"] = pumpMod2_; - } - - // Outside temperature - if (Helpers::hasValue(outdoorTemp_)) { - json["outdoorTemp"] = (float)outdoorTemp_ / 10; - } - - // Current flow temperature - if (Helpers::hasValue(curFlowTemp_)) { - json["curFlowTemp"] = (float)curFlowTemp_ / 10; - } - - // Return temperature - if (Helpers::hasValue(retTemp_)) { - json["retTemp"] = (float)retTemp_ / 10; - } - - // Mixing switch temperature - if (Helpers::hasValue(switchTemp_)) { - json["switchTemp"] = (float)switchTemp_ / 10; - } - - // System pressure - if (Helpers::hasValue(sysPress_)) { - json["sysPress"] = (float)sysPress_ / 10; - } - - // Max boiler temperature - if (Helpers::hasValue(boilTemp_)) { - json["boilTemp"] = (float)boilTemp_ / 10; - } - - // Exhaust temperature - if (Helpers::hasValue(exhaustTemp_)) { - json["exhaustTemp"] = (float)exhaustTemp_ / 10; - } - - // Gas bool - if (Helpers::hasValue(burnGas_, EMS_VALUE_BOOL)) { - json["burnGas"] = Helpers::render_value(s, burnGas_, EMS_VALUE_BOOL); - } - - // Flame current uA - if (Helpers::hasValue(flameCurr_)) { - json["flameCurr"] = (float)(int16_t)flameCurr_ / 10; - } - - // Boiler pump bool - if (Helpers::hasValue(heatPump_, EMS_VALUE_BOOL)) { - json["heatPump"] = Helpers::render_value(s, heatPump_, EMS_VALUE_BOOL); - } - - // Fan bool - if (Helpers::hasValue(fanWork_, EMS_VALUE_BOOL)) { - json["fanWork"] = Helpers::render_value(s, fanWork_, EMS_VALUE_BOOL); - } - - // Ignition bool - if (Helpers::hasValue(ignWork_, EMS_VALUE_BOOL)) { - json["ignWork"] = Helpers::render_value(s, ignWork_, EMS_VALUE_BOOL); - } - - // heating activated bool - if (Helpers::hasValue(heatingActivated_, EMS_VALUE_BOOL)) { - json["heatingActivated"] = Helpers::render_value(s, heatingActivated_, EMS_VALUE_BOOL); - } - - // Heating temperature setting on the boiler - if (Helpers::hasValue(heatingTemp_)) { - json["heatingTemp"] = heatingTemp_; - } - - // Boiler circuit pump modulation max power % - if (Helpers::hasValue(pumpModMax_)) { - json["pumpModMax"] = pumpModMax_; - } - - // Boiler circuit pump modulation min power % - if (Helpers::hasValue(pumpModMin_)) { - json["pumpModMin"] = pumpModMin_; - } - - // Boiler circuit pump delay time min - if (Helpers::hasValue(pumpDelay_)) { - json["pumpDelay"] = pumpDelay_; - } - - // Boiler burner min period min - if (Helpers::hasValue(burnMinPeriod_)) { - json["burnMinPeriod"] = burnMinPeriod_; - } - - // Boiler burner min power % - if (Helpers::hasValue(burnMinPower_)) { - json["burnMinPower"] = burnMinPower_; - } - - // Boiler burner max power % - if (Helpers::hasValue(burnMaxPower_)) { - json["burnMaxPower"] = burnMaxPower_; - } - - // Boiler temp hysteresis on degrees - if (Helpers::hasValue(boilHystOn_)) { - json["boilHystOn"] = boilHystOn_; - } - - // Boiler temp hysteresis off degrees - if (Helpers::hasValue(boilHystOff_)) { - json["boilHystOff"] = boilHystOff_; - } - - // Set Flow temperature - if (Helpers::hasValue(setFlowTemp_)) { - json["setFlowTemp"] = setFlowTemp_; - } - - // burn power % - if (Helpers::hasValue(setBurnPow_)) { - json["setBurnPow"] = setBurnPow_; - } - - // Burner # starts - if (Helpers::hasValue(burnStarts_)) { - json["burnStarts"] = burnStarts_; - } - - // Total burner operating time - if (Helpers::hasValue(burnWorkMin_)) { - if (textformat) { - char slong[40]; - json["burnWorkMin"] = Helpers::render_value(slong, burnWorkMin_, EMS_VALUE_TIME); - } else { - json["burnWorkMin"] = burnWorkMin_; - } - } - - // Total heat operating time - if (Helpers::hasValue(heatWorkMin_)) { - if (textformat) { - char slong[40]; - json["heatWorkMin"] = Helpers::render_value(slong, heatWorkMin_, EMS_VALUE_TIME); - } else { - json["heatWorkMin"] = heatWorkMin_; - } - } - - // Total UBA working time - if (Helpers::hasValue(UBAuptime_)) { - if (textformat) { - char slong[40]; - json["UBAuptime"] = Helpers::render_value(slong, UBAuptime_, EMS_VALUE_TIME); - } else { - json["UBAuptime"] = UBAuptime_; - } - } - - // Service Code & Service Code Number - if (Helpers::hasValue(serviceCodeNumber_)) { - json["serviceCode"] = serviceCode_; - json["serviceCodeNumber"] = serviceCodeNumber_; - } - - if (lastCode_[0] != '\0') { - json["lastCode"] = lastCode_; - } - - // Total heat operating time - if (Helpers::hasValue(upTimeControl_)) { - if (textformat) { - char slong[40]; - json["upTimeControl"] = Helpers::render_value(slong, upTimeControl_ / 60, EMS_VALUE_TIME); - } else { - json["upTimeControl"] = upTimeControl_ / 60; - } - } - - // Operating time compressor heating - if (Helpers::hasValue(upTimeCompHeating_)) { - if (textformat) { - char slong[40]; - json["upTimeCompHeating"] = Helpers::render_value(slong, upTimeCompHeating_ / 60, EMS_VALUE_TIME); - } else { - json["upTimeCompHeating"] = upTimeCompHeating_ / 60; - } - } - - // Operating time compressor cooling - if (Helpers::hasValue(upTimeCompCooling_)) { - if (textformat) { - char slong[40]; - json["upTimeCompCooling"] = Helpers::render_value(slong, upTimeCompCooling_ / 60, EMS_VALUE_TIME); - } else { - json["upTimeCompCooling"] = upTimeCompCooling_ / 60; - } - } - - // Operating time compressor warm water - if (Helpers::hasValue(upTimeCompWw_)) { - if (textformat) { - char slong[40]; - json["upTimeCompWw"] = Helpers::render_value(slong, upTimeCompWw_ / 60, EMS_VALUE_TIME); - } else { - json["upTimeCompWw"] = upTimeCompWw_ / 60; - } - } - - // Number of heating starts - if (Helpers::hasValue(heatingStarts_)) { - json["heatingStarts"] = heatingStarts_; - } - - // Number of cooling starts - if (Helpers::hasValue(coolingStarts_)) { - json["coolingStarts"] = coolingStarts_; - } - - // Number of warm water starts - if (Helpers::hasValue(wWStarts2_)) { - json["wWStarts2"] = wWStarts2_; - } - - // Total energy consumption - if (Helpers::hasValue(nrgConsTotal_)) { - json["nrgConsTotal"] = nrgConsTotal_; - } - - // Auxiliary electrical heater energy total - if (Helpers::hasValue(auxElecHeatNrgConsTotal_)) { - json["auxElecHeatNrgConsTotal"] = auxElecHeatNrgConsTotal_; - } - - // Auxiliary electrical heater energy heating - if (Helpers::hasValue(auxElecHeatNrgConsHeating_)) { - json["auxElecHeatNrgConsHeating"] = auxElecHeatNrgConsHeating_; - } - - // Auxiliary electrical heater energy DHW - if (Helpers::hasValue(auxElecHeatNrgConsDHW_)) { - json["auxElecHeatNrgConsDHW"] = auxElecHeatNrgConsDHW_; - } - - // Energy consumption compressor total - if (Helpers::hasValue(nrgConsCompTotal_)) { - json["nrgConsCompTotal"] = nrgConsCompTotal_; - } - - // Energy consumption compressor heating - if (Helpers::hasValue(nrgConsCompHeating_)) { - json["nrgConsCompHeating"] = nrgConsCompHeating_; - } - - // Energy consumption compressor warm water - if (Helpers::hasValue(nrgConsCompWw_)) { - json["nrgConsCompWw"] = nrgConsCompWw_; - } - - // Energy consumption compressor cooling - if (Helpers::hasValue(nrgConsCompCooling_)) { - json["nrgConsCompCooling"] = nrgConsCompCooling_; - } - - // Total energy supplied - if (Helpers::hasValue(nrgSuppTotal_)) { - json["nrgSuppTotal"] = nrgSuppTotal_; - } - - // Total energy heating - if (Helpers::hasValue(nrgSuppHeating_)) { - json["nrgSuppHeating"] = nrgSuppHeating_; - } - - // Total energy warm water - if (Helpers::hasValue(nrgSuppWw_)) { - json["nrgSuppWw"] = nrgSuppWw_; - } - - // Total energy cooling - if (Helpers::hasValue(nrgSuppCooling_)) { - json["nrgSuppCooling"] = nrgSuppCooling_; - } - - if (Helpers::hasValue(maintenanceMessage_) && maintenanceMessage_ > 0) { - char s[5]; - snprintf_P(s, sizeof(s), PSTR("H%02d"), maintenanceMessage_); - json["maintenanceMessage"] = s; - } - - if (Helpers::hasValue(maintenanceType_)) { - if (maintenanceType_ == 0) { - json["maintenance"] = FJSON("off"); - } else if (maintenanceType_ == 1) { - json["maintenanceTime"] = maintenanceTime_ * 100; - } else if (maintenanceType_ == 2) { - json["maintenanceDate"] = maintenanceDate_; - } - } - - return (json.size()); -} // namespace emsesp - -// publish values via MQTT -void Boiler::publish_values(JsonObject & json, bool force) { - // handle HA first - if (Mqtt::mqtt_format() == Mqtt::Format::HA) { - if (force) { - mqtt_ha_config_ = false; - mqtt_ha_config_ww_ = false; - } - // register ww in next cycle if both unregistered - if (!mqtt_ha_config_) { - register_mqtt_ha_config(); - return; - } else if (!mqtt_ha_config_ww_) { - register_mqtt_ha_config_ww(); - return; - } - } - - StaticJsonDocument doc; - JsonObject json_data = doc.to(); - if (export_values_main(json_data)) { - Mqtt::publish(F("boiler_data"), json_data); - } - json_data.clear(); - - if (export_values_ww(json_data)) { - Mqtt::publish(F("boiler_data_ww"), json_data); - } - - // send out heating and tapwater status - check_active(force); -} - -// called after a process command is called, to check values and see if we need to force an MQTT publish -bool Boiler::updated_values() { - if (changed_) { - changed_ = false; - return true; - } - return false; -} - -/* - * Check if hot tap water or heating is active - * If a value has changed, post it immediately to MQTT so we get real time data - * Values will always be posted first time as heatingActive_ and tapwaterActive_ will have values EMS_VALUE_BOOL_NOTSET - */ +// Check if hot tap water or heating is active +// Values will always be posted first time as heatingActive_ and tapwaterActive_ will have values EMS_VALUE_BOOL_NOTSET void Boiler::check_active(const bool force) { if (!Helpers::hasValue(boilerState_)) { return; } + bool b; uint8_t val; @@ -912,52 +379,61 @@ void Boiler::check_active(const bool force) { // 0x33 void Boiler::process_UBAParameterWW(std::shared_ptr telegram) { - changed_ |= telegram->read_value(wWActivated_, 1); // 0xFF means on - changed_ |= telegram->read_value(wWCircPump_, 6); // 0xFF means on - changed_ |= telegram->read_value(wWCircPumpMode_, 7); // 1=1x3min... 6=6x3min, 7=continuous - changed_ |= telegram->read_value(wWChargeType_, 10); // 0 = charge pump, 0xff = 3-way valve - changed_ |= telegram->read_value(wWSelTemp_, 2); - changed_ |= telegram->read_value(wWDisinfectionTemp_, 8); - changed_ |= telegram->read_value(wWComfort_, 9); + has_update(telegram->read_value(wWActivated_, 1)); // 0xFF means on + has_update(telegram->read_value(wWCircPump_, 6)); // 0xFF means on + has_update(telegram->read_value(wWCircPumpMode_, 7)); // 1=1x3min... 6=6x3min, 7=continuous + has_update(telegram->read_value(wWChargeType_, 10)); // 0 = charge pump, 0xff = 3-way valve + has_update(telegram->read_value(wWSelTemp_, 2)); + has_update(telegram->read_value(wWDisinfectionTemp_, 8)); + + telegram->read_value(wWComfort_, 9); + if (wWComfort_ == 0x00) { + wWComfort_ = 0; // Hot + } else if (wWComfort_ == 0xD8) { + wWComfort_ = 1; // Eco + } else if (wWComfort_ == 0xEC) { + wWComfort_ = 2; // Intelligent + } else { + wWComfort_ = EMS_VALUE_UINT_NOTSET; + } } // 0x18 void Boiler::process_UBAMonitorFast(std::shared_ptr telegram) { - changed_ |= telegram->read_value(selFlowTemp_, 0); - changed_ |= telegram->read_value(curFlowTemp_, 1); - changed_ |= telegram->read_value(selBurnPow_, 3); // burn power max setting - changed_ |= telegram->read_value(curBurnPow_, 4); - changed_ |= telegram->read_value(boilerState_, 5); - - changed_ |= telegram->read_bitvalue(burnGas_, 7, 0); - changed_ |= telegram->read_bitvalue(fanWork_, 7, 2); - changed_ |= telegram->read_bitvalue(ignWork_, 7, 3); - changed_ |= telegram->read_bitvalue(heatPump_, 7, 5); - changed_ |= telegram->read_bitvalue(wWHeat_, 7, 6); - changed_ |= telegram->read_bitvalue(wWCirc_, 7, 7); + has_update(telegram->read_value(selFlowTemp_, 0)); + has_update(telegram->read_value(curFlowTemp_, 1)); + has_update(telegram->read_value(selBurnPow_, 3)); // burn power max setting + has_update(telegram->read_value(curBurnPow_, 4)); + has_update(telegram->read_value(boilerState_, 5)); + + has_update(telegram->read_bitvalue(burnGas_, 7, 0)); + has_update(telegram->read_bitvalue(fanWork_, 7, 2)); + has_update(telegram->read_bitvalue(ignWork_, 7, 3)); + has_update(telegram->read_bitvalue(heatPump_, 7, 5)); + has_update(telegram->read_bitvalue(wWHeat_, 7, 6)); + has_update(telegram->read_bitvalue(wWCirc_, 7, 7)); // warm water storage sensors (if present) // wwStorageTemp2 is also used by some brands as the boiler temperature - see https://github.com/proddy/EMS-ESP/issues/206 - changed_ |= telegram->read_value(wwStorageTemp1_, 9); // 0x8300 if not available - changed_ |= telegram->read_value(wwStorageTemp2_, 11); // 0x8000 if not available - this is boiler temp + has_update(telegram->read_value(wwStorageTemp1_, 9)); // 0x8300 if not available + has_update(telegram->read_value(wwStorageTemp2_, 11)); // 0x8000 if not available - this is boiler temp - changed_ |= telegram->read_value(retTemp_, 13); - changed_ |= telegram->read_value(flameCurr_, 15); + has_update(telegram->read_value(retTemp_, 13)); + has_update(telegram->read_value(flameCurr_, 15)); // system pressure. FF means missing - changed_ |= telegram->read_value(sysPress_, 17); // is *10 + has_update(telegram->read_value(sysPress_, 17)); // is *10 // read the service code / installation status as appears on the display if ((telegram->message_length > 18) && (telegram->offset == 0)) { - changed_ |= telegram->read_value(serviceCode_[0], 18); - changed_ |= telegram->read_value(serviceCode_[1], 19); + has_update(telegram->read_value(serviceCode_[0], 18)); + has_update(telegram->read_value(serviceCode_[1], 19)); serviceCode_[2] = '\0'; // null terminate string } - changed_ |= telegram->read_value(serviceCodeNumber_, 20); + has_update(telegram->read_value(serviceCodeNumber_, 20)); - // at this point do a quick check to see if the hot water or heating is active - check_active(); + check_active(); // do a quick check to see if the hot water or heating is active } /* @@ -965,23 +441,23 @@ void Boiler::process_UBAMonitorFast(std::shared_ptr telegram) { * received only after requested (not broadcasted) */ void Boiler::process_UBATotalUptime(std::shared_ptr telegram) { - changed_ |= telegram->read_value(UBAuptime_, 0, 3); // force to 3 bytes + has_update(telegram->read_value(UBAuptime_, 0, 3)); // force to 3 bytes } /* * UBAParameters - type 0x16 */ void Boiler::process_UBAParameters(std::shared_ptr telegram) { - changed_ |= telegram->read_value(heatingActivated_, 0); - changed_ |= telegram->read_value(heatingTemp_, 1); - changed_ |= telegram->read_value(burnMaxPower_, 2); - changed_ |= telegram->read_value(burnMinPower_, 3); - changed_ |= telegram->read_value(boilHystOff_, 4); - changed_ |= telegram->read_value(boilHystOn_, 5); - changed_ |= telegram->read_value(burnMinPeriod_, 6); - changed_ |= telegram->read_value(pumpDelay_, 8); - changed_ |= telegram->read_value(pumpModMax_, 9); - changed_ |= telegram->read_value(pumpModMin_, 10); + has_update(telegram->read_value(heatingActivated_, 0)); + has_update(telegram->read_value(heatingTemp_, 1)); + has_update(telegram->read_value(burnMaxPower_, 2)); + has_update(telegram->read_value(burnMinPower_, 3)); + has_update(telegram->read_value(boilHystOff_, 4)); + has_update(telegram->read_value(boilHystOn_, 5)); + has_update(telegram->read_value(burnMinPeriod_, 6)); + has_update(telegram->read_value(pumpDelay_, 8)); + has_update(telegram->read_value(pumpModMax_, 9)); + has_update(telegram->read_value(pumpModMin_, 10)); } /* @@ -989,21 +465,21 @@ void Boiler::process_UBAParameters(std::shared_ptr telegram) { * received every 10 seconds */ void Boiler::process_UBAMonitorWW(std::shared_ptr telegram) { - changed_ |= telegram->read_value(wWSetTemp_, 0); - changed_ |= telegram->read_value(wWCurTemp_, 1); - changed_ |= telegram->read_value(wWCurTemp2_, 3); - changed_ |= telegram->read_value(wWCurFlow_, 9); - changed_ |= telegram->read_value(wWType_, 8); - - changed_ |= telegram->read_value(wWWorkM_, 10, 3); // force to 3 bytes - changed_ |= telegram->read_value(wWStarts_, 13, 3); // force to 3 bytes - - changed_ |= telegram->read_bitvalue(wWOneTime_, 5, 1); - changed_ |= telegram->read_bitvalue(wWDisinfecting_, 5, 2); - changed_ |= telegram->read_bitvalue(wWCharging_, 5, 3); - changed_ |= telegram->read_bitvalue(wWRecharging_, 5, 4); - changed_ |= telegram->read_bitvalue(wWTempOK_, 5, 5); - changed_ |= telegram->read_bitvalue(wWActive_, 5, 6); + has_update(telegram->read_value(wWSetTemp_, 0)); + has_update(telegram->read_value(wWCurTemp_, 1)); + has_update(telegram->read_value(wWCurTemp2_, 3)); + has_update(telegram->read_value(wWCurFlow_, 9)); + has_update(telegram->read_value(wWType_, 8)); + + has_update(telegram->read_value(wWWorkM_, 10, 3)); // force to 3 bytes + has_update(telegram->read_value(wWStarts_, 13, 3)); // force to 3 bytes + + has_update(telegram->read_bitvalue(wWOneTime_, 5, 1)); + has_update(telegram->read_bitvalue(wWDisinfecting_, 5, 2)); + has_update(telegram->read_bitvalue(wWCharging_, 5, 3)); + has_update(telegram->read_bitvalue(wWRecharging_, 5, 4)); + has_update(telegram->read_bitvalue(wWTempOK_, 5, 5)); + has_update(telegram->read_bitvalue(wWActive_, 5, 6)); } /* @@ -1014,25 +490,25 @@ void Boiler::process_UBAMonitorWW(std::shared_ptr telegram) { * 88 00 E4 23 00 00 00 00 00 2B 2B 83 */ void Boiler::process_UBAMonitorFastPlus(std::shared_ptr telegram) { - changed_ |= telegram->read_value(selFlowTemp_, 6); - changed_ |= telegram->read_bitvalue(burnGas_, 11, 0); - // changed_ |= telegram->read_bitvalue(heatPump_, 11, 1); // heating active? see SlowPlus - changed_ |= telegram->read_bitvalue(wWHeat_, 11, 2); - changed_ |= telegram->read_value(curBurnPow_, 10); - changed_ |= telegram->read_value(selBurnPow_, 9); - changed_ |= telegram->read_value(curFlowTemp_, 7); - changed_ |= telegram->read_value(flameCurr_, 19); + has_update(telegram->read_value(selFlowTemp_, 6)); + has_update(telegram->read_bitvalue(burnGas_, 11, 0)); + // has_update(telegram->read_bitvalue(heatPump_, 11, 1)); // heating active? see SlowPlus + has_update(telegram->read_bitvalue(wWHeat_, 11, 2)); + has_update(telegram->read_value(curBurnPow_, 10)); + has_update(telegram->read_value(selBurnPow_, 9)); + has_update(telegram->read_value(curFlowTemp_, 7)); + has_update(telegram->read_value(flameCurr_, 19)); - //changed_ |= telegram->read_value(temperatur_, 13); unknown temperature + //has_update(telegram->read_value(temperatur_, 13)); unknown temperature // read 3 char service code / installation status as appears on the display if ((telegram->message_length > 3) && (telegram->offset == 0)) { - changed_ |= telegram->read_value(serviceCode_[0], 1); - changed_ |= telegram->read_value(serviceCode_[1], 2); - changed_ |= telegram->read_value(serviceCode_[2], 3); + has_update(telegram->read_value(serviceCode_[0], 1)); + has_update(telegram->read_value(serviceCode_[1], 2)); + has_update(telegram->read_value(serviceCode_[2], 3)); serviceCode_[3] = '\0'; } - changed_ |= telegram->read_value(serviceCodeNumber_, 4); + has_update(telegram->read_value(serviceCodeNumber_, 4)); // at this point do a quick check to see if the hot water or heating is active uint8_t state = 0; @@ -1041,7 +517,7 @@ void Boiler::process_UBAMonitorFastPlus(std::shared_ptr telegram boilerState_ |= state & 0x02 ? 0x01 : 0; boilerState_ |= state & 0x04 ? 0x02 : 0; - check_active(); + check_active(); // do a quick check to see if the hot water or heating is active } /* @@ -1052,14 +528,14 @@ void Boiler::process_UBAMonitorFastPlus(std::shared_ptr telegram * 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 17 19 20 21 22 23 24 */ void Boiler::process_UBAMonitorSlow(std::shared_ptr telegram) { - changed_ |= telegram->read_value(outdoorTemp_, 0); - changed_ |= telegram->read_value(boilTemp_, 2); - changed_ |= telegram->read_value(exhaustTemp_, 4); - changed_ |= telegram->read_value(switchTemp_, 25); // only if there is a mixer module present - changed_ |= telegram->read_value(pumpMod_, 9); - changed_ |= telegram->read_value(burnStarts_, 10, 3); // force to 3 bytes - changed_ |= telegram->read_value(burnWorkMin_, 13, 3); // force to 3 bytes - changed_ |= telegram->read_value(heatWorkMin_, 19, 3); // force to 3 bytes + has_update(telegram->read_value(outdoorTemp_, 0)); + has_update(telegram->read_value(boilTemp_, 2)); + has_update(telegram->read_value(exhaustTemp_, 4)); + has_update(telegram->read_value(switchTemp_, 25)); // only if there is a mixer module present + has_update(telegram->read_value(pumpMod_, 9)); + has_update(telegram->read_value(burnStarts_, 10, 3)); // force to 3 bytes + has_update(telegram->read_value(burnWorkMin_, 13, 3)); // force to 3 bytes + has_update(telegram->read_value(heatWorkMin_, 19, 3)); // force to 3 bytes } /* @@ -1067,7 +543,7 @@ void Boiler::process_UBAMonitorSlow(std::shared_ptr telegram) { * 88 00 E3 00 04 00 00 00 00 01 00 00 00 00 00 02 22 2B 64 46 01 00 00 61 */ void Boiler::process_UBAMonitorSlowPlus2(std::shared_ptr telegram) { - changed_ |= telegram->read_value(pumpMod2_, 13); // Heat Pump Modulation + has_update(telegram->read_value(pumpMod2_, 13)); // Heat Pump Modulation } /* @@ -1076,15 +552,15 @@ void Boiler::process_UBAMonitorSlowPlus2(std::shared_ptr telegra * data: 01 00 20 00 00 78 00 00 00 00 00 1E EB 00 9D 3E 00 00 00 00 6B 5E 00 06 4C 64 00 00 00 00 8A A3 */ void Boiler::process_UBAMonitorSlowPlus(std::shared_ptr telegram) { - changed_ |= telegram->read_bitvalue(fanWork_, 2, 2); - changed_ |= telegram->read_bitvalue(ignWork_, 2, 3); - changed_ |= telegram->read_bitvalue(heatPump_, 2, 5); - changed_ |= telegram->read_bitvalue(wWCirc_, 2, 7); - changed_ |= telegram->read_value(exhaustTemp_, 6); - changed_ |= telegram->read_value(burnStarts_, 10, 3); // force to 3 bytes - changed_ |= telegram->read_value(burnWorkMin_, 13, 3); // force to 3 bytes - changed_ |= telegram->read_value(heatWorkMin_, 19, 3); // force to 3 bytes - changed_ |= telegram->read_value(pumpMod_, 25); + has_update(telegram->read_bitvalue(fanWork_, 2, 2)); + has_update(telegram->read_bitvalue(ignWork_, 2, 3)); + has_update(telegram->read_bitvalue(heatPump_, 2, 5)); + has_update(telegram->read_bitvalue(wWCirc_, 2, 7)); + has_update(telegram->read_value(exhaustTemp_, 6)); + has_update(telegram->read_value(burnStarts_, 10, 3)); // force to 3 bytes + has_update(telegram->read_value(burnWorkMin_, 13, 3)); // force to 3 bytes + has_update(telegram->read_value(heatWorkMin_, 19, 3)); // force to 3 bytes + has_update(telegram->read_value(pumpMod_, 25)); // temperature measurements at 4, see #620 } @@ -1093,44 +569,44 @@ void Boiler::process_UBAMonitorSlowPlus(std::shared_ptr telegram * 88 0B E6 00 01 46 00 00 46 0A 00 01 06 FA 0A 01 02 64 01 00 00 1E 00 3C 01 00 00 00 01 00 9A */ void Boiler::process_UBAParametersPlus(std::shared_ptr telegram) { - changed_ |= telegram->read_value(heatingActivated_, 0); - changed_ |= telegram->read_value(heatingTemp_, 1); - changed_ |= telegram->read_value(burnMaxPower_, 6); - changed_ |= telegram->read_value(burnMinPower_, 7); - changed_ |= telegram->read_value(boilHystOff_, 8); - changed_ |= telegram->read_value(boilHystOn_, 9); - changed_ |= telegram->read_value(burnMinPeriod_, 10); + has_update(telegram->read_value(heatingActivated_, 0)); + has_update(telegram->read_value(heatingTemp_, 1)); + has_update(telegram->read_value(burnMaxPower_, 6)); + has_update(telegram->read_value(burnMinPower_, 7)); + has_update(telegram->read_value(boilHystOff_, 8)); + has_update(telegram->read_value(boilHystOn_, 9)); + has_update(telegram->read_value(burnMinPeriod_, 10)); } // 0xEA void Boiler::process_UBAParameterWWPlus(std::shared_ptr telegram) { - changed_ |= telegram->read_value(wWActivated_, 5); // 0x01 means on - changed_ |= telegram->read_value(wWCircPump_, 10); // 0x01 means yes - changed_ |= telegram->read_value(wWCircPumpMode_, 11); // 1=1x3min... 6=6x3min, 7=continuous - // changed_ |= telegram->read_value(wWDisinfectTemp_, 12); // settings, status in E9 - // changed_ |= telegram->read_value(wWSelTemp_, 6); // settings, status in E9 + has_update(telegram->read_value(wWActivated_, 5)); // 0x01 means on + has_update(telegram->read_value(wWCircPump_, 10)); // 0x01 means yes + has_update(telegram->read_value(wWCircPumpMode_, 11)); // 1=1x3min... 6=6x3min, 7=continuous + // has_update(telegram->read_value(wWDisinfectTemp_, 12)); // settings, status in E9 + // has_update(telegram->read_value(wWSelTemp_, 6)); // settings, status in E9 } // 0xE9 - DHW Status // e.g. 08 00 E9 00 37 01 F6 01 ED 00 00 00 00 41 3C 00 00 00 00 00 00 00 00 00 00 00 00 37 00 00 00 (CRC=77) #data=27 void Boiler::process_UBADHWStatus(std::shared_ptr telegram) { - changed_ |= telegram->read_value(wWSetTemp_, 0); - changed_ |= telegram->read_value(wWCurTemp_, 1); - changed_ |= telegram->read_value(wWCurTemp2_, 3); - - changed_ |= telegram->read_value(wWWorkM_, 17, 3); // force to 3 bytes - changed_ |= telegram->read_value(wWStarts_, 14, 3); // force to 3 bytes - - changed_ |= telegram->read_bitvalue(wWOneTime_, 12, 2); - changed_ |= telegram->read_bitvalue(wWDisinfecting_, 12, 3); - changed_ |= telegram->read_bitvalue(wWCharging_, 12, 4); - changed_ |= telegram->read_bitvalue(wWRecharging_, 13, 4); - changed_ |= telegram->read_bitvalue(wWTempOK_, 13, 5); - changed_ |= telegram->read_bitvalue(wWCircPump_, 13, 2); - - // changed_ |= telegram->read_value(wWActivated_, 20); // Activated is in 0xEA, this is something other 0/100% - changed_ |= telegram->read_value(wWSelTemp_, 10); - changed_ |= telegram->read_value(wWDisinfectionTemp_, 9); + has_update(telegram->read_value(wWSetTemp_, 0)); + has_update(telegram->read_value(wWCurTemp_, 1)); + has_update(telegram->read_value(wWCurTemp2_, 3)); + + has_update(telegram->read_value(wWWorkM_, 17, 3)); // force to 3 bytes + has_update(telegram->read_value(wWStarts_, 14, 3)); // force to 3 bytes + + has_update(telegram->read_bitvalue(wWOneTime_, 12, 2)); + has_update(telegram->read_bitvalue(wWDisinfecting_, 12, 3)); + has_update(telegram->read_bitvalue(wWCharging_, 12, 4)); + has_update(telegram->read_bitvalue(wWRecharging_, 13, 4)); + has_update(telegram->read_bitvalue(wWTempOK_, 13, 5)); + has_update(telegram->read_bitvalue(wWCircPump_, 13, 2)); + + // has_update(telegram->read_value(wWActivated_, 20)); // Activated is in 0xEA, this is something other 0/100% + has_update(telegram->read_value(wWSelTemp_, 10)); + has_update(telegram->read_value(wWDisinfectionTemp_, 9)); } /* @@ -1143,25 +619,25 @@ void Boiler::process_UBADHWStatus(std::shared_ptr telegram) { * 08 00 FF 48 03 95 00 00 06 C0 00 00 07 66 FF FF FF FF 2E */ void Boiler::process_UBAInformation(std::shared_ptr telegram) { - changed_ |= telegram->read_value(upTimeControl_, 0); - changed_ |= telegram->read_value(upTimeCompHeating_, 8); - changed_ |= telegram->read_value(upTimeCompCooling_, 16); - changed_ |= telegram->read_value(upTimeCompWw_, 4); + has_update(telegram->read_value(upTimeControl_, 0)); + has_update(telegram->read_value(upTimeCompHeating_, 8)); + has_update(telegram->read_value(upTimeCompCooling_, 16)); + has_update(telegram->read_value(upTimeCompWw_, 4)); - changed_ |= telegram->read_value(heatingStarts_, 28); - changed_ |= telegram->read_value(coolingStarts_, 36); - changed_ |= telegram->read_value(wWStarts2_, 24); + has_update(telegram->read_value(heatingStarts_, 28)); + has_update(telegram->read_value(coolingStarts_, 36)); + has_update(telegram->read_value(wWStarts2_, 24)); - changed_ |= telegram->read_value(nrgConsTotal_, 64); + has_update(telegram->read_value(nrgConsTotal_, 64)); - changed_ |= telegram->read_value(auxElecHeatNrgConsTotal_, 40); - changed_ |= telegram->read_value(auxElecHeatNrgConsHeating_, 48); - changed_ |= telegram->read_value(auxElecHeatNrgConsDHW_, 44); + has_update(telegram->read_value(auxElecHeatNrgConsTotal_, 40)); + has_update(telegram->read_value(auxElecHeatNrgConsHeating_, 48)); + has_update(telegram->read_value(auxElecHeatNrgConsDHW_, 44)); - changed_ |= telegram->read_value(nrgConsCompTotal_, 56); - changed_ |= telegram->read_value(nrgConsCompHeating_, 68); - changed_ |= telegram->read_value(nrgConsCompWw_, 72); - changed_ |= telegram->read_value(nrgConsCompCooling_, 76); + has_update(telegram->read_value(nrgConsCompTotal_, 56)); + has_update(telegram->read_value(nrgConsCompHeating_, 68)); + has_update(telegram->read_value(nrgConsCompWw_, 72)); + has_update(telegram->read_value(nrgConsCompCooling_, 76)); } /* @@ -1172,58 +648,63 @@ void Boiler::process_UBAInformation(std::shared_ptr telegram) { * 08 00 FF 31 03 94 00 00 00 00 00 00 00 38 */ void Boiler::process_UBAEnergySupplied(std::shared_ptr telegram) { - changed_ |= telegram->read_value(nrgSuppTotal_, 4); - changed_ |= telegram->read_value(nrgSuppHeating_, 12); - changed_ |= telegram->read_value(nrgSuppWw_, 8); - changed_ |= telegram->read_value(nrgSuppCooling_, 16); + has_update(telegram->read_value(nrgSuppTotal_, 4)); + has_update(telegram->read_value(nrgSuppHeating_, 12)); + has_update(telegram->read_value(nrgSuppWw_, 8)); + has_update(telegram->read_value(nrgSuppCooling_, 16)); } // 0x2A - MC10Status // e.g. 88 00 2A 00 00 00 00 00 00 00 00 00 D2 00 00 80 00 00 01 08 80 00 02 47 00 // see https://github.com/proddy/EMS-ESP/issues/397 void Boiler::process_MC10Status(std::shared_ptr telegram) { - changed_ |= telegram->read_value(wwMixTemperature_, 14); - changed_ |= telegram->read_value(wwBufferTemperature_, 18); + has_update(telegram->read_value(wwMixTemperature_, 14)); + has_update(telegram->read_value(wwBufferTemperature_, 18)); } /* * UBAOutdoorTemp - type 0xD1 - external temperature EMS+ */ void Boiler::process_UBAOutdoorTemp(std::shared_ptr telegram) { - changed_ |= telegram->read_value(outdoorTemp_, 0); + has_update(telegram->read_value(outdoorTemp_, 0)); } // UBASetPoint 0x1A void Boiler::process_UBASetPoints(std::shared_ptr telegram) { - changed_ |= telegram->read_value(setFlowTemp_, 0); // boiler set temp from thermostat - changed_ |= telegram->read_value(setBurnPow_, 1); // max json power in % - changed_ |= telegram->read_value(wWSetPumpPower_, 2); // ww pump speed/power? + has_update(telegram->read_value(setFlowTemp_, 0)); // boiler set temp from thermostat + has_update(telegram->read_value(setBurnPow_, 1)); // max json power in % + has_update(telegram->read_value(wWSetPumpPower_, 2)); // ww pump speed/power? } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" - -// 0x35 -// not yet implemented +// 0x35 - not yet implemented void Boiler::process_UBAFlags(std::shared_ptr telegram) { } - #pragma GCC diagnostic pop // 0x1C // 08 00 1C 94 0B 0A 1D 31 08 00 80 00 00 00 -> message for 29.11.2020 // 08 00 1C 94 0B 0A 1D 31 00 00 00 00 00 00 -> message reset - void Boiler::process_UBAMaintenanceStatus(std::shared_ptr telegram) { +void Boiler::process_UBAMaintenanceStatus(std::shared_ptr telegram) { // 5. byte: Maintenance due (0 = no, 3 = yes, due to operating hours, 8 = yes, due to date) - changed_ |= telegram->read_value(maintenanceMessage_,5); + uint8_t message_code; + has_update(telegram->read_value(message_code, 5)); + + // ignore if 0, which means all is ok + if (Helpers::hasValue(message_code) && message_code > 0) { + snprintf_P(maintenanceMessage_, sizeof(maintenanceMessage_), PSTR("H%02d"), message_code); + } } // 0x10, 0x11 void Boiler::process_UBAErrorMessage(std::shared_ptr telegram) { // data: displaycode(2), errornumber(2), year, month, hour, day, minute, duration(2), src-addr if (telegram->message_data[4] & 0x80) { // valid date - char code[3]; - uint16_t codeNo; + + static uint32_t lastCodeDate_ = 0; // last code date + char code[3]; + uint16_t codeNo; code[0] = telegram->message_data[0]; code[1] = telegram->message_data[1]; code[2] = 0; @@ -1245,13 +726,21 @@ void Boiler::process_UBAErrorMessage(std::shared_ptr telegram) { // 0x15 void Boiler::process_UBAMaintenanceData(std::shared_ptr telegram) { // first byte: Maintenance messages (0 = none, 1 = by operating hours, 2 = by date) + telegram->read_value(maintenanceType_, 0); - telegram->read_value(maintenanceTime_, 1); - uint8_t day = telegram->message_data[2]; - uint8_t month = telegram->message_data[3]; - uint8_t year = telegram->message_data[4]; - if (day > 0 && month > 0 && year > 0) { - snprintf_P(maintenanceDate_,sizeof(maintenanceDate_),PSTR("%02d.%02d.%04d"),day,month,year + 2000); + + if (maintenanceType_ == 1) { + // time only + telegram->read_value(maintenanceTime_, 1); + maintenanceTime_ = maintenanceTime_ * 100; + } else if (maintenanceType_ == 2) { + // date only + uint8_t day = telegram->message_data[2]; + uint8_t month = telegram->message_data[3]; + uint8_t year = telegram->message_data[4]; + if (day > 0 && month > 0 && year > 0) { + snprintf_P(maintenanceDate_, sizeof(maintenanceDate_), PSTR("%02d.%02d.%04d"), day, month, year + 2000); + } } } @@ -1283,7 +772,10 @@ bool Boiler::set_flow_temp(const char * value, const int8_t id) { } LOG_INFO(F("Setting boiler flow temperature to %d C"), v); + // some boiler have it in 0x1A, some in 0x35, but both telegrams are sometimes writeonly write_command(EMS_TYPE_UBASetPoints, 0, v, EMS_TYPE_UBASetPoints); + // write_command(0x35, 3, v, 0x35); + return true; } @@ -1602,22 +1094,37 @@ bool Boiler::set_warmwater_circulation_mode(const char * value, const int8_t id) return true; } // Reset command +// 0 & 1 Reset-Mode (Manuel, others) +// 8 reset maintenance message Hxx +// 12 & 13 Reset that Error-memory bool Boiler::set_reset(const char * value, const int8_t id) { - bool v = false; - if (!Helpers::value2bool(value, v)) { - return false; - } - if (v == false) { + std::string s(12, '\0'); + if (!Helpers::value2string(value, s)) { return false; } - LOG_INFO(F("Reset boiler maintenance message")); - write_command(0x05, 0x08, 0xFF, 0x1C); - return true; + if (s == "maintenance") { + LOG_INFO(F("Reset boiler maintenance message")); + write_command(0x05, 0x08, 0xFF, 0x1C); + return true; + } else if (s == "error") { + LOG_INFO(F("Reset boiler error message")); + write_command(0x05, 0x00, 0x5A); // error reset + return true; + } + return false; } //maintenance bool Boiler::set_maintenance(const char * value, const int8_t id) { + std::string s(12, '\0'); + if (Helpers::value2string(value, s)) { + if (s == "reset") { + LOG_INFO(F("Reset boiler maintenance message")); + write_command(0x05, 0x08, 0xFF, 0x1C); + return true; + } + } if (strlen(value) == 10) { // date uint8_t day = (value[0] - '0') * 10 + (value[1] - '0'); uint8_t month = (value[3] - '0') * 10 + (value[4] - '0'); @@ -1635,15 +1142,15 @@ bool Boiler::set_maintenance(const char * value, const int8_t id) { return true; } - int hrs; - if (!Helpers::value2number(value, hrs)) { - LOG_WARNING(F("Setting maintenance: wrong format")); - return false; + int hrs; + if (!Helpers::value2number(value, hrs)) { + LOG_WARNING(F("Setting maintenance: wrong format")); + return false; } if (hrs == 0) { LOG_INFO(F("Setting maintenance off")); - write_command(0x15, 0, 0, 0x15); // off + write_command(0x15, 0, 0, 0x15); // off } else { LOG_INFO(F("Setting maintenance in %d hours"), hrs); write_command(0x15, 1, (uint8_t)(hrs / 100)); diff --git a/src/devices/boiler.h b/src/devices/boiler.h index a81fd98d..45d40788 100644 --- a/src/devices/boiler.h +++ b/src/devices/boiler.h @@ -38,23 +38,14 @@ class Boiler : public EMSdevice { public: Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); - virtual void publish_values(JsonObject & json, bool force); - virtual bool export_values(JsonObject & json); - virtual void device_info_web(JsonArray & root); - virtual bool updated_values(); + virtual bool publish_ha_config(); private: static uuid::log::Logger logger_; - void register_mqtt_ha_config(); - void register_mqtt_ha_config_ww(); void check_active(const bool force = false); - bool export_values_main(JsonObject & doc, const bool textformat = false); - bool export_values_ww(JsonObject & doc, const bool textformat = false); - bool changed_ = false; - bool mqtt_ha_config_ = false; // HA MQTT Discovery - bool mqtt_ha_config_ww_ = false; // HA MQTT Discovery + uint8_t boilerState_ = EMS_VALUE_UINT_NOTSET; // Boiler state flag - FOR INTERNAL USE static constexpr uint8_t EMS_TYPE_UBAParameterWW = 0x33; static constexpr uint8_t EMS_TYPE_UBAFunctionTest = 0x1D; @@ -68,120 +59,99 @@ class Boiler : public EMSdevice { static constexpr uint8_t EMS_BOILER_SELFLOWTEMP_HEATING = 20; // was originally 70, changed to 30 for issue #193, then to 20 with issue #344 - // UBAParameterWW - uint8_t wWActivated_ = EMS_VALUE_BOOL_NOTSET; // Warm Water activated - uint8_t wWSelTemp_ = EMS_VALUE_UINT_NOTSET; // Warm Water selected temperature - uint8_t wWCircPump_ = EMS_VALUE_BOOL_NOTSET; // Warm Water circulation pump available - uint8_t wWCircPumpMode_ = EMS_VALUE_UINT_NOTSET; // Warm Water circulation pump mode - uint8_t wWChargeType_ = EMS_VALUE_BOOL_NOTSET; // Warm Water charge type (pump or 3-way-valve) - uint8_t wWDisinfectionTemp_ = EMS_VALUE_UINT_NOTSET; // Warm Water disinfection temperature to prevent infection - uint8_t wWComfort_ = EMS_VALUE_UINT_NOTSET; // WW comfort mode - - // MC10Status - uint16_t wwMixTemperature_ = EMS_VALUE_USHORT_NOTSET; // mengertemperatuur - uint16_t wwBufferTemperature_ = EMS_VALUE_USHORT_NOTSET; // buffertemperature - - // UBAMonitorFast - 0x18 on EMS1 - uint8_t selFlowTemp_ = EMS_VALUE_UINT_NOTSET; // Selected flow temperature - uint16_t curFlowTemp_ = EMS_VALUE_USHORT_NOTSET; // Current flow temperature - uint16_t wwStorageTemp1_ = EMS_VALUE_USHORT_NOTSET; // warm water storage temp 1 - uint16_t wwStorageTemp2_ = EMS_VALUE_USHORT_NOTSET; // warm water storage temp 2 - uint16_t retTemp_ = EMS_VALUE_USHORT_NOTSET; // Return temperature - uint8_t burnGas_ = EMS_VALUE_BOOL_NOTSET; // Gas on/off - uint8_t fanWork_ = EMS_VALUE_BOOL_NOTSET; // Fan on/off - uint8_t ignWork_ = EMS_VALUE_BOOL_NOTSET; // Ignition on/off - uint8_t heatPump_ = EMS_VALUE_BOOL_NOTSET; // Boiler pump on/off - uint8_t wWHeat_ = EMS_VALUE_BOOL_NOTSET; // 3-way valve on WW - uint8_t wWCirc_ = EMS_VALUE_BOOL_NOTSET; // Circulation on/off - uint8_t selBurnPow_ = EMS_VALUE_UINT_NOTSET; // Burner max power % - uint8_t curBurnPow_ = EMS_VALUE_UINT_NOTSET; // Burner current power % - uint16_t flameCurr_ = EMS_VALUE_USHORT_NOTSET; // Flame current in micro amps - uint8_t sysPress_ = EMS_VALUE_UINT_NOTSET; // System pressure - char serviceCode_[4] = {'\0'}; // 3 character status/service code - uint16_t serviceCodeNumber_ = EMS_VALUE_USHORT_NOTSET; // error/service code - uint8_t boilerState_ = EMS_VALUE_UINT_NOTSET; // Boiler state flag - char lastCode_[30] = {'\0'}; - uint32_t lastCodeDate_ = 0; - - // UBAMonitorSlow - 0x19 on EMS1 - int16_t outdoorTemp_ = EMS_VALUE_SHORT_NOTSET; // Outside temperature - uint16_t boilTemp_ = EMS_VALUE_USHORT_NOTSET; // Boiler temperature - uint16_t exhaustTemp_ = EMS_VALUE_USHORT_NOTSET; // Exhaust temperature - uint8_t pumpMod_ = EMS_VALUE_UINT_NOTSET; // Pump modulation % - uint32_t burnStarts_ = EMS_VALUE_ULONG_NOTSET; // # burner restarts - uint32_t burnWorkMin_ = EMS_VALUE_ULONG_NOTSET; // Total burner operating time - uint32_t heatWorkMin_ = EMS_VALUE_ULONG_NOTSET; // Total heat operating time - uint16_t switchTemp_ = EMS_VALUE_USHORT_NOTSET; // Switch temperature - - // UBAMonitorWW - uint8_t wWSetTemp_ = EMS_VALUE_UINT_NOTSET; // Warm Water set temperature - uint16_t wWCurTemp_ = EMS_VALUE_USHORT_NOTSET; // Warm Water current temperature - uint16_t wWCurTemp2_ = EMS_VALUE_USHORT_NOTSET; // Warm Water current temperature storage - uint32_t wWStarts_ = EMS_VALUE_ULONG_NOTSET; // Warm Water # starts - uint32_t wWWorkM_ = EMS_VALUE_ULONG_NOTSET; // Warm Water # minutes - uint8_t wWOneTime_ = EMS_VALUE_BOOL_NOTSET; // Warm Water one time function on/off - uint8_t wWDisinfecting_ = EMS_VALUE_BOOL_NOTSET; // Warm Water disinfection on/off - uint8_t wWCharging_ = EMS_VALUE_BOOL_NOTSET; // Warm Water charging on/off - uint8_t wWRecharging_ = EMS_VALUE_BOOL_NOTSET; // Warm Water recharge on/off - uint8_t wWTempOK_ = EMS_VALUE_BOOL_NOTSET; // Warm Water temperature ok on/off - uint8_t wWCurFlow_ = EMS_VALUE_UINT_NOTSET; // Warm Water current flow temp in l/min - uint8_t wWType_ = EMS_VALUE_UINT_NOTSET; // 0-off, 1-flow, 2-flowbuffer, 3-buffer, 4-layered buffer - uint8_t wWActive_ = EMS_VALUE_BOOL_NOTSET; - - // UBATotalUptime - uint32_t UBAuptime_ = EMS_VALUE_ULONG_NOTSET; // Total UBA working hours - - // UBAParameters - uint8_t heatingActivated_ = EMS_VALUE_BOOL_NOTSET; // Heating activated on the boiler - uint8_t heatingTemp_ = EMS_VALUE_UINT_NOTSET; // Heating temperature setting on the boiler - uint8_t pumpModMax_ = EMS_VALUE_UINT_NOTSET; // Boiler circuit pump modulation max. power % - uint8_t pumpModMin_ = EMS_VALUE_UINT_NOTSET; // Boiler circuit pump modulation min. power - uint8_t burnMinPower_ = EMS_VALUE_UINT_NOTSET; - uint8_t burnMaxPower_ = EMS_VALUE_UINT_NOTSET; - int8_t boilHystOff_ = EMS_VALUE_INT_NOTSET; - int8_t boilHystOn_ = EMS_VALUE_INT_NOTSET; - uint8_t burnMinPeriod_ = EMS_VALUE_UINT_NOTSET; - uint8_t pumpDelay_ = EMS_VALUE_UINT_NOTSET; - - // UBASetPoint - uint8_t setFlowTemp_ = EMS_VALUE_UINT_NOTSET; // boiler setpoint temp - uint8_t setBurnPow_ = EMS_VALUE_UINT_NOTSET; // max output power in % - uint8_t wWSetPumpPower_ = EMS_VALUE_UINT_NOTSET; // ww pump speed/power? - - // other internal calculated params - uint8_t tapwaterActive_ = EMS_VALUE_BOOL_NOTSET; // Hot tap water is on/off - uint8_t heatingActive_ = EMS_VALUE_BOOL_NOTSET; // Central heating is on/off - uint8_t pumpMod2_ = EMS_VALUE_UINT_NOTSET; // heatpump modulation from 0xE3 (heatpumps) - - // UBAInformation - uint32_t upTimeControl_ = EMS_VALUE_ULONG_NOTSET; // Operating time control - uint32_t upTimeCompHeating_ = EMS_VALUE_ULONG_NOTSET; // Operating time compressor heating - uint32_t upTimeCompCooling_ = EMS_VALUE_ULONG_NOTSET; // Operating time compressor cooling - uint32_t upTimeCompWw_ = EMS_VALUE_ULONG_NOTSET; // Operating time compressor warm water - uint32_t heatingStarts_ = EMS_VALUE_ULONG_NOTSET; // Heating starts (control) - uint32_t coolingStarts_ = EMS_VALUE_ULONG_NOTSET; // Cooling starts (control) - uint32_t wWStarts2_ = EMS_VALUE_ULONG_NOTSET; // Warm water starts (control) - uint32_t nrgConsTotal_ = EMS_VALUE_ULONG_NOTSET; // Energy consumption total - uint32_t auxElecHeatNrgConsTotal_ = EMS_VALUE_ULONG_NOTSET; // Auxiliary electrical heater energy consumption total - uint32_t auxElecHeatNrgConsHeating_ = EMS_VALUE_ULONG_NOTSET; // Auxiliary electrical heater energy consumption heating - uint32_t auxElecHeatNrgConsDHW_ = EMS_VALUE_ULONG_NOTSET; // Auxiliary electrical heater energ consumption DHW - uint32_t nrgConsCompTotal_ = EMS_VALUE_ULONG_NOTSET; // Energy consumption compressor total - uint32_t nrgConsCompHeating_ = EMS_VALUE_ULONG_NOTSET; // Energy consumption compressor heating - uint32_t nrgConsCompWw_ = EMS_VALUE_ULONG_NOTSET; // Energy consumption compressor warm water - uint32_t nrgConsCompCooling_ = EMS_VALUE_ULONG_NOTSET; // Energy consumption compressor cooling - - // UBAEnergySupplied - uint32_t nrgSuppTotal_ = EMS_VALUE_ULONG_NOTSET; // Energy supplied total - uint32_t nrgSuppHeating_ = EMS_VALUE_ULONG_NOTSET; // Energy supplied heating - uint32_t nrgSuppWw_ = EMS_VALUE_ULONG_NOTSET; // Energy supplied warm water - uint32_t nrgSuppCooling_ = EMS_VALUE_ULONG_NOTSET; // Energy supplied cooling - - // _UBAMaintenanceData - uint8_t maintenanceMessage_ = EMS_VALUE_UINT_NOTSET; - uint8_t maintenanceType_ = EMS_VALUE_UINT_NOTSET; - uint8_t maintenanceTime_ = EMS_VALUE_UINT_NOTSET; - char maintenanceDate_[12] = {'\0'}; - + // ww + uint8_t wWSetTemp_; // Warm Water set temperature + uint8_t wWSelTemp_; // Warm Water selected temperature + uint8_t wWType_; // 0-off, 1-flow, 2-flowbuffer, 3-buffer, 4-layered buffer + uint8_t wWComfort_; // WW comfort mode + uint8_t wWCircPump_; // Warm Water circulation pump available + uint8_t wWChargeType_; // Warm Water charge type (pump or 3-way-valve) + uint8_t wWDisinfectionTemp_; // Warm Water disinfection temperature to prevent infection + uint8_t wWCircPumpMode_; // Warm Water circulation pump mode + uint8_t wWCirc_; // Circulation on/off + uint16_t wWCurTemp_; // Warm Water current temperature + uint16_t wWCurTemp2_; // Warm Water current temperature storage + uint8_t wWCurFlow_; // Warm Water current flow temp in l/min + uint16_t wwStorageTemp1_; // warm water storage temp 1 + uint16_t wwStorageTemp2_; // warm water storage temp 2 + uint8_t wWActivated_; // Warm Water activated + uint8_t wWOneTime_; // Warm Water one time function on/off + uint8_t wWDisinfecting_; // Warm Water disinfection on/off + uint8_t wWCharging_; // Warm Water charging on/off + uint8_t wWRecharging_; // Warm Water recharge on/off + uint8_t wWTempOK_; // Warm Water temperature ok on/off + uint8_t wWActive_; + uint8_t wWHeat_; // 3-way valve on WW + uint8_t wWSetPumpPower_; // ww pump speed/power? + uint16_t wwMixTemperature_; // mixing temperature + uint16_t wwBufferTemperature_; // buffertemperature + uint32_t wWStarts_; // Warm Water # starts + uint32_t wWStarts2_; // Warm water starts (control) + uint32_t wWWorkM_; // Warm Water # minutes + + // main + uint8_t heatingActive_; // Central heating is on/off + uint8_t tapwaterActive_; // Hot tap water is on/off + uint8_t selFlowTemp_; // Selected flow temperature + uint8_t selBurnPow_; // Burner max power % + uint8_t pumpMod2_; // heatpump modulation from 0xE3 (heatpumps) + uint8_t pumpMod_; // Pump modulation % + int16_t outdoorTemp_; // Outside temperature + uint16_t curFlowTemp_; // Current flow temperature + uint16_t retTemp_; // Return temperature + uint16_t switchTemp_; // Switch temperature + uint8_t sysPress_; // System pressure + uint16_t boilTemp_; // Boiler temperature + uint16_t exhaustTemp_; // Exhaust temperature + uint8_t burnGas_; // Gas on/off + uint16_t flameCurr_; // Flame current in micro amps + uint8_t heatPump_; // Boiler pump on/off + uint8_t fanWork_; // Fan on/off + uint8_t ignWork_; // Ignition on/off + uint8_t heatingActivated_; // Heating activated on the boiler + uint8_t heatingTemp_; // Heating temperature setting on the boiler + uint8_t pumpModMax_; // Boiler circuit pump modulation max. power % + uint8_t pumpModMin_; // Boiler circuit pump modulation min. power + uint8_t pumpDelay_; + uint8_t burnMinPeriod_; + uint8_t burnMinPower_; + uint8_t burnMaxPower_; + int8_t boilHystOn_; + int8_t boilHystOff_; + uint8_t setFlowTemp_; // boiler setpoint temp + uint8_t curBurnPow_; // Burner current power % + uint8_t setBurnPow_; // max output power in % + uint32_t burnStarts_; // # burner restarts + uint32_t burnWorkMin_; // Total burner operating time + uint32_t heatWorkMin_; // Total heat operating time + uint32_t UBAuptime_; // Total UBA working hours + char lastCode_[30]; // last error code + char serviceCode_[4]; // 3 character status/service code + uint16_t serviceCodeNumber_; // error/service code + + // info + uint32_t upTimeControl_; // Operating time control + uint32_t upTimeCompHeating_; // Operating time compressor heating + uint32_t upTimeCompCooling_; // Operating time compressor cooling + uint32_t upTimeCompWw_; // Operating time compressor warm water + uint32_t heatingStarts_; // Heating starts (control) + uint32_t coolingStarts_; // Cooling starts (control) + uint32_t nrgConsTotal_; // Energy consumption total + uint32_t nrgConsCompTotal_; // Energy consumption compressor total + uint32_t nrgConsCompHeating_; // Energy consumption compressor heating + uint32_t nrgConsCompWw_; // Energy consumption compressor warm water + uint32_t nrgConsCompCooling_; // Energy consumption compressor cooling + uint32_t nrgSuppTotal_; // Energy supplied total + uint32_t nrgSuppHeating_; // Energy supplied heating + uint32_t nrgSuppWw_; // Energy supplied warm water + uint32_t nrgSuppCooling_; // Energy supplied cooling + uint32_t auxElecHeatNrgConsTotal_; // Auxiliary electrical heater energy consumption total + uint32_t auxElecHeatNrgConsHeating_; // Auxiliary electrical heater energy consumption heating + uint32_t auxElecHeatNrgConsDHW_; // Auxiliary electrical heater energy consumption DHW + char maintenanceMessage_[4]; + char maintenanceDate_[12]; + uint8_t maintenanceType_; + uint8_t maintenanceTime_; void process_UBAParameterWW(std::shared_ptr telegram); void process_UBAMonitorFast(std::shared_ptr telegram); diff --git a/src/devices/connect.cpp b/src/devices/connect.cpp index 401f08aa..3b77241d 100644 --- a/src/devices/connect.cpp +++ b/src/devices/connect.cpp @@ -28,21 +28,9 @@ Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, con : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { } -void Connect::device_info_web(JsonArray & root) { -} - -// publish values via MQTT -void Connect::publish_values(JsonObject & json, bool force) { -} - -// export values to JSON -bool Connect::export_values(JsonObject & json) { +// publish HA config +bool Connect::publish_ha_config() { return true; } -// check to see if values have been updated -bool Connect::updated_values() { - return false; -} - } // namespace emsesp \ No newline at end of file diff --git a/src/devices/connect.h b/src/devices/connect.h index 40991315..edf536a3 100644 --- a/src/devices/connect.h +++ b/src/devices/connect.h @@ -35,10 +35,7 @@ class Connect : public EMSdevice { public: Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); - virtual void publish_values(JsonObject & json, bool force); - virtual bool export_values(JsonObject & json); - virtual void device_info_web(JsonArray & root); - virtual bool updated_values(); + virtual bool publish_ha_config(); private: static uuid::log::Logger logger_; diff --git a/src/devices/controller.cpp b/src/devices/controller.cpp index 7acc5460..cf89d6a5 100644 --- a/src/devices/controller.cpp +++ b/src/devices/controller.cpp @@ -28,21 +28,9 @@ Controller::Controller(uint8_t device_type, uint8_t device_id, uint8_t product_i : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { } -void Controller::device_info_web(JsonArray & root) { -} - -// publish values via MQTT -void Controller::publish_values(JsonObject & json, bool force) { -} - -// export values to JSON -bool Controller::export_values(JsonObject & json) { - return true; -} - -// check to see if values have been updated -bool Controller::updated_values() { - return false; +// publish HA config +bool Controller::publish_ha_config() { + return true; } } // namespace emsesp \ No newline at end of file diff --git a/src/devices/controller.h b/src/devices/controller.h index 1db0222a..6dcf24f0 100644 --- a/src/devices/controller.h +++ b/src/devices/controller.h @@ -35,10 +35,7 @@ class Controller : public EMSdevice { public: Controller(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); - virtual void publish_values(JsonObject & json, bool force); - virtual bool export_values(JsonObject & json); - virtual void device_info_web(JsonArray & root); - virtual bool updated_values(); + virtual bool publish_ha_config(); private: static uuid::log::Logger logger_; diff --git a/src/devices/gateway.cpp b/src/devices/gateway.cpp index 9a0827f1..a29fe595 100644 --- a/src/devices/gateway.cpp +++ b/src/devices/gateway.cpp @@ -28,21 +28,9 @@ Gateway::Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, con : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { } -void Gateway::device_info_web(JsonArray & root) { -} - -// publish values via MQTT -void Gateway::publish_values(JsonObject & json, bool force) { -} - -// export values to JSON -bool Gateway::export_values(JsonObject & json) { - return true; -} - -// check to see if values have been updated -bool Gateway::updated_values() { - return false; +// publish HA config +bool Gateway::publish_ha_config() { + return true; } } // namespace emsesp \ No newline at end of file diff --git a/src/devices/gateway.h b/src/devices/gateway.h index 92563e00..69c9631a 100644 --- a/src/devices/gateway.h +++ b/src/devices/gateway.h @@ -35,10 +35,7 @@ class Gateway : public EMSdevice { public: Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); - virtual void publish_values(JsonObject & json, bool force); - virtual bool export_values(JsonObject & json); - virtual void device_info_web(JsonArray & root); - virtual bool updated_values(); + virtual bool publish_ha_config(); private: static uuid::log::Logger logger_; diff --git a/src/devices/generic.cpp b/src/devices/generic.cpp index 749c01ed..7e28b0fe 100644 --- a/src/devices/generic.cpp +++ b/src/devices/generic.cpp @@ -28,21 +28,9 @@ Generic::Generic(uint8_t device_type, uint8_t device_id, uint8_t product_id, con : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { } -void Generic::device_info_web(JsonArray & root) { -} - -// publish values via MQTT -void Generic::publish_values(JsonObject & json, bool force) { -} - -// export values to JSON -bool Generic::export_values(JsonObject & json) { - return true; -} - -// check to see if values have been updated -bool Generic::updated_values() { - return false; +// publish HA config +bool Generic::publish_ha_config() { + return true; } } // namespace emsesp \ No newline at end of file diff --git a/src/devices/generic.h b/src/devices/generic.h index 4762bd9c..aa0a5a71 100644 --- a/src/devices/generic.h +++ b/src/devices/generic.h @@ -35,10 +35,7 @@ class Generic : public EMSdevice { public: Generic(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); - virtual void publish_values(JsonObject & json, bool force); - virtual bool export_values(JsonObject & json); - virtual void device_info_web(JsonArray & root); - virtual bool updated_values(); + virtual bool publish_ha_config(); private: static uuid::log::Logger logger_; diff --git a/src/devices/heatpump.cpp b/src/devices/heatpump.cpp index 8245d1b6..3023de36 100644 --- a/src/devices/heatpump.cpp +++ b/src/devices/heatpump.cpp @@ -31,66 +31,23 @@ Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, c // telegram handlers register_telegram_type(0x042B, F("HP1"), true, [&](std::shared_ptr t) { process_HPMonitor1(t); }); register_telegram_type(0x047B, F("HP2"), true, [&](std::shared_ptr t) { process_HPMonitor2(t); }); -} - -// creates JSON doc from values -// returns false if empty -bool Heatpump::export_values(JsonObject & json) { - if (Helpers::hasValue(airHumidity_)) { - json["airHumidity"] = (float)airHumidity_ / 2; - } - - if (Helpers::hasValue(dewTemperature_)) { - json["dewTemperature"] = dewTemperature_; - } - - return json.size(); -} - -void Heatpump::device_info_web(JsonArray & root) { - // fetch the values into a JSON document - StaticJsonDocument doc; - JsonObject json = doc.to(); - if (!export_values(json)) { - return; // empty - } - create_value_json(root, F("airHumidity"), nullptr, F_(airHumidity), F_(percent), json); - create_value_json(root, F("dewTemperature"), nullptr, F_(dewTemperature), F_(degrees), json); + std::string empty(""); + register_device_value(empty, &airHumidity_, DeviceValueType::UINT, flash_string_vector{F("2")}, F("airHumidity"), F("Relative air humidity"), DeviceValueUOM::NONE); + register_device_value(empty, &dewTemperature_, DeviceValueType::UINT, {}, F("dewTemperature"), F("Dew point temperature"), DeviceValueUOM::NONE); } -// publish values via MQTT -void Heatpump::publish_values(JsonObject & json, bool force) { - // handle HA first - if (Mqtt::mqtt_format() == Mqtt::Format::HA) { - if (!mqtt_ha_config_ || force) { - register_mqtt_ha_config(); - return; - } - } - - StaticJsonDocument doc; - JsonObject json_data = doc.to(); - if (export_values(json_data)) { - Mqtt::publish(F("heatpump_data"), doc.as()); - } -} - -void Heatpump::register_mqtt_ha_config() { - if (!Mqtt::connected()) { - return; - } - - // Create the Master device - StaticJsonDocument doc; - doc["name"] = F_(EMSESP); +// publish HA config +bool Heatpump::publish_ha_config() { + StaticJsonDocument doc; doc["uniq_id"] = F_(heatpump); - doc["ic"] = F_(iconheatpump); + doc["ic"] = F_(iconvalve); char stat_t[50]; snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/heatpump_data"), System::hostname().c_str()); doc["stat_t"] = stat_t; + doc["name"] = FJSON("Humidity"); doc["val_tpl"] = FJSON("{{value_json.airHumidity}}"); JsonObject dev = doc.createNestedObject("dev"); @@ -100,21 +57,12 @@ void Heatpump::register_mqtt_ha_config() { dev["mdl"] = this->name(); JsonArray ids = dev.createNestedArray("ids"); ids.add("ems-esp-heatpump"); - Mqtt::publish_ha(F("homeassistant/sensor/ems-esp/heatpump/config"), doc.as()); // publish the config payload with retain flag - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(airHumidity), device_type(), "airHumidity", F_(percent), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(dewTemperature), device_type(), "dewTemperature", F_(degrees), nullptr); - - mqtt_ha_config_ = true; // done -} + char topic[100]; + snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/heatpump/config"), System::hostname().c_str()); + Mqtt::publish_ha(topic, doc.as()); // publish the config payload with retain flag -// check to see if values have been updated -bool Heatpump::updated_values() { - if (changed_) { - changed_ = false; - return true; - } - return false; + return true; } /* @@ -122,8 +70,8 @@ bool Heatpump::updated_values() { * e.g. "38 10 FF 00 03 7B 08 24 00 4B" */ void Heatpump::process_HPMonitor2(std::shared_ptr telegram) { - changed_ |= telegram->read_value(dewTemperature_, 0); - changed_ |= telegram->read_value(airHumidity_, 1); + has_update(telegram->read_value(dewTemperature_, 0)); + has_update(telegram->read_value(airHumidity_, 1)); } #pragma GCC diagnostic push diff --git a/src/devices/heatpump.h b/src/devices/heatpump.h index 6685df54..9634f3a1 100644 --- a/src/devices/heatpump.h +++ b/src/devices/heatpump.h @@ -36,21 +36,13 @@ class Heatpump : public EMSdevice { public: Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); - virtual void publish_values(JsonObject & json, bool force); - virtual bool export_values(JsonObject & json); - virtual void device_info_web(JsonArray & root); - virtual bool updated_values(); + virtual bool publish_ha_config(); private: static uuid::log::Logger logger_; - void register_mqtt_ha_config(); - - uint8_t airHumidity_ = EMS_VALUE_UINT_NOTSET; - uint8_t dewTemperature_ = EMS_VALUE_UINT_NOTSET; - - bool changed_ = false; - bool mqtt_ha_config_ = false; // for HA MQTT Discovery + uint8_t airHumidity_; + uint8_t dewTemperature_; void process_HPMonitor1(std::shared_ptr telegram); void process_HPMonitor2(std::shared_ptr telegram); diff --git a/src/devices/mixer.cpp b/src/devices/mixer.cpp index 7b9a7a03..4257c559 100644 --- a/src/devices/mixer.cpp +++ b/src/devices/mixer.cpp @@ -55,103 +55,50 @@ Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s } } -// output json to web UI -void Mixer::device_info_web(JsonArray & root) { - if (type() == Type::NONE) { - return; // don't have any values yet - } - - // fetch the values into a JSON document - StaticJsonDocument doc; - JsonObject json = doc.to(); - - if (!export_values_format(Mqtt::Format::SINGLE, json)) { - return; // empty - } - char prefix_str[10]; - if (type() == Type::HC) { - snprintf_P(prefix_str, sizeof(prefix_str), PSTR("(hc %d) "), hc_); - create_value_json(root, F("flowTemp"), FPSTR(prefix_str), F_(flowTemp), F_(degrees), json); - create_value_json(root, F("flowSetTemp"), FPSTR(prefix_str), F_(flowSetTemp), F_(degrees), json); - create_value_json(root, F("pumpStatus"), FPSTR(prefix_str), F_(pumpStatus), nullptr, json); - create_value_json(root, F("valveStatus"), FPSTR(prefix_str), F_(valveStatus), F_(percent), json); - } else { - snprintf_P(prefix_str, sizeof(prefix_str), PSTR("(wwc %d) "), hc_); - create_value_json(root, F("wwTemp"), FPSTR(prefix_str), F_(wwTemp), F_(degrees), json); - create_value_json(root, F("pumpStatus"), FPSTR(prefix_str), F_(pumpStatus), nullptr, json); - create_value_json(root, F("tempStatus"), FPSTR(prefix_str), F_(tempStatus), nullptr, json); +// register values, depending on type (hc or wwc) +void Mixer::register_values(const Type type, uint16_t hc) { + if (type == Type::NONE) { + return; // already done } -} -// check to see if values have been updated -bool Mixer::updated_values() { - if (changed_) { - changed_ = false; - return true; - } - return false; -} + // store the heating circuit and type + hc_ = hc + 1; + type_ = type; -// publish values via MQTT -// topic is mixer_data -void Mixer::publish_values(JsonObject & json, bool force) { - // handle HA first - if (Mqtt::mqtt_format() == Mqtt::Format::HA) { - if (!mqtt_ha_config_ || force) { - register_mqtt_ha_config(); - return; - } - } + std::string prefix(10, '\0'); + snprintf_P(&prefix[0], sizeof(prefix), PSTR("%s%d"), (type_ == Type::HC) ? "hc" : "wwc", hc + 1); - if (Mqtt::mqtt_format() == Mqtt::Format::SINGLE) { - StaticJsonDocument doc; - JsonObject json_data = doc.to(); - if (export_values_format(Mqtt::mqtt_format(), json_data)) { - char topic[30]; - if (type() == Type::HC) { - snprintf_P(topic, 30, PSTR("mixer_data_hc%d"), hc_); - } else { - snprintf_P(topic, 30, PSTR("mixer_data_wwc%d"), hc_); - } - Mqtt::publish(topic, doc.as()); - } - } else { - // format is HA or Nested. This is bundled together and sent in emsesp.cpp - export_values_format(Mqtt::mqtt_format(), json); - } + register_device_value( + prefix, &flowTemp_, DeviceValueType::USHORT, flash_string_vector{F("10")}, F("flowTemp"), F("Current flow temperature"), DeviceValueUOM::DEGREES); + register_device_value(prefix, &flowSetTemp_, DeviceValueType::UINT, {}, F("flowSetTemp"), F("Setpoint flow temperature"), DeviceValueUOM::DEGREES); + register_device_value(prefix, &pumpStatus_, DeviceValueType::BOOL, {}, F("pumpStatus"), F("Pump/Valve status"), DeviceValueUOM::NONE); + register_device_value(prefix, &status_, DeviceValueType::INT, {}, F("status"), F("Current status"), DeviceValueUOM::NONE); } -// publish config topic for HA MQTT Discovery -void Mixer::register_mqtt_ha_config() { - if (!Mqtt::connected()) { - return; - } +// publish HA config +bool Mixer::publish_ha_config() { // if we don't have valid values for this HC don't add it ever again if (!Helpers::hasValue(pumpStatus_)) { - return; + return false; } - // Create the Master device - StaticJsonDocument doc; - - char name[20]; - snprintf_P(name, sizeof(name), PSTR("Mixer %02X"), device_id() - 0x20 + 1); - doc["name"] = name; + StaticJsonDocument doc; char uniq_id[20]; - snprintf_P(uniq_id, sizeof(uniq_id), PSTR("mixer%02X"), device_id() - 0x20 + 1); + snprintf_P(uniq_id, sizeof(uniq_id), PSTR("Mixer%02X"), device_id() - 0x20 + 1); doc["uniq_id"] = uniq_id; - doc["ic"] = FJSON("mdi:home-thermometer-outline"); - char stat_t[50]; snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/mixer_data"), System::hostname().c_str()); doc["stat_t"] = stat_t; - doc["val_tpl"] = FJSON("{{value_json.type}}"); // HA needs a single value. We take the type which is wwc or hc + char name[20]; + snprintf_P(name, sizeof(name), PSTR("Mixer %02X Type"), device_id() - 0x20 + 1); + doc["name"] = name; + doc["val_tpl"] = FJSON("{{value_json.type}}"); // HA needs a single value. We take the type which is wwc or hc JsonObject dev = doc.createNestedObject("dev"); dev["name"] = FJSON("EMS-ESP Mixer"); dev["sw"] = EMSESP_APP_VERSION; @@ -160,127 +107,45 @@ void Mixer::register_mqtt_ha_config() { JsonArray ids = dev.createNestedArray("ids"); ids.add("ems-esp-mixer"); + // determine the topic, if its HC and WWC. This is determined by the incoming telegram types. std::string topic(100, '\0'); - if (type() == Type::HC) { - snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/sensor/ems-esp/mixer_hc%d/config"), hc_); - Mqtt::publish_ha(topic, doc.as()); // publish the config payload with retain flag - char hc_name[10]; - snprintf_P(hc_name, sizeof(hc_name), PSTR("hc%d"), hc_); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(flowTemp), device_type(), "flowTemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(flowSetTemp), device_type(), "flowSetTemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(pumpStatus), device_type(), "pumpStatus", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(valveStatus), device_type(), "valveStatus", F_(percent), F_(iconpercent)); + if (type_ == Type::HC) { + snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/sensor/%s/mixer_hc%d/config"), System::hostname().c_str(), hc_); } else { - // WWC - snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/sensor/ems-esp/mixer_wwc%d/config"), hc_); - Mqtt::publish_ha(topic, doc.as()); // publish the config payload with retain flag - char wwc_name[10]; - snprintf_P(wwc_name, sizeof(wwc_name), PSTR("wwc%d"), hc_); - Mqtt::register_mqtt_ha_sensor(wwc_name, nullptr, F_(wwTemp), device_type(), "wwTemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(wwc_name, nullptr, F_(pumpStatus), device_type(), "pumpStatus", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(wwc_name, nullptr, F_(tempStatus), device_type(), "tempStatus", nullptr, nullptr); + snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/sensor/%s/mixer_wwc%d/config"), System::hostname().c_str(), hc_); // WWC } - mqtt_ha_config_ = true; // done -} + Mqtt::publish_ha(topic, doc.as()); // publish the config payload with retain flag -bool Mixer::export_values(JsonObject & json) { - return export_values_format(Mqtt::Format::NESTED, json); -} - -// creates JSON doc from values -// returns false if empty -bool Mixer::export_values_format(uint8_t mqtt_format, JsonObject & json) { - // check if there is data for the mixer unit - if (type() == Type::NONE) { - return 0; - } - - JsonObject json_hc; - char hc_name[10]; // hc{1-4} - - if (type() == Type::HC) { - snprintf_P(hc_name, sizeof(hc_name), PSTR("hc%d"), hc_); - if (mqtt_format == Mqtt::Format::SINGLE) { - json_hc = json; - json["type"] = FJSON("hc"); - } else if (mqtt_format == Mqtt::Format::HA) { - json_hc = json.createNestedObject(hc_name); - json_hc["type"] = FJSON("hc"); - } else { - json_hc = json.createNestedObject(hc_name); - } - if (Helpers::hasValue(flowTemp_)) { - json_hc["flowTemp"] = (float)flowTemp_ / 10; - } - if (Helpers::hasValue(flowSetTemp_)) { - json_hc["flowSetTemp"] = flowSetTemp_; - } - if (Helpers::hasValue(pumpStatus_)) { - char s[7]; - json_hc["pumpStatus"] = Helpers::render_value(s, pumpStatus_, EMS_VALUE_BOOL); - } - if (Helpers::hasValue(status_)) { - json_hc["valveStatus"] = status_; - } - - return json_hc.size(); - } - - // WWC - snprintf_P(hc_name, sizeof(hc_name), PSTR("wwc%d"), hc_); - if (mqtt_format == Mqtt::Format::SINGLE) { - json_hc = json; - json["type"] = FJSON("wwc"); - } else if (mqtt_format == Mqtt::Format::HA) { - json_hc = json.createNestedObject(hc_name); - json_hc["type"] = FJSON("wwc"); - } else { - json_hc = json.createNestedObject(hc_name); - } - if (Helpers::hasValue(flowTemp_)) { - json_hc["wwTemp"] = (float)flowTemp_ / 10; - } - if (Helpers::hasValue(pumpStatus_)) { - char s[7]; - json_hc["pumpStatus"] = Helpers::render_value(s, pumpStatus_, EMS_VALUE_BOOL); - } - if (Helpers::hasValue(status_)) { - json_hc["tempStatus"] = status_; - } - - return json_hc.size(); + return true; } // heating circuits 0x02D7, 0x02D8 etc... // e.g. A0 00 FF 00 01 D7 00 00 00 80 00 00 00 00 03 C5 // A0 0B FF 00 01 D7 00 00 00 80 00 00 00 00 03 80 void Mixer::process_MMPLUSStatusMessage_HC(std::shared_ptr telegram) { - type(Type::HC); - hc_ = telegram->type_id - 0x02D7 + 1; // determine which circuit this is - changed_ |= telegram->read_value(flowTemp_, 3); // is * 10 - changed_ |= telegram->read_value(flowSetTemp_, 5); - changed_ |= telegram->read_bitvalue(pumpStatus_, 0, 0); - changed_ |= telegram->read_value(status_, 2); // valve status + register_values(Type::HC, telegram->type_id - 0x02D7); + has_update(telegram->read_value(flowTemp_, 3)); // is * 10 + has_update(telegram->read_value(flowSetTemp_, 5)); + has_update(telegram->read_bitvalue(pumpStatus_, 0, 0)); + has_update(telegram->read_value(status_, 2)); // valve status } // Mixer warm water loading/DHW - 0x0331, 0x0332 // e.g. A9 00 FF 00 02 32 02 6C 00 3C 00 3C 3C 46 02 03 03 00 3C // on 0x28 // A8 00 FF 00 02 31 02 35 00 3C 00 3C 3C 46 02 03 03 00 3C // in 0x29 void Mixer::process_MMPLUSStatusMessage_WWC(std::shared_ptr telegram) { - type(Type::WWC); - hc_ = telegram->type_id - 0x0331 + 1; // determine which circuit this is. There are max 2. - changed_ |= telegram->read_value(flowTemp_, 0); // is * 10 - changed_ |= telegram->read_bitvalue(pumpStatus_, 2, 0); - changed_ |= telegram->read_value(status_, 11); // temp status + register_values(Type::WWC, telegram->type_id - 0x0331); + has_update(telegram->read_value(flowTemp_, 0)); // is * 10 + has_update(telegram->read_bitvalue(pumpStatus_, 2, 0)); + has_update(telegram->read_value(status_, 11)); // temp status } // Mixer IMP - 0x010C // e.g. A0 00 FF 00 00 0C 01 00 00 00 00 00 54 // A1 00 FF 00 00 0C 02 04 00 01 1D 00 82 void Mixer::process_IPMStatusMessage(std::shared_ptr telegram) { - type(Type::HC); - hc_ = device_id() - 0x20 + 1; + register_values(Type::HC, device_id() - 0x20); // check if circuit is active, 0-off, 1-unmixed, 2-mixed uint8_t ismixed = 0; @@ -291,28 +156,27 @@ void Mixer::process_IPMStatusMessage(std::shared_ptr telegram) { // do we have a mixed circuit if (ismixed == 2) { - changed_ |= telegram->read_value(flowTemp_, 3); // is * 10 - changed_ |= telegram->read_value(flowSetTemp_, 5); - changed_ |= telegram->read_value(status_, 2); // valve status + has_update(telegram->read_value(flowTemp_, 3)); // is * 10 + has_update(telegram->read_value(flowSetTemp_, 5)); + has_update(telegram->read_value(status_, 2)); // valve status } - changed_ |= telegram->read_bitvalue(pumpStatus_, 1, 0); // pump is also in unmixed circuits + has_update(telegram->read_bitvalue(pumpStatus_, 1, 0)); // pump is also in unmixed circuits } // Mixer on a MM10 - 0xAB // e.g. Mixer Module -> All, type 0xAB, telegram: 21 00 AB 00 2D 01 BE 64 04 01 00 (CRC=15) #data=7 // see also https://github.com/proddy/EMS-ESP/issues/386 void Mixer::process_MMStatusMessage(std::shared_ptr telegram) { - type(Type::HC); - // the heating circuit is determine by which device_id it is, 0x20 - 0x23 // 0x21 is position 2. 0x20 is typically reserved for the WM10 switch module // see https://github.com/proddy/EMS-ESP/issues/270 and https://github.com/proddy/EMS-ESP/issues/386#issuecomment-629610918 - hc_ = device_id() - 0x20 + 1; - changed_ |= telegram->read_value(flowTemp_, 1); // is * 10 - changed_ |= telegram->read_bitvalue(pumpStatus_, 3, 2); // is 0 or 0x64 (100%), check only bit 2 - changed_ |= telegram->read_value(flowSetTemp_, 0); - changed_ |= telegram->read_value(status_, 4); // valve status -100 to 100 + register_values(Type::HC, device_id() - 0x20); + + has_update(telegram->read_value(flowTemp_, 1)); // is * 10 + has_update(telegram->read_bitvalue(pumpStatus_, 3, 2)); // is 0 or 0x64 (100%), check only bit 2 + has_update(telegram->read_value(flowSetTemp_, 0)); + has_update(telegram->read_value(status_, 4)); // valve status -100 to 100 } #pragma GCC diagnostic push @@ -321,7 +185,7 @@ void Mixer::process_MMStatusMessage(std::shared_ptr telegram) { // Mixer on a MM10 - 0xAA // e.g. Thermostat -> Mixer Module, type 0xAA, telegram: 10 21 AA 00 FF 0C 0A 11 0A 32 xx void Mixer::process_MMConfigMessage(std::shared_ptr telegram) { - hc_ = device_id() - 0x20 + 1; + register_values(Type::HC, device_id() - 0x20); // pos 0: active FF = on // pos 1: valve runtime 0C = 120 sec in units of 10 sec } @@ -329,7 +193,7 @@ void Mixer::process_MMConfigMessage(std::shared_ptr telegram) { // Mixer on a MM10 - 0xAC // e.g. Thermostat -> Mixer Module, type 0xAC, telegram: 10 21 AC 00 1E 64 01 AB void Mixer::process_MMSetMessage(std::shared_ptr telegram) { - hc_ = device_id() - 0x20 + 1; + register_values(Type::HC, device_id() - 0x20); // pos 0: flowtemp setpoint 1E = 30°C // pos 1: position in % } diff --git a/src/devices/mixer.h b/src/devices/mixer.h index 4ad5d044..66a47563 100644 --- a/src/devices/mixer.h +++ b/src/devices/mixer.h @@ -36,17 +36,11 @@ class Mixer : public EMSdevice { public: Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); - virtual void publish_values(JsonObject & json, bool force); - virtual bool export_values(JsonObject & json); - virtual void device_info_web(JsonArray & root); - virtual bool updated_values(); + virtual bool publish_ha_config(); private: static uuid::log::Logger logger_; - bool export_values_format(uint8_t mqtt_format, JsonObject & doc); - void register_mqtt_ha_config(); - void process_MMPLUSStatusMessage_HC(std::shared_ptr telegram); void process_MMPLUSStatusMessage_WWC(std::shared_ptr telegram); void process_IPMStatusMessage(std::shared_ptr telegram); @@ -60,25 +54,15 @@ class Mixer : public EMSdevice { WWC // warm water circuit }; - Type type() const { - return type_; - } - - void type(Type new_type) { - type_ = new_type; - } - private: - uint16_t hc_ = EMS_VALUE_USHORT_NOTSET; - uint16_t flowTemp_ = EMS_VALUE_USHORT_NOTSET; - uint8_t pumpStatus_ = EMS_VALUE_UINT_NOTSET; - int8_t status_ = EMS_VALUE_INT_NOTSET; - uint8_t flowSetTemp_ = EMS_VALUE_UINT_NOTSET; - - Type type_ = Type::NONE; - - bool changed_ = false; - bool mqtt_ha_config_ = false; // for HA MQTT Discovery + uint16_t flowTemp_; + uint8_t pumpStatus_; + int8_t status_; + uint8_t flowSetTemp_; + + void register_values(const Type type, const uint16_t hc); + Type type_ = Type::NONE; + uint16_t hc_ = EMS_VALUE_USHORT_NOTSET; }; } // namespace emsesp diff --git a/src/devices/solar.cpp b/src/devices/solar.cpp index 5b8dd9df..6d6be711 100644 --- a/src/devices/solar.cpp +++ b/src/devices/solar.cpp @@ -59,67 +59,62 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s register_telegram_type(0x0103, F("ISM1StatusMessage"), true, [&](std::shared_ptr t) { process_ISM1StatusMessage(t); }); register_telegram_type(0x0101, F("ISM1Set"), false, [&](std::shared_ptr t) { process_ISM1Set(t); }); } -} - -// print to web -void Solar::device_info_web(JsonArray & root) { - // fetch the values into a JSON document - StaticJsonDocument doc; - JsonObject json = doc.to(); - if (!export_values(json)) { - return; // empty - } - create_value_json(root, F("collectorTemp"), nullptr, F_(collectorTemp), F_(degrees), json); - create_value_json(root, F("tankBottomTemp"), nullptr, F_(tankBottomTemp), F_(degrees), json); - create_value_json(root, F("tankBottomTemp2"), nullptr, F_(tankBottomTemp2), F_(degrees), json); - create_value_json(root, F("tank1MaxTempCurrent"), nullptr, F_(tank1MaxTempCurrent), F_(degrees), json); - create_value_json(root, F("heatExchangerTemp"), nullptr, F_(heatExchangerTemp), F_(degrees), json); - create_value_json(root, F("solarPumpModulation"), nullptr, F_(solarPumpModulation), F_(percent), json); - create_value_json(root, F("cylinderPumpModulation"), nullptr, F_(cylinderPumpModulation), F_(percent), json); - create_value_json(root, F("valveStatus"), nullptr, F_(valveStatus), nullptr, json); - create_value_json(root, F("solarPump"), nullptr, F_(solarPump), nullptr, json); - create_value_json(root, F("tankHeated"), nullptr, F_(tankHeated), nullptr, json); - create_value_json(root, F("collectorShutdown"), nullptr, F_(collectorShutdown), nullptr, json); - create_value_json(root, F("energyLastHour"), nullptr, F_(energyLastHour), F_(wh), json); - create_value_json(root, F("energyToday"), nullptr, F_(energyToday), F_(wh), json); - create_value_json(root, F("energyTotal"), nullptr, F_(energyTotal), F_(kwh), json); - create_value_json(root, F("pumpWorkMin"), nullptr, F_(pumpWorkMin), F_(min), json); - create_value_json(root, F("pumpWorkMintxt"), nullptr, F_(pumpWorkMintxt), F_(min), json); -} + std::string empty(""); -// publish values via MQTT -void Solar::publish_values(JsonObject & json, bool force) { - // handle HA first - if (Mqtt::mqtt_format() == Mqtt::Format::HA) { - if ((!mqtt_ha_config_ || force)) { - register_mqtt_ha_config(); - return; - } + // special case for a device_id with 0x2A where it's not actual a solar module + if (device_id == 0x2A) { + register_device_value(empty, &type_, DeviceValueType::TEXT, {}, F("type"), F("Type"), DeviceValueUOM::NONE); + strncpy(type_, "warm water circuit", sizeof(type_)); } - StaticJsonDocument doc; - JsonObject json_payload = doc.to(); - if (export_values(json_payload)) { - if (device_id() == 0x2A) { - Mqtt::publish(F("ww_data"), doc.as()); - } else { - Mqtt::publish(F("solar_data"), doc.as()); - } - } + register_device_value(empty, + &collectorTemp_, + DeviceValueType::SHORT, + flash_string_vector{F("10")}, + F("collectorTemp"), + F("Collector temperature (TS1)"), + DeviceValueUOM::DEGREES); + register_device_value(empty, + &tankBottomTemp_, + DeviceValueType::SHORT, + flash_string_vector{F("10")}, + F("tankBottomTemp"), + F("Bottom temperature (TS2)"), + DeviceValueUOM::DEGREES); + register_device_value(empty, + &tankBottomTemp2_, + DeviceValueType::SHORT, + flash_string_vector{F("10")}, + F("tankBottomTemp2"), + F("Bottom temperature (TS5)"), + DeviceValueUOM::DEGREES); + register_device_value( + empty, &heatExchangerTemp_, DeviceValueType::SHORT, {F("10")}, F("heatExchangerTemp"), F("Heat exchanger temperature (TS6)"), DeviceValueUOM::DEGREES); + + register_device_value(empty, &tank1MaxTempCurrent_, DeviceValueType::UINT, {}, F("tank1MaxTempCurrent"), F("Maximum Tank temperature"), DeviceValueUOM::NONE); + register_device_value(empty, &solarPumpModulation_, DeviceValueType::UINT, {}, F("solarPumpModulation"), F("Solar pump modulation (PS1)"), DeviceValueUOM::PERCENT); + register_device_value( + empty, &cylinderPumpModulation_, DeviceValueType::UINT, {}, F("cylinderPumpModulation"), F("Cylinder pump modulation (PS5)"), DeviceValueUOM::PERCENT); + + register_device_value(empty, &solarPump_, DeviceValueType::BOOL, {}, F("solarPump"), F("Solar pump (PS1) active"), DeviceValueUOM::NONE); + register_device_value(empty, &valveStatus_, DeviceValueType::BOOL, {}, F("valveStatus"), F("Valve status"), DeviceValueUOM::NONE); + register_device_value(empty, &tankHeated_, DeviceValueType::BOOL, {}, F("tankHeated"), F("Tank heated"), DeviceValueUOM::NONE); + register_device_value(empty, &collectorShutdown_, DeviceValueType::BOOL, {}, F("collectorShutdown"), F("Collector shutdown"), DeviceValueUOM::NONE); + + register_device_value(empty, &pumpWorkMin_, DeviceValueType::TIME, {}, F("pumpWorkMin"), F("Pump working time"), DeviceValueUOM::MINUTES); + + register_device_value( + empty, &energyLastHour_, DeviceValueType::ULONG, flash_string_vector{F("10")}, F("energyLastHour"), F("Energy last hour"), DeviceValueUOM::WH); + register_device_value(empty, &energyTotal_, DeviceValueType::ULONG, flash_string_vector{F("10")}, F("energyTotal"), F("Energy total"), DeviceValueUOM::KWH); + register_device_value(empty, &energyToday_, DeviceValueType::ULONG, {}, F("energyToday"), F("Energy today"), DeviceValueUOM::WH); } -// publish config topic for HA MQTT Discovery -void Solar::register_mqtt_ha_config() { - if (!Mqtt::connected()) { - return; - } - - // Create the Master device - StaticJsonDocument doc; - doc["name"] = F_(EMSESP); +// publish HA config +bool Solar::publish_ha_config() { + StaticJsonDocument doc; + doc["name"] = FJSON("Solar Status"); doc["uniq_id"] = F_(solar); - doc["ic"] = F_(iconthermostat); char stat_t[50]; snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/solar_data"), System::hostname().c_str()); @@ -133,113 +128,21 @@ void Solar::register_mqtt_ha_config() { dev["mdl"] = name(); JsonArray ids = dev.createNestedArray("ids"); ids.add("ems-esp-solar"); - Mqtt::publish_ha(F("homeassistant/sensor/ems-esp/solar/config"), doc.as()); // publish the config payload with retain flag - - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(collectorTemp), device_type(), "collectorTemp", F_(degrees), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(tankBottomTemp), device_type(), "tankBottomTemp", F_(degrees), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(tankBottomTemp2), device_type(), "tankBottomTemp2", F_(degrees), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(tank1MaxTempCurrent), device_type(), "tank1MaxTempCurrent", F_(degrees), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(heatExchangerTemp), device_type(), "heatExchangerTemp", F_(degrees), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(solarPumpModulation), device_type(), "solarPumpModulation", F_(percent), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(cylinderPumpModulation), device_type(), "cylinderPumpModulation", F_(percent), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(pumpWorkMin), device_type(), "pumpWorkMin", F_(min), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(energyLastHour), device_type(), "energyLastHour", F_(wh), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(energyToday), device_type(), "energyToday", F_(wh), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(energyTotal), device_type(), "energyTotal", F_(kwh), nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(solarPump), device_type(), "solarPump", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(valveStatus), device_type(), "valveStatus", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(tankHeated), device_type(), "tankHeated", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(collectorShutdown), device_type(), "collectorShutdown", nullptr, nullptr); - - mqtt_ha_config_ = true; // done -} - -// creates JSON doc from values -// returns false if empty -bool Solar::export_values(JsonObject & json) { - char s[10]; // for formatting strings - - if (Helpers::hasValue(collectorTemp_)) { - json["collectorTemp"] = (float)collectorTemp_ / 10; - } - - if (Helpers::hasValue(tankBottomTemp_)) { - json["tankBottomTemp"] = (float)tankBottomTemp_ / 10; - } - - if (Helpers::hasValue(tankBottomTemp2_)) { - json["tankBottomTemp2"] = (float)tankBottomTemp2_ / 10; - } - - if (Helpers::hasValue(tank1MaxTempCurrent_)) { - json["tank1MaxTempCurrent"] = tank1MaxTempCurrent_; - } - - if (Helpers::hasValue(heatExchangerTemp_)) { - json["heatExchangerTemp"] = (float)heatExchangerTemp_ / 10; - } - - if (Helpers::hasValue(solarPumpModulation_)) { - json["solarPumpModulation"] = solarPumpModulation_; - } - - if (Helpers::hasValue(cylinderPumpModulation_)) { - json["cylinderPumpModulation"] = cylinderPumpModulation_; - } - - if (Helpers::hasValue(solarPump_, EMS_VALUE_BOOL)) { - json["solarPump"] = Helpers::render_value(s, solarPump_, EMS_VALUE_BOOL); - } - - if (Helpers::hasValue(valveStatus_, EMS_VALUE_BOOL)) { - json["valveStatus"] = Helpers::render_value(s, valveStatus_, EMS_VALUE_BOOL); - } - - if (Helpers::hasValue(pumpWorkMin_)) { - json["pumpWorkMin"] = pumpWorkMin_; - char slong[40]; - json["pumpWorkMintxt"] = Helpers::render_value(slong, pumpWorkMin_, EMS_VALUE_TIME); - } - if (Helpers::hasValue(tankHeated_, EMS_VALUE_BOOL)) { - json["tankHeated"] = Helpers::render_value(s, tankHeated_, EMS_VALUE_BOOL); - } - - if (Helpers::hasValue(collectorShutdown_, EMS_VALUE_BOOL)) { - json["collectorShutdown"] = Helpers::render_value(s, collectorShutdown_, EMS_VALUE_BOOL); - } + char topic[100]; + snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/solar/config"), System::hostname().c_str()); + Mqtt::publish_ha(topic, doc.as()); // publish the config payload with retain flag - if (Helpers::hasValue(energyLastHour_)) { - json["energyLastHour"] = (float)energyLastHour_ / 10; - } - - if (Helpers::hasValue(energyToday_)) { - json["energyToday"] = energyToday_; - } - - if (Helpers::hasValue(energyTotal_)) { - json["energyTotal"] = (float)energyTotal_ / 10; - } - - return json.size(); -} - -// check to see if values have been updated -bool Solar::updated_values() { - if (changed_) { - changed_ = false; - return true; - } - return false; + return true; } // SM10Monitor - type 0x97 void Solar::process_SM10Monitor(std::shared_ptr telegram) { - changed_ |= telegram->read_value(collectorTemp_, 2); // collector temp from SM10, is *10 - changed_ |= telegram->read_value(tankBottomTemp_, 5); // bottom temp from SM10, is *10 - changed_ |= telegram->read_value(solarPumpModulation_, 4); // modulation solar pump - changed_ |= telegram->read_bitvalue(solarPump_, 7, 1); - changed_ |= telegram->read_value(pumpWorkMin_, 8, 3); + has_update(telegram->read_value(collectorTemp_, 2)); // collector temp from SM10, is *10 + has_update(telegram->read_value(tankBottomTemp_, 5)); // bottom temp from SM10, is *10 + has_update(telegram->read_value(solarPumpModulation_, 4)); // modulation solar pump + has_update(telegram->read_bitvalue(solarPump_, 7, 1)); + has_update(telegram->read_value(pumpWorkMin_, 8, 3)); } /* @@ -247,11 +150,11 @@ void Solar::process_SM10Monitor(std::shared_ptr telegram) { * e.g. B0 0B FF 00 02 58 FF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF 00 FF 01 00 00 */ void Solar::process_SM100SystemConfig(std::shared_ptr telegram) { - changed_ |= telegram->read_value(heatTransferSystem_, 5, 1); - changed_ |= telegram->read_value(externalTank_, 9, 1); - changed_ |= telegram->read_value(thermalDisinfect_, 10, 1); - changed_ |= telegram->read_value(heatMetering_, 14, 1); - changed_ |= telegram->read_value(solarIsEnabled_, 19, 1); + has_update(telegram->read_value(heatTransferSystem_, 5, 1)); + has_update(telegram->read_value(externalTank_, 9, 1)); + has_update(telegram->read_value(thermalDisinfect_, 10, 1)); + has_update(telegram->read_value(heatMetering_, 14, 1)); + has_update(telegram->read_value(solarIsEnabled_, 19, 1)); } /* @@ -259,16 +162,16 @@ void Solar::process_SM100SystemConfig(std::shared_ptr telegram) * e.g. B0 0B FF 00 02 5A 64 05 00 58 14 01 01 32 64 00 00 00 5A 0C */ void Solar::process_SM100SolarCircuitConfig(std::shared_ptr telegram) { - changed_ |= telegram->read_value(collectorTempMax_, 0, 1); - changed_ |= telegram->read_value(tank1MaxTempCurrent_, 3, 1); - changed_ |= telegram->read_value(collectorTempMin_, 4, 1); - changed_ |= telegram->read_value(solarPumpMode_, 5, 1); - changed_ |= telegram->read_value(solarPumpMinRPM_, 6, 1); - changed_ |= telegram->read_value(solarPumpTurnoffDiff_, 7, 1); - changed_ |= telegram->read_value(solarPumpTurnonDiff_, 8, 1); - changed_ |= telegram->read_value(solarPumpKick_, 9, 1); - changed_ |= telegram->read_value(plainWaterMode_, 10, 1); - changed_ |= telegram->read_value(doubleMatchFlow_, 11, 1); + has_update(telegram->read_value(collectorTempMax_, 0, 1)); + has_update(telegram->read_value(tank1MaxTempCurrent_, 3, 1)); + has_update(telegram->read_value(collectorTempMin_, 4, 1)); + has_update(telegram->read_value(solarPumpMode_, 5, 1)); + has_update(telegram->read_value(solarPumpMinRPM_, 6, 1)); + has_update(telegram->read_value(solarPumpTurnoffDiff_, 7, 1)); + has_update(telegram->read_value(solarPumpTurnonDiff_, 8, 1)); + has_update(telegram->read_value(solarPumpKick_, 9, 1)); + has_update(telegram->read_value(plainWaterMode_, 10, 1)); + has_update(telegram->read_value(doubleMatchFlow_, 11, 1)); } /* process_SM100ParamCfg - type 0xF9 EMS 1.0 @@ -290,14 +193,14 @@ void Solar::process_SM100ParamCfg(std::shared_ptr telegram) { uint16_t t_id; uint8_t of; int32_t min, def, max, cur; - telegram->read_value(t_id, 1); - telegram->read_value(of, 3); - telegram->read_value(min, 5); - telegram->read_value(def, 9); - telegram->read_value(max, 13); - telegram->read_value(cur, 17); - - // LOG_DEBUG(F("SM100ParamCfg param=0x%04X, offset=%d, min=%d, default=%d, max=%d, current=%d"), t_id, of, min, def, max, cur); + has_update(telegram->read_value(t_id, 1)); + has_update(telegram->read_value(of, 3)); + has_update(telegram->read_value(min, 5)); + has_update(telegram->read_value(def, 9)); + has_update(telegram->read_value(max, 13)); + has_update(telegram->read_value(cur, 17)); + + // LOG_DEBUG(F("SM100ParamCfg param=0x%04X, offset=%d, min=%d, default=%d, max=%d, current=%d"), t_id, of, min, def, max, cur)); } /* @@ -312,10 +215,10 @@ void Solar::process_SM100ParamCfg(std::shared_ptr telegram) { * bytes 20+21 = TS6 Temperature sensor external heat exchanger */ void Solar::process_SM100Monitor(std::shared_ptr telegram) { - changed_ |= telegram->read_value(collectorTemp_, 0); // is *10 - TS1: Temperature sensor for collector array 1 - changed_ |= telegram->read_value(tankBottomTemp_, 2); // is *10 - TS2: Temperature sensor 1 cylinder, bottom - changed_ |= telegram->read_value(tankBottomTemp2_, 16); // is *10 - TS5: Temperature sensor 2 cylinder, bottom, or swimming pool - changed_ |= telegram->read_value(heatExchangerTemp_, 20); // is *10 - TS6: Heat exchanger temperature sensor + has_update(telegram->read_value(collectorTemp_, 0)); // is *10 - TS1: Temperature sensor for collector array 1 + has_update(telegram->read_value(tankBottomTemp_, 2)); // is *10 - TS2: Temperature sensor 1 cylinder, bottom + has_update(telegram->read_value(tankBottomTemp2_, 16)); // is *10 - TS5: Temperature sensor 2 cylinder, bottom, or swimming pool + has_update(telegram->read_value(heatExchangerTemp_, 20)); // is *10 - TS6: Heat exchanger temperature sensor } #pragma GCC diagnostic push @@ -330,17 +233,17 @@ void Solar::process_SM100Monitor2(std::shared_ptr telegram) { // SM100wwTemperatur - 0x07D6 // Solar Module(0x2A) -> (0x00), (0x7D6), data: 01 C1 00 00 02 5B 01 AF 01 AD 80 00 01 90 void Solar::process_SM100wwTemperature(std::shared_ptr telegram) { - // changed_ |= telegram->read_value(wwTemp_1_, 0); - // changed_ |= telegram->read_value(wwTemp_3_, 4); - // changed_ |= telegram->read_value(wwTemp_4_, 6); - // changed_ |= telegram->read_value(wwTemp_5_, 8); - // changed_ |= telegram->read_value(wwTemp_7_, 12); + // has_update(telegram->read_value(wwTemp_1_, 0)); + // has_update(telegram->read_value(wwTemp_3_, 4)); + // has_update(telegram->read_value(wwTemp_4_, 6)); + // has_update(telegram->read_value(wwTemp_5_, 8)); + // has_update(telegram->read_value(wwTemp_7_, 12)); } // SM100wwStatus - 0x07AA // Solar Module(0x2A) -> (0x00), (0x7AA), data: 64 00 04 00 03 00 28 01 0F void Solar::process_SM100wwStatus(std::shared_ptr telegram) { - // changed_ |= telegram->read_value(wwPump_, 0); + // has_update(telegram->read_value(wwPump_, 0)); } // SM100wwCommand - 0x07AB @@ -349,15 +252,14 @@ void Solar::process_SM100wwCommand(std::shared_ptr telegram) { // not implemented yet } - #pragma GCC diagnostic pop // SM100Config - 0x0366 // e.g. B0 00 FF 00 02 66 01 62 00 13 40 14 void Solar::process_SM100Config(std::shared_ptr telegram) { - changed_ |= telegram->read_value(availabilityFlag_, 0); - changed_ |= telegram->read_value(configFlag_, 1); - changed_ |= telegram->read_value(userFlag_, 2); + has_update(telegram->read_value(availabilityFlag_, 0)); + has_update(telegram->read_value(configFlag_, 1)); + has_update(telegram->read_value(userFlag_, 2)); } /* @@ -369,8 +271,8 @@ void Solar::process_SM100Config(std::shared_ptr telegram) { void Solar::process_SM100Status(std::shared_ptr telegram) { uint8_t solarpumpmod = solarPumpModulation_; uint8_t cylinderpumpmod = cylinderPumpModulation_; - changed_ |= telegram->read_value(cylinderPumpModulation_, 8); - changed_ |= telegram->read_value(solarPumpModulation_, 9); + has_update(telegram->read_value(cylinderPumpModulation_, 8)); + has_update(telegram->read_value(solarPumpModulation_, 9)); if (solarpumpmod == 0 && solarPumpModulation_ == 100) { // mask out boosts solarPumpModulation_ = 15; // set to minimum @@ -379,8 +281,8 @@ void Solar::process_SM100Status(std::shared_ptr telegram) { if (cylinderpumpmod == 0 && cylinderPumpModulation_ == 100) { // mask out boosts cylinderPumpModulation_ = 15; // set to minimum } - changed_ |= telegram->read_bitvalue(tankHeated_, 3, 1); // issue #422 - changed_ |= telegram->read_bitvalue(collectorShutdown_, 3, 0); // collector shutdown + has_update(telegram->read_bitvalue(tankHeated_, 3, 1)); // issue #422 + has_update(telegram->read_bitvalue(collectorShutdown_, 3, 0)); // collector shutdown } /* @@ -390,8 +292,8 @@ void Solar::process_SM100Status(std::shared_ptr telegram) { * byte 10 = PS1 Solar circuit pump for collector array 1: test=b0001(1), on=b0100(4) and off=b0011(3) */ void Solar::process_SM100Status2(std::shared_ptr telegram) { - changed_ |= telegram->read_bitvalue(valveStatus_, 4, 2); // on if bit 2 set - changed_ |= telegram->read_bitvalue(solarPump_, 10, 2); // on if bit 2 set + has_update(telegram->read_bitvalue(valveStatus_, 4, 2)); // on if bit 2 set + has_update(telegram->read_bitvalue(solarPump_, 10, 2)); // on if bit 2 set } /* @@ -399,9 +301,9 @@ void Solar::process_SM100Status2(std::shared_ptr telegram) { * e.g. B0 0B FF 00 02 80 50 64 00 00 29 01 00 00 01 */ void Solar::process_SM100CollectorConfig(std::shared_ptr telegram) { - changed_ |= telegram->read_value(climateZone_, 0, 1); - changed_ |= telegram->read_value(collector1Area_, 3, 2); - changed_ |= telegram->read_value(collector1Type_, 5, 1); + has_update(telegram->read_value(climateZone_, 0, 1)); + has_update(telegram->read_value(collector1Area_, 3, 2)); + has_update(telegram->read_value(collector1Type_, 5, 1)); } /* @@ -409,16 +311,16 @@ void Solar::process_SM100CollectorConfig(std::shared_ptr telegra * e.g. 30 00 FF 00 02 8E 00 00 00 00 00 00 06 C5 00 00 76 35 */ void Solar::process_SM100Energy(std::shared_ptr telegram) { - changed_ |= telegram->read_value(energyLastHour_, 0); // last hour / 10 in Wh - changed_ |= telegram->read_value(energyToday_, 4); // todays in Wh - changed_ |= telegram->read_value(energyTotal_, 8); // total / 10 in kWh + has_update(telegram->read_value(energyLastHour_, 0)); // last hour / 10 in Wh + has_update(telegram->read_value(energyToday_, 4)); // todays in Wh + has_update(telegram->read_value(energyTotal_, 8)); // total / 10 in kWh } /* * SM100Time - type 0x0391 EMS+ for pump working time */ void Solar::process_SM100Time(std::shared_ptr telegram) { - changed_ |= telegram->read_value(pumpWorkMin_, 1, 3); + has_update(telegram->read_value(pumpWorkMin_, 1, 3)); } /* @@ -426,26 +328,26 @@ void Solar::process_SM100Time(std::shared_ptr telegram) { * e.g. B0 00 FF 00 00 03 32 00 00 00 00 13 00 D6 00 00 00 FB D0 F0 */ void Solar::process_ISM1StatusMessage(std::shared_ptr telegram) { - changed_ |= telegram->read_value(collectorTemp_, 4); // Collector Temperature - changed_ |= telegram->read_value(tankBottomTemp_, 6); // Temperature Bottom of Solar Boiler + has_update(telegram->read_value(collectorTemp_, 4)); // Collector Temperature + has_update(telegram->read_value(tankBottomTemp_, 6)); // Temperature Bottom of Solar Boiler uint16_t Wh = 0xFFFF; - changed_ |= telegram->read_value(Wh, 2); // Solar Energy produced in last hour only ushort, is not * 10 + has_update(telegram->read_value(Wh, 2)); // Solar Energy produced in last hour only ushort, is not * 10 if (Wh != 0xFFFF) { energyLastHour_ = Wh * 10; // set to *10 } - changed_ |= telegram->read_bitvalue(solarPump_, 8, 0); // PS1 Solar pump on (1) or off (0) - changed_ |= telegram->read_value(pumpWorkMin_, 10, 3); // force to 3 bytes - changed_ |= telegram->read_bitvalue(collectorShutdown_, 9, 0); // collector shutdown on/off - changed_ |= telegram->read_bitvalue(tankHeated_, 9, 2); // tank full + has_update(telegram->read_bitvalue(solarPump_, 8, 0)); // PS1 Solar pump on (1) or off (0) + has_update(telegram->read_value(pumpWorkMin_, 10, 3)); // force to 3 bytes + has_update(telegram->read_bitvalue(collectorShutdown_, 9, 0)); // collector shutdown on/off + has_update(telegram->read_bitvalue(tankHeated_, 9, 2)); // tank full } /* * Junkers ISM1 Solar Module - type 0x0101 EMS+ for setting values */ void Solar::process_ISM1Set(std::shared_ptr telegram) { - changed_ |= telegram->read_value(setpoint_maxBottomTemp_, 6); + has_update(telegram->read_value(setpoint_maxBottomTemp_, 6)); } // set temperature for tank diff --git a/src/devices/solar.h b/src/devices/solar.h index 33b54c5a..5febdccf 100644 --- a/src/devices/solar.h +++ b/src/devices/solar.h @@ -36,61 +36,56 @@ class Solar : public EMSdevice { public: Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); - virtual void publish_values(JsonObject & json, bool force); - virtual bool export_values(JsonObject & json); - virtual void device_info_web(JsonArray & root); - virtual bool updated_values(); + virtual bool publish_ha_config(); private: static uuid::log::Logger logger_; - void register_mqtt_ha_config(); - - int16_t collectorTemp_ = EMS_VALUE_SHORT_NOTSET; // TS1: Temperature sensor for collector array 1 - int16_t tankBottomTemp_ = EMS_VALUE_SHORT_NOTSET; // TS2: Temperature sensor 1 cylinder, bottom (solar thermal system) - int16_t tankBottomTemp2_ = EMS_VALUE_SHORT_NOTSET; // TS5: Temperature sensor 2 cylinder, bottom, or swimming pool (solar thermal system) - int16_t heatExchangerTemp_ = EMS_VALUE_SHORT_NOTSET; // TS6: Heat exchanger temperature sensor - uint8_t solarPumpModulation_ = EMS_VALUE_UINT_NOTSET; // PS1: modulation solar pump - uint8_t cylinderPumpModulation_ = EMS_VALUE_UINT_NOTSET; // PS5: modulation cylinder pump - uint8_t solarPump_ = EMS_VALUE_BOOL_NOTSET; // PS1: solar pump active - uint8_t valveStatus_ = EMS_VALUE_BOOL_NOTSET; // VS2: status 3-way valve for cylinder 2 (solar thermal system) with valve - int16_t setpoint_maxBottomTemp_ = EMS_VALUE_SHORT_NOTSET; // setpoint for maximum collector temp - uint32_t energyLastHour_ = EMS_VALUE_ULONG_NOTSET; - uint32_t energyToday_ = EMS_VALUE_ULONG_NOTSET; - uint32_t energyTotal_ = EMS_VALUE_ULONG_NOTSET; - uint32_t pumpWorkMin_ = EMS_VALUE_ULONG_NOTSET; // Total solar pump operating time - uint8_t tankHeated_ = EMS_VALUE_BOOL_NOTSET; - uint8_t collectorShutdown_ = EMS_VALUE_BOOL_NOTSET; // Collector shutdown on/off - - uint8_t availabilityFlag_ = EMS_VALUE_BOOL_NOTSET; - uint8_t configFlag_ = EMS_VALUE_BOOL_NOTSET; - uint8_t userFlag_ = EMS_VALUE_BOOL_NOTSET; + + int16_t collectorTemp_; // TS1: Temperature sensor for collector array 1 + int16_t tankBottomTemp_; // TS2: Temperature sensor 1 cylinder, bottom (solar thermal system) + int16_t tankBottomTemp2_; // TS5: Temperature sensor 2 cylinder, bottom, or swimming pool (solar thermal system) + int16_t heatExchangerTemp_; // TS6: Heat exchanger temperature sensor + uint8_t solarPumpModulation_; // PS1: modulation solar pump + uint8_t cylinderPumpModulation_; // PS5: modulation cylinder pump + uint8_t solarPump_; // PS1: solar pump active + uint8_t valveStatus_; // VS2: status 3-way valve for cylinder 2 (solar thermal system) with valve + int16_t setpoint_maxBottomTemp_; // setpoint for maximum collector temp + uint32_t energyLastHour_; + uint32_t energyToday_; + uint32_t energyTotal_; + uint32_t pumpWorkMin_; // Total solar pump operating time + uint8_t tankHeated_; + uint8_t collectorShutdown_; // Collector shutdown on/off + + uint8_t availabilityFlag_; + uint8_t configFlag_; + uint8_t userFlag_; // telegram 0x0358 - uint8_t heatTransferSystem_ = EMS_VALUE_UINT_NOTSET; // Umladesystem, 00=no - uint8_t externalTank_ = EMS_VALUE_UINT_NOTSET; // Heat exchanger, 00=no - uint8_t thermalDisinfect_ = EMS_VALUE_UINT_NOTSET; // Daily heatup for disinfection, 00=no - uint8_t heatMetering_ = EMS_VALUE_UINT_NOTSET; // Wärmemengenzählung, 00=no - uint8_t solarIsEnabled_ = EMS_VALUE_UINT_NOTSET; // System enable, 00=no + uint8_t heatTransferSystem_; // Umladesystem, 00=no + uint8_t externalTank_; // Heat exchanger, 00=no + uint8_t thermalDisinfect_; // Daily heatup for disinfection, 00=no + uint8_t heatMetering_; // Wärmemengenzählung, 00=no + uint8_t solarIsEnabled_; // System enable, 00=no // telegram 0x035A - uint8_t collectorTempMax_ = EMS_VALUE_UINT_NOTSET; // maximum allowable temperature for collector - uint8_t tank1MaxTempCurrent_ = EMS_VALUE_UINT_NOTSET; // Current value for max tank temp - uint8_t collectorTempMin_ = EMS_VALUE_UINT_NOTSET; // minimum allowable temperature for collector - uint8_t solarPumpMode_ = EMS_VALUE_UINT_NOTSET; // 00=off, 01=PWM, 02=10V - uint8_t solarPumpMinRPM_ = EMS_VALUE_UINT_NOTSET; // minimum RPM setting, *5 % - uint8_t solarPumpTurnoffDiff_ = EMS_VALUE_UINT_NOTSET; // solar pump turnoff collector/tank diff - uint8_t solarPumpTurnonDiff_ = EMS_VALUE_UINT_NOTSET; // solar pump turnon collector/tank diff - uint8_t solarPumpKick_ = EMS_VALUE_UINT_NOTSET; // pump kick for vacuum collector, 00=off - uint8_t plainWaterMode_ = EMS_VALUE_UINT_NOTSET; // system does not use antifreeze, 00=off - uint8_t doubleMatchFlow_ = EMS_VALUE_UINT_NOTSET; // double Match Flow, 00=off + uint8_t collectorTempMax_; // maximum allowable temperature for collector + uint8_t tank1MaxTempCurrent_; // Current value for max tank temp + uint8_t collectorTempMin_; // minimum allowable temperature for collector + uint8_t solarPumpMode_; // 00=off, 01=PWM, 02=10V + uint8_t solarPumpMinRPM_; // minimum RPM setting, *5 % + uint8_t solarPumpTurnoffDiff_; // solar pump turnoff collector/tank diff + uint8_t solarPumpTurnonDiff_; // solar pump turnon collector/tank diff + uint8_t solarPumpKick_; // pump kick for vacuum collector, 00=off + uint8_t plainWaterMode_; // system does not use antifreeze, 00=off + uint8_t doubleMatchFlow_; // double Match Flow, 00=off // telegram 0x380 - uint8_t climateZone_ = EMS_VALUE_UINT_NOTSET; // climate zone identifier - uint16_t collector1Area_ = EMS_VALUE_USHORT_NOTSET; // Area of collector field 1 - uint8_t collector1Type_ = EMS_VALUE_UINT_NOTSET; // Type of collector field 1, 01=flat, 02=vacuum + uint8_t climateZone_; // climate zone identifier + uint16_t collector1Area_; // Area of collector field 1 + uint8_t collector1Type_; // Type of collector field 1, 01=flat, 02=vacuum - bool changed_ = false; - bool mqtt_ha_config_ = false; // for HA MQTT Discovery + char type_[20]; // Solar of WWC void process_SM10Monitor(std::shared_ptr telegram); void process_SM100SystemConfig(std::shared_ptr telegram); diff --git a/src/devices/switch.cpp b/src/devices/switch.cpp index 278536a7..ddc03a52 100644 --- a/src/devices/switch.cpp +++ b/src/devices/switch.cpp @@ -32,92 +32,30 @@ Switch::Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const register_telegram_type(0x9C, F("WM10MonitorMessage"), false, [&](std::shared_ptr t) { process_WM10MonitorMessage(t); }); register_telegram_type(0x9B, F("WM10SetMessage"), false, [&](std::shared_ptr t) { process_WM10SetMessage(t); }); -} - -// fetch the values into a JSON document for display in the web -void Switch::device_info_web(JsonArray & root) { - StaticJsonDocument doc; - JsonObject json = doc.to(); - if (export_values(json)) { - create_value_json(root, F("activated"), nullptr, F_(activated), nullptr, json); - create_value_json(root, F("flowTemp"), nullptr, F_(flowTemp), F_(degrees), json); - create_value_json(root, F("status"), nullptr, F_(status), nullptr, json); - } -} -// publish values via MQTT -void Switch::publish_values(JsonObject & json, bool force) { - if (Mqtt::mqtt_format() == Mqtt::Format::HA) { - if (!mqtt_ha_config_ || force) { - register_mqtt_ha_config(); - return; - } - } - - StaticJsonDocument doc; - JsonObject json_data = doc.to(); - if (export_values(json_data)) { - Mqtt::publish(F("switch_data"), doc.as()); - } + std::string empty(""); + register_device_value(empty, &activated_, DeviceValueType::BOOL, {}, F("activated"), F("Activated"), DeviceValueUOM::NONE); + register_device_value( + empty, &flowTemp_, DeviceValueType::USHORT, flash_string_vector{F("10")}, F("flowTemp"), F("Current flow temperature"), DeviceValueUOM::DEGREES); + register_device_value(empty, &status_, DeviceValueType::INT, {}, F("status"), F("Status"), DeviceValueUOM::NONE); } -// export values to JSON -bool Switch::export_values(JsonObject & json) { - if (Helpers::hasValue(flowTemp_)) { - char s[7]; - json["activated"] = Helpers::render_value(s, activated_, EMS_VALUE_BOOL); - } - - if (Helpers::hasValue(flowTemp_)) { - json["flowTemp"] = (float)flowTemp_ / 10; - } - - if (Helpers::hasValue(flowTemp_)) { - json["status"] = status_; - } - - return true; -} - -// check to see if values have been updated -bool Switch::updated_values() { - if (changed_) { - changed_ = false; - return true; - } - return false; -} - -// publish config topic for HA MQTT Discovery -void Switch::register_mqtt_ha_config() { - if (!Mqtt::connected()) { - return; - } - - // if we don't have valid values for this HC don't add it ever again +// publish HA config +bool Switch::publish_ha_config() { + // if we don't have valid values don't add it ever again if (!Helpers::hasValue(flowTemp_)) { - return; + return false; } - // Create the Master device - StaticJsonDocument doc; - - char name[10]; - snprintf_P(name, sizeof(name), PSTR("Switch")); - doc["name"] = name; - - char uniq_id[10]; - snprintf_P(uniq_id, sizeof(uniq_id), PSTR("switch")); - doc["uniq_id"] = uniq_id; - - doc["ic"] = FJSON("mdi:home-thermometer-outline"); + StaticJsonDocument doc; + doc["uniq_id"] = F_(switch); char stat_t[50]; snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/switch_data"), System::hostname().c_str()); doc["stat_t"] = stat_t; + doc["name"] = FJSON("Type"); doc["val_tpl"] = FJSON("{{value_json.type}}"); // HA needs a single value. We take the type which is wwc or hc - JsonObject dev = doc.createNestedObject("dev"); dev["name"] = FJSON("EMS-ESP Switch"); dev["sw"] = EMSESP_APP_VERSION; @@ -126,24 +64,22 @@ void Switch::register_mqtt_ha_config() { JsonArray ids = dev.createNestedArray("ids"); ids.add("ems-esp-switch"); - Mqtt::publish_ha(F("homeassistant/sensor/ems-esp/switch/config"), doc.as()); // publish the config payload with retain flag - - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(activated), device_type(), "activated", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(flowTemp), device_type(), "flowTemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(status), device_type(), "status", nullptr, nullptr); + char topic[100]; + snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/switch/config"), System::hostname().c_str()); + Mqtt::publish_ha(topic, doc.as()); // publish the config payload with retain flag - mqtt_ha_config_ = true; // done + return true; } // message 0x9B switch on/off void Switch::process_WM10SetMessage(std::shared_ptr telegram) { - changed_ |= telegram->read_value(activated_, 0); + has_update(telegram->read_value(activated_, 0)); } // message 0x9C holds flowtemp and unknown status value void Switch::process_WM10MonitorMessage(std::shared_ptr telegram) { - changed_ |= telegram->read_value(flowTemp_, 0); // is * 10 - changed_ |= telegram->read_value(status_, 2); + has_update(telegram->read_value(flowTemp_, 0)); // is * 10 + has_update(telegram->read_value(status_, 2)); } } // namespace emsesp \ No newline at end of file diff --git a/src/devices/switch.h b/src/devices/switch.h index c5c82617..bc3b1e20 100644 --- a/src/devices/switch.h +++ b/src/devices/switch.h @@ -36,23 +36,17 @@ class Switch : public EMSdevice { public: Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); - virtual void publish_values(JsonObject & json, bool force); - virtual bool export_values(JsonObject & json); - virtual void device_info_web(JsonArray & root); - virtual bool updated_values(); + virtual bool publish_ha_config(); private: static uuid::log::Logger logger_; void process_WM10SetMessage(std::shared_ptr telegram); void process_WM10MonitorMessage(std::shared_ptr telegram); - void register_mqtt_ha_config(); - uint16_t flowTemp_ = EMS_VALUE_USHORT_NOTSET; - uint8_t status_ = EMS_VALUE_UINT_NOTSET; - uint8_t activated_ = EMS_VALUE_BOOL_NOTSET; - bool changed_ = false; - bool mqtt_ha_config_ = false; // for HA MQTT Discovery + uint16_t flowTemp_; + uint8_t status_; + uint8_t activated_; }; } // namespace emsesp diff --git a/src/devices/thermostat.cpp b/src/devices/thermostat.cpp index a8b6b00f..d32c3803 100644 --- a/src/devices/thermostat.cpp +++ b/src/devices/thermostat.cpp @@ -67,6 +67,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i for (uint8_t i = 0; i < monitor_typeids.size(); i++) { register_telegram_type(monitor_typeids[i], F("RC35Monitor"), false, [&](std::shared_ptr t) { process_RC35Monitor(t); }); register_telegram_type(set_typeids[i], F("RC35Set"), false, [&](std::shared_ptr t) { process_RC35Set(t); }); + register_telegram_type(timer_typeids[i], F("RC35Timer"), false, [&](std::shared_ptr t) { process_RC35Timer(t); }); } register_telegram_type(EMS_TYPE_IBASettings, F("IBASettings"), true, [&](std::shared_ptr t) { process_IBASettings(t); }); register_telegram_type(EMS_TYPE_wwSettings, F("WWSettings"), true, [&](std::shared_ptr t) { process_RC35wwSettings(t); }); @@ -151,524 +152,53 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i } } + // register device values for common values (not heating circuit) + register_device_values(); + + // reserve some memory for the heating circuits (max 4 to start with) + heating_circuits_.reserve(4); + if (actual_master_thermostat != device_id) { LOG_DEBUG(F("Adding new thermostat with device ID 0x%02X"), device_id); return; // don't fetch data if more than 1 thermostat } + + // + // this next section is only for the master thermostat.... + // LOG_DEBUG(F("Adding new thermostat with device ID 0x%02X (as master)"), device_id); add_commands(); - // reserve some memory for the heating circuits (max 4 to start with) - heating_circuits_.reserve(4); - // only for for the master-thermostat, go a query all the heating circuits. This is only done once. // The automatic fetch will from now on only update the active heating circuits for (uint8_t i = 0; i < monitor_typeids.size(); i++) { EMSESP::send_read_request(monitor_typeids[i], device_id); } - - for (uint8_t i = 0; i < set_typeids.size(); i++) { - EMSESP::send_read_request(set_typeids[i], device_id); - } - - for (uint8_t i = 0; i < summer_typeids.size(); i++) { - EMSESP::send_read_request(summer_typeids[i], device_id); - } - for (uint8_t i = 0; i < curve_typeids.size(); i++) { - EMSESP::send_read_request(curve_typeids[i], device_id); - } EMSESP::send_read_request(0x12, device_id); // read last error (only published on errors) } -// prepare data for Web UI -void Thermostat::device_info_web(JsonArray & root) { - StaticJsonDocument doc; - JsonObject json_main = doc.to(); - if (export_values_main(json_main)) { - create_value_json(root, F("time"), nullptr, F_(time), nullptr, json_main); - create_value_json(root, F("errorcode"), nullptr, F_(error), nullptr, json_main); - create_value_json(root, F("lastcode"), nullptr, F_(lastCode), nullptr, json_main); - create_value_json(root, F("display"), nullptr, F_(display), nullptr, json_main); - create_value_json(root, F("language"), nullptr, F_(language), nullptr, json_main); - create_value_json(root, F("offsetclock"), nullptr, F_(offsetclock), nullptr, json_main); - create_value_json(root, F("dampedtemp"), nullptr, F_(dampedtemp), F_(degrees), json_main); - create_value_json(root, F("inttemp1"), nullptr, F_(inttemp1), F_(degrees), json_main); - create_value_json(root, F("inttemp2"), nullptr, F_(inttemp2), F_(degrees), json_main); - create_value_json(root, F("intoffset"), nullptr, F_(intoffset), nullptr, json_main); - create_value_json(root, F("minexttemp"), nullptr, F_(minexttemp), F_(degrees), json_main); - create_value_json(root, F("building"), nullptr, F_(building), nullptr, json_main); - create_value_json(root, F("floordry"), nullptr, F_(floordry), nullptr, json_main); - create_value_json(root, F("floordrytemp"), nullptr, F_(floordrytemp), F_(degrees), json_main); - create_value_json(root, F("wwmode"), nullptr, F_(wwmode), nullptr, json_main); - create_value_json(root, F("wwtemp"), nullptr, F_(wwtemp), nullptr, json_main); - create_value_json(root, F("wwtemplow"), nullptr, F_(wwtemplow), nullptr, json_main); - create_value_json(root, F("wwextra1"), nullptr, F_(wwextra1), nullptr, json_main); - create_value_json(root, F("wwcircmode"), nullptr, F_(wwcircmode), nullptr, json_main); - } - doc.clear(); - JsonObject json_hc = doc.to(); - - if (export_values_hc(Mqtt::Format::NESTED, json_hc)) { - // display for each active heating circuit - for (const auto & hc : heating_circuits_) { - if (hc->is_active()) { - char prefix_str[10]; - snprintf_P(prefix_str, sizeof(prefix_str), PSTR("hc%d"), hc->hc_num()); - JsonObject json = json_hc[prefix_str]; - - snprintf_P(prefix_str, sizeof(prefix_str), PSTR("(hc %d) "), hc->hc_num()); - - create_value_json(root, F("seltemp"), FPSTR(prefix_str), F_(seltemp), F_(degrees), json); - create_value_json(root, F("currtemp"), FPSTR(prefix_str), F_(currtemp), F_(degrees), json); - create_value_json(root, F("heattemp"), FPSTR(prefix_str), F_(heattemp), F_(degrees), json); - create_value_json(root, F("comforttemp"), FPSTR(prefix_str), F_(comforttemp), F_(degrees), json); - create_value_json(root, F("daytemp"), FPSTR(prefix_str), F_(daytemp), F_(degrees), json); - create_value_json(root, F("ecotemp"), FPSTR(prefix_str), F_(ecotemp), F_(degrees), json); - create_value_json(root, F("nighttemp"), FPSTR(prefix_str), F_(nighttemp), F_(degrees), json); - create_value_json(root, F("manualtemp"), FPSTR(prefix_str), F_(manualtemp), F_(degrees), json); - create_value_json(root, F("holidaytemp"), FPSTR(prefix_str), F_(holidaytemp), F_(degrees), json); - create_value_json(root, F("nofrosttemp"), FPSTR(prefix_str), F_(nofrosttemp), F_(degrees), json); - create_value_json(root, F("heatingtype"), FPSTR(prefix_str), F_(heatingtype), nullptr, json); - create_value_json(root, F("targetflowtemp"), FPSTR(prefix_str), F_(targetflowtemp), F_(degrees), json); - create_value_json(root, F("offsettemp"), FPSTR(prefix_str), F_(offsettemp), F_(degrees), json); - create_value_json(root, F("designtemp"), FPSTR(prefix_str), F_(designtemp), F_(degrees), json); - create_value_json(root, F("roominfluence"), FPSTR(prefix_str), F_(roominfluence), F_(degrees), json); - create_value_json(root, F("flowtempoffset"), FPSTR(prefix_str), F_(flowtempoffset), F_(degrees), json); - create_value_json(root, F("minflowtemp"), FPSTR(prefix_str), F_(minflowtemp), F_(degrees), json); - create_value_json(root, F("maxflowtemp"), FPSTR(prefix_str), F_(maxflowtemp), F_(degrees), json); - create_value_json(root, F("summertemp"), FPSTR(prefix_str), F_(summertemp), F_(degrees), json); - create_value_json(root, F("summermode"), FPSTR(prefix_str), F_(summermode), F_(degrees), json); - create_value_json(root, F("mode"), FPSTR(prefix_str), F_(mode), nullptr, json); - create_value_json(root, F("modetype"), FPSTR(prefix_str), F_(modetype), nullptr, json); - } - } - } -} - -// this function is called post the telegram handler function has been executed -// we check if any of the thermostat values have changed and then republish if necessary -bool Thermostat::updated_values() { - // only publish on the master thermostat - if (EMSESP::actual_master_thermostat() != device_id()) { - return false; - } - if (changed_) { - changed_ = false; - return true; - } - return false; -} - -bool Thermostat::export_values(JsonObject & json) { - bool has_value = export_values_main(json); - has_value |= export_values_hc(Mqtt::Format::NESTED, json); - return has_value; -} - -// publish values via MQTT -void Thermostat::publish_values(JsonObject & json, bool force) { - if (EMSESP::actual_master_thermostat() != device_id()) { - return; - } - - // if MQTT is in single mode send out the main data to the thermostat_data topic - if (Mqtt::mqtt_format() == Mqtt::Format::SINGLE) { - StaticJsonDocument doc; - JsonObject json_data = doc.to(); - if (export_values_main(json_data)) { - Mqtt::publish(F("thermostat_data"), json_data); - json_data.clear(); - } - // this function will also have published each of the heating circuits - export_values_hc(Mqtt::mqtt_format(), json_data); - return; - } - - // see if we have already registered this with HA MQTT Discovery, if not send the config first - if (Mqtt::mqtt_format() == Mqtt::Format::HA) { - if (!ha_config(force)) { - return; - } - } - - StaticJsonDocument doc; - JsonObject json_data = doc.to(); - bool has_data = false; - - // get the thermostat data. - has_data |= export_values_main(json_data); - has_data |= export_values_hc(Mqtt::mqtt_format(), json_data); - - // we're in HA or CUSTOM, send out the complete topic with all the data - if (has_data) { - Mqtt::publish(F("thermostat_data"), json_data); - } -} - -bool Thermostat::export_values_main(JsonObject & rootThermostat) { - uint8_t model = this->model(); - - // Clock time - if (datetime_.size()) { - rootThermostat["time"] = datetime_; - } - - if (Helpers::hasValue(errorNumber_)) { - rootThermostat["errorcode"] = errorCode_; - } - - if (lastCode_[0] != '\0') { - rootThermostat["lastcode"] = lastCode_; - } - - if (model == EMSdevice::EMS_DEVICE_FLAG_RC30_1) { - // Display - if (Helpers::hasValue(ibaMainDisplay_)) { - if (ibaMainDisplay_ == 0) { - rootThermostat["display"] = FJSON("internal temperature"); - } else if (ibaMainDisplay_ == 1) { - rootThermostat["display"] = FJSON("internal setpoint"); - } else if (ibaMainDisplay_ == 2) { - rootThermostat["display"] = FJSON("external temperature"); - } else if (ibaMainDisplay_ == 3) { - rootThermostat["display"] = FJSON("burner temperature"); - } else if (ibaMainDisplay_ == 4) { - rootThermostat["display"] = FJSON("WW temperature"); - } else if (ibaMainDisplay_ == 5) { - rootThermostat["display"] = FJSON("functioning mode"); - } else if (ibaMainDisplay_ == 6) { - rootThermostat["display"] = FJSON("time"); - } else if (ibaMainDisplay_ == 7) { - rootThermostat["display"] = FJSON("date"); - } else if (ibaMainDisplay_ == 8) { - rootThermostat["display"] = FJSON("smoke temperature"); - } - } - - // Language - if (Helpers::hasValue(ibaLanguage_)) { - if (ibaLanguage_ == 0) { - rootThermostat["language"] = FJSON("German"); - } else if (ibaLanguage_ == 1) { - rootThermostat["language"] = FJSON("Dutch"); - } else if (ibaLanguage_ == 2) { - rootThermostat["language"] = FJSON("French"); - } else if (ibaLanguage_ == 3) { - rootThermostat["language"] = FJSON("Italian"); - } - } - - // Offset clock - if (Helpers::hasValue(ibaClockOffset_)) { - rootThermostat["offsetclock"] = ibaClockOffset_; // offset (in sec) to clock, 0xff=-1s, 0x02=2s - } - } - - // Damped outdoor temperature (RC35) - if (Helpers::hasValue(dampedoutdoortemp_)) { - rootThermostat["dampedtemp"] = dampedoutdoortemp_; - } - - // Damped outdoor temperature (RC300) - if (Helpers::hasValue(dampedoutdoortemp2_)) { - rootThermostat["dampedtemp"] = (float)dampedoutdoortemp2_ / 10; - } - - // Floordry - if (Helpers::hasValue(floordrystatus_) && Helpers::hasValue(floordrytemp_) && (floordrytemp_ > 0)) { - char s[10]; - rootThermostat["floordry"] = Helpers::render_enum(s, {F("off"), F("start"), F("heat"), F("hold"), F("cool"), F("end")}, floordrystatus_); - rootThermostat["floordrytemp"] = floordrytemp_; - } - - // Temp sensor 1 - if (Helpers::hasValue(tempsensor1_)) { - rootThermostat["inttemp1"] = (float)tempsensor1_ / 10; - } - - // Temp sensor 2 - if (Helpers::hasValue(tempsensor2_)) { - rootThermostat["inttemp2"] = (float)tempsensor2_ / 10; - } - - // Offset int. temperature - if (Helpers::hasValue(ibaCalIntTemperature_)) { - rootThermostat["intoffset"] = (float)ibaCalIntTemperature_ / 2; - } - - // Min ext. temperature - if (Helpers::hasValue(ibaMinExtTemperature_)) { - rootThermostat["minexttemp"] = (float)ibaMinExtTemperature_; // min ext temp for heating curve, in deg. - } - - // Building - if (Helpers::hasValue(ibaBuildingType_)) { - char s[10]; - if (model == EMS_DEVICE_FLAG_RC300 || model == EMS_DEVICE_FLAG_RC100) { - rootThermostat["building"] = Helpers::render_enum(s, {F("light"), F("medium"), F("heavy")}, ibaBuildingType_ - 1); - } else { - rootThermostat["building"] = Helpers::render_enum(s, {F("light"), F("medium"), F("heavy")}, ibaBuildingType_); - } - } - - // Warm water mode - if (Helpers::hasValue(wwMode_)) { - char s[10]; - if (model == EMS_DEVICE_FLAG_RC300 || model == EMS_DEVICE_FLAG_RC100) { - rootThermostat["wwmode"] = Helpers::render_enum(s, {F("off"), F("low"), F("high"), F("auto"), F("own_prog")}, wwMode_); - } else { - rootThermostat["wwmode"] = Helpers::render_enum(s, {F("off"), F("on"), F("auto")}, wwMode_); - } - } - - // Warm water temp - if (Helpers::hasValue(wwTemp_)) { - rootThermostat["wwtemp"] = wwTemp_; - } - - // Warm water low temp - if (Helpers::hasValue(wwTempLow_)) { - rootThermostat["wwtemplow"] = wwTempLow_; - } - - // Warm water extra1 - if (Helpers::hasValue(wwExtra1_)) { - rootThermostat["wwextra1"] = wwExtra1_; - } - - // Warm water extra2 - if (Helpers::hasValue(wwExtra2_)) { - rootThermostat["wwextra2"] = wwExtra2_; - } - - // Warm Water circulation mode - if (Helpers::hasValue(wwCircMode_)) { - char s[10]; - if (model == EMS_DEVICE_FLAG_RC300 || model == EMS_DEVICE_FLAG_RC100) { - rootThermostat["wwcircmode"] = Helpers::render_enum(s, {F("off"), F("on"), F("auto"), F("own_prog")}, wwCircMode_); - } else { - rootThermostat["wwcircmode"] = Helpers::render_enum(s, {F("off"), F("on"), F("auto")}, wwCircMode_); - } - } - - return (rootThermostat.size()); -} - -// creates JSON doc from values, for each heating circuit -// if the mqtt_format is 0 then it will not perform the MQTT publish -// returns false if empty -bool Thermostat::export_values_hc(uint8_t mqtt_format, JsonObject & rootThermostat) { - uint8_t model = this->model(); - JsonObject dataThermostat; - bool has_data = false; - - // go through all the heating circuits - for (const auto & hc : heating_circuits_) { - if (hc->is_active()) { - has_data = true; - - // if the MQTT format is 'nested' or 'ha' then create the parent object hc - if (mqtt_format == Mqtt::Format::SINGLE) { - dataThermostat = rootThermostat; - } else { - char hc_name[10]; // hc{1-4} - snprintf_P(hc_name, 10, PSTR("hc%d"), hc->hc_num()); - dataThermostat = rootThermostat.createNestedObject(hc_name); - } - - // different logic on how temperature values are stored, depending on model - uint8_t setpoint_temp_divider; - uint8_t curr_temp_divider; - if (model == EMS_DEVICE_FLAG_EASY) { - setpoint_temp_divider = 100; - curr_temp_divider = 100; - } else if (model == EMS_DEVICE_FLAG_JUNKERS) { - setpoint_temp_divider = 10; - curr_temp_divider = 10; - } else { - setpoint_temp_divider = 2; - curr_temp_divider = 10; - } - - // Setpoint room temperature - if (Helpers::hasValue(hc->setpoint_roomTemp)) { - dataThermostat["seltemp"] = Helpers::round2((float)hc->setpoint_roomTemp / setpoint_temp_divider); - } - - // Current room temperature - if (Helpers::hasValue(hc->curr_roomTemp)) { - dataThermostat["currtemp"] = Helpers::round2((float)hc->curr_roomTemp / curr_temp_divider); - } - - if (Helpers::hasValue(hc->daytemp)) { - if (model == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) { - // Heat temperature - dataThermostat["heattemp"] = (float)hc->daytemp / 2; - } else if (model == EMSdevice::EMS_DEVICE_FLAG_RC300 || model == EMSdevice::EMS_DEVICE_FLAG_RC100) { - // Comfort temperature - dataThermostat["comforttemp"] = (float)hc->daytemp / 2; - } else { - // Day temperature - dataThermostat["daytemp"] = (float)hc->daytemp / 2; - } - } - - if (Helpers::hasValue(hc->nighttemp)) { - if (model == EMSdevice::EMS_DEVICE_FLAG_JUNKERS || model == EMSdevice::EMS_DEVICE_FLAG_RC300 || model == EMSdevice::EMS_DEVICE_FLAG_RC100) { - // Eco temperature - dataThermostat["ecotemp"] = (float)hc->nighttemp / 2; - } else { - // Night temperature - dataThermostat["nighttemp"] = (float)hc->nighttemp / 2; - } - } - - // Manual temperature - if (Helpers::hasValue(hc->manualtemp)) { - dataThermostat["manualtemp"] = (float)hc->manualtemp / 2; - } - - // Holiday temperature - if (Helpers::hasValue(hc->holidaytemp)) { - dataThermostat["holidaytemp"] = (float)hc->holidaytemp / 2; - } - - // Nofrost temperature - if (Helpers::hasValue(hc->nofrosttemp)) { - if (model == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) { - dataThermostat["nofrosttemp"] = (float)hc->nofrosttemp / 2; - } else { - dataThermostat["nofrosttemp"] = hc->nofrosttemp; - } - } - - // Heating Type - if (Helpers::hasValue(hc->heatingtype)) { - dataThermostat["heatingtype"] = hc->heatingtype; - } - - // Target flow temperature - if (Helpers::hasValue(hc->targetflowtemp)) { - dataThermostat["targetflowtemp"] = hc->targetflowtemp; - } - - // Offset temperature - if (Helpers::hasValue(hc->offsettemp)) { - if (model == EMSdevice::EMS_DEVICE_FLAG_RC300 || model == EMSdevice::EMS_DEVICE_FLAG_RC100) { - dataThermostat["offsettemp"] = hc->offsettemp; - } else { - dataThermostat["offsettemp"] = hc->offsettemp / 2; - } - } - - // Design temperature - if (Helpers::hasValue(hc->designtemp)) { - dataThermostat["designtemp"] = hc->designtemp; - } - - // Room influence - if (Helpers::hasValue(hc->roominfluence)) { - dataThermostat["roominfluence"] = hc->roominfluence; - } - - // Flow temperature offset - if (Helpers::hasValue(hc->flowtempoffset)) { - dataThermostat["flowtempoffset"] = hc->flowtempoffset; - } - - // Min Flow temperature offset - if (Helpers::hasValue(hc->minflowtemp)) { - dataThermostat["minflowtemp"] = hc->minflowtemp; - } - - // Max Flow temperature offset - if (Helpers::hasValue(hc->maxflowtemp)) { - dataThermostat["maxflowtemp"] = hc->maxflowtemp; - } - - // Summer temperature - if (Helpers::hasValue(hc->summertemp)) { - dataThermostat["summertemp"] = hc->summertemp; - } - - // Summer mode - if (Helpers::hasValue(hc->summer_setmode)) { - char s[7]; - dataThermostat["summermode"] = Helpers::render_enum(s, {F("summer"), F("auto"), F("winter")}, hc->summer_setmode); - } - - // mode - always force showing this when in HA so not to break HA's climate component - if ((Helpers::hasValue(hc->mode)) || (mqtt_format == Mqtt::Format::HA)) { - uint8_t hc_mode = hc->get_mode(model); - // if we're sending to HA the only valid mode types are heat, auto and off - if (mqtt_format == Mqtt::Format::HA) { - if ((hc_mode == HeatingCircuit::Mode::MANUAL) || (hc_mode == HeatingCircuit::Mode::DAY)) { - hc_mode = HeatingCircuit::Mode::HEAT; - } else if ((hc_mode == HeatingCircuit::Mode::NIGHT) || (hc_mode == HeatingCircuit::Mode::OFF)) { - hc_mode = HeatingCircuit::Mode::OFF; - } else { - hc_mode = HeatingCircuit::Mode::AUTO; - } - } - // Mode - dataThermostat["mode"] = mode_tostring(hc_mode); - } - - // special handling of mode type, for the RC35 replace with summer/holiday if set - // https://github.com/proddy/EMS-ESP/issues/373#issuecomment-619810209 - // Mode Type - if (Helpers::hasValue(hc->summer_mode) && hc->summer_mode) { - dataThermostat["modetype"] = FJSON("summer"); - } else if (Helpers::hasValue(hc->holiday_mode) && hc->holiday_mode) { - dataThermostat["modetype"] = FJSON("holiday"); - } else if (Helpers::hasValue(hc->mode_type)) { - dataThermostat["modetype"] = mode_tostring(hc->get_mode_type(model)); - } - - // if format is single, send immediately and clear object for next hc - // the topic will have the hc number appended - if (mqtt_format == Mqtt::Format::SINGLE) { - char topic[30]; - snprintf_P(topic, 30, PSTR("thermostat_data_hc%d"), hc->hc_num()); - Mqtt::publish(topic, rootThermostat); - rootThermostat.clear(); // clear object - } - } - } - - return (has_data); -} - -// set up HA MQTT Discovery -bool Thermostat::ha_config(bool force) { - if (!Mqtt::connected()) { - return false; - } +// publish HA config +bool Thermostat::publish_ha_config() { + StaticJsonDocument doc; + doc["uniq_id"] = F_(thermostat); - // if force, reset registered flag for main controller and all heating circuits - if (force) { - for (const auto & hc : heating_circuits_) { - hc->ha_registered(false); - } - ha_registered(false); - } + char stat_t[50]; + snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/thermostat_data"), System::hostname().c_str()); + doc["stat_t"] = stat_t; - // set up the main controller - if (!ha_registered()) { - register_mqtt_ha_config(); - ha_registered(true); - } + doc["name"] = FJSON("Thermostat Status"); + doc["val_tpl"] = FJSON("{{value_json.errorcode}}"); // default value - must have one, so we use errorcode + JsonObject dev = doc.createNestedObject("dev"); + dev["name"] = FJSON("EMS-ESP Thermostat"); + dev["sw"] = EMSESP_APP_VERSION; + dev["mf"] = brand_to_string(); + dev["mdl"] = name(); + JsonArray ids = dev.createNestedArray("ids"); + ids.add("ems-esp-thermostat"); - // check to see which heating circuits need to be added as HA climate components - // but only if it's active and there is a real value for the current room temperature (https://github.com/proddy/EMS-ESP/issues/582) - for (const auto & hc : heating_circuits_) { - if (hc->is_active() && !hc->ha_registered()) { - if (Helpers::hasValue(hc->curr_roomTemp)) { - register_mqtt_ha_config(hc->hc_num()); - hc->ha_registered(true); - } - } - } + char topic[100]; + snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/thermostat/config"), System::hostname().c_str()); + Mqtt::publish_ha(topic, doc.as()); // publish the config payload with retain flag return true; } @@ -770,10 +300,19 @@ std::shared_ptr Thermostat::heating_circuit(std::sha } // create a new heating circuit object - auto new_hc = std::make_shared(hc_num); + auto new_hc = std::make_shared(hc_num, model()); heating_circuits_.push_back(new_hc); - std::sort(heating_circuits_.begin(), heating_circuits_.end()); // sort based on hc number + // sort based on hc number so there's a nice order when displaying + std::sort(heating_circuits_.begin(), heating_circuits_.end()); + + // register the device values + register_device_values_hc(new_hc); + + // now create the HA topics to send to MQTT for each sensor + if (Mqtt::ha_enabled()) { + register_mqtt_ha_config_hc(hc_num); + } // set the flag saying we want its data during the next auto fetch toggle_fetch(monitor_typeids[hc_num - 1], toggle_); @@ -794,63 +333,10 @@ std::shared_ptr Thermostat::heating_circuit(std::sha return heating_circuits_.back(); // even after sorting, this should still point back to the newly created HC } -// publish config topic for HA MQTT Discovery for main thermostat values -// homeassistant/sensor/ems-esp/thermostat/config -void Thermostat::register_mqtt_ha_config() { - StaticJsonDocument doc; - doc["uniq_id"] = FJSON("thermostat"); - doc["ic"] = FJSON("mdi:home-thermometer-outline"); - - char stat_t[50]; - snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/thermostat_data"), System::hostname().c_str()); - doc["stat_t"] = stat_t; - - doc["name"] = FJSON("Thermostat Status"); - doc["val_tpl"] = FJSON("{{value_json.errorcode}}"); // default value - must have one, so we use errorcode - JsonObject dev = doc.createNestedObject("dev"); - dev["name"] = FJSON("EMS-ESP Thermostat"); - dev["sw"] = EMSESP_APP_VERSION; - dev["mf"] = brand_to_string(); - dev["mdl"] = name(); - JsonArray ids = dev.createNestedArray("ids"); - ids.add("ems-esp-thermostat"); - Mqtt::publish_ha(F("homeassistant/sensor/ems-esp/thermostat/config"), doc.as()); // publish the config payload with retain flag - - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(time), device_type(), "time", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(error), device_type(), "errorcode", nullptr, nullptr); - - uint8_t model = this->model(); - - if (model == EMS_DEVICE_FLAG_RC30_1) { - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(display), device_type(), "display", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(language), device_type(), "language", nullptr, nullptr); - } - - if (model == EMS_DEVICE_FLAG_RC300 || model == EMS_DEVICE_FLAG_RC100) { - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(dampedtemp), device_type(), "dampedtemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(building), device_type(), "building", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(minexttemp), device_type(), "minexttemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(floordry), device_type(), "floordry", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(floordrytemp), device_type(), "floordrytemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(wwmode), device_type(), "wwmode", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(wwtemp), device_type(), "wwtemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(wwcircmode), device_type(), "wwcircmode", nullptr, nullptr); - } - - if (model == EMS_DEVICE_FLAG_RC35 || model == EMS_DEVICE_FLAG_RC30_1) { - // excluding inttemp1, inttemp2, intoffset, minexttemp - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(dampedtemp), device_type(), "dampedtemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(building), device_type(), "building", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(minexttemp), device_type(), "minexttemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(wwmode), device_type(), "wwmode", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(wwcircmode), device_type(), "wwcircmode", nullptr, nullptr); - } -} - // publish config topic for HA MQTT Discovery for each of the heating circuit // e.g. homeassistant/climate/ems-esp/thermostat_hc1/config -void Thermostat::register_mqtt_ha_config(uint8_t hc_num) { - StaticJsonDocument doc; +void Thermostat::register_mqtt_ha_config_hc(uint8_t hc_num) { + StaticJsonDocument doc; char str1[20]; snprintf_P(str1, sizeof(str1), PSTR("Thermostat hc%d"), hc_num); @@ -872,7 +358,7 @@ void Thermostat::register_mqtt_ha_config(uint8_t hc_num) { doc["curr_temp_t"] = FJSON("~/thermostat_data"); char mode_str[30]; - snprintf_P(mode_str, sizeof(mode_str), PSTR("{{value_json.hc%d.mode}}"), hc_num); + snprintf_P(mode_str, sizeof(mode_str), PSTR("{{value_json.hc%d.hamode}}"), hc_num); doc["mode_stat_tpl"] = mode_str; char seltemp_str[30]; @@ -880,7 +366,7 @@ void Thermostat::register_mqtt_ha_config(uint8_t hc_num) { doc["temp_stat_tpl"] = seltemp_str; char currtemp_str[30]; - snprintf_P(currtemp_str, sizeof(currtemp_str), PSTR("{{value_json.hc%d.currtemp}}"), hc_num); + snprintf_P(currtemp_str, sizeof(currtemp_str), PSTR("{{value_json.hc%d.hatemp}}"), hc_num); doc["curr_temp_tpl"] = currtemp_str; doc["min_temp"] = FJSON("5"); @@ -901,8 +387,8 @@ void Thermostat::register_mqtt_ha_config(uint8_t hc_num) { JsonArray ids = dev.createNestedArray("ids"); ids.add("ems-esp-thermostat"); - std::string topic(100, '\0'); - snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/thermostat_hc%d/config"), hc_num); + char topic[100]; + snprintf_P(topic, sizeof(topic), PSTR("homeassistant/climate/%s/thermostat_hc%d/config"), System::hostname().c_str(), hc_num); Mqtt::publish_ha(topic, doc.as()); // publish the config payload with retain flag // enable the a special "thermostat_hc" topic to take both mode strings and floats for each of the heating circuits @@ -914,56 +400,6 @@ void Thermostat::register_mqtt_ha_config(uint8_t hc_num) { strlcpy(hc_name, "hc", 10); char s[3]; strlcat(hc_name, Helpers::itoa(s, hc_num), 10); - - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(mode), device_type(), "mode", nullptr, nullptr); - - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(seltemp), device_type(), "seltemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(currtemp), device_type(), "currtemp", F_(degrees), F_(icontemperature)); - - switch (this->model()) { - case EMS_DEVICE_FLAG_RC100: - case EMS_DEVICE_FLAG_RC300: - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(modetype), device_type(), "modetype", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(ecotemp), device_type(), "ecotemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(manualtemp), device_type(), "manualtemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(comforttemp), device_type(), "comforttemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(summertemp), device_type(), "summertemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(designtemp), device_type(), "designtemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(offsettemp), device_type(), "offsettemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(minflowtemp), device_type(), "minflowtemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(maxflowtemp), device_type(), "maxflowtemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(roominfluence), device_type(), "roominfluence", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(nofrosttemp), device_type(), "nofrosttemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(targetflowtemp), device_type(), "targetflowtemp", F_(degrees), F_(icontemperature)); - break; - case EMS_DEVICE_FLAG_RC20_2: - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(daytemp), device_type(), "daytemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(nighttemp), device_type(), "nighttemp", F_(degrees), F_(icontemperature)); - break; - case EMS_DEVICE_FLAG_RC30_1: - case EMS_DEVICE_FLAG_RC35: - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(modetype), device_type(), "modetype", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(nighttemp), device_type(), "nighttemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(daytemp), device_type(), "daytemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(designtemp), device_type(), "designtemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(offsettemp), device_type(), "offsettemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(holidaytemp), device_type(), "holidaytemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(targetflowtemp), device_type(), "targetflowtemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(summertemp), device_type(), "summertemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(nofrosttemp), device_type(), "nofrosttemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(roominfluence), device_type(), "roominfluence", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(minflowtemp), device_type(), "minflowtemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(maxflowtemp), device_type(), "maxflowtemp", F_(degrees), F_(icontemperature)); - break; - case EMS_DEVICE_FLAG_JUNKERS: - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(modetype), device_type(), "modetype", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(heattemp), device_type(), "heattemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(ecotemp), device_type(), "ecotemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(nofrosttemp), device_type(), "nofrosttemp", F_(degrees), F_(icontemperature)); - break; - default: - break; - } } // for HA specifically when receiving over MQTT in the thermostat topic @@ -986,8 +422,10 @@ bool Thermostat::thermostat_ha_cmd(const char * message, uint8_t hc_num) { } // decodes the thermostat mode for the heating circuit based on the thermostat type -// modes are off, manual, auto, day and night -uint8_t Thermostat::HeatingCircuit::get_mode(uint8_t model) const { +// modes are off, manual, auto, day, night and holiday +uint8_t Thermostat::HeatingCircuit::get_mode() const { + uint8_t model = get_model(); + if (!Helpers::hasValue(mode)) { return HeatingCircuit::Mode::UNKNOWN; } @@ -1029,29 +467,31 @@ uint8_t Thermostat::HeatingCircuit::get_mode(uint8_t model) const { // figures out the thermostat day/night mode depending on the thermostat type // mode types are day, night, eco, comfort -uint8_t Thermostat::HeatingCircuit::get_mode_type(uint8_t model) const { +uint8_t Thermostat::HeatingCircuit::get_mode_type() const { + uint8_t model = get_model(); + if (model == EMS_DEVICE_FLAG_JUNKERS) { - if (mode_type == 3) { + if (modetype == 3) { return HeatingCircuit::Mode::HEAT; - } else if (mode_type == 2) { + } else if (modetype == 2) { return HeatingCircuit::Mode::ECO; - } else if (mode_type == 1) { + } else if (modetype == 1) { return HeatingCircuit::Mode::NOFROST; } } else if ((model == EMS_DEVICE_FLAG_RC35) || (model == EMS_DEVICE_FLAG_RC30_1)) { - if (mode_type == 0) { + if (modetype == 0) { return HeatingCircuit::Mode::NIGHT; - } else if (mode_type == 1) { + } else if (modetype == 1) { return HeatingCircuit::Mode::DAY; } } else if (model == EMS_DEVICE_FLAG_RC300) { - if (mode_type == 0) { + if (modetype == 0) { return HeatingCircuit::Mode::ECO; - } else if (mode_type == 1) { + } else if (modetype == 1) { return HeatingCircuit::Mode::COMFORT; } } else if (model == EMS_DEVICE_FLAG_RC100) { - return HeatingCircuit::Mode::DAY; // no modes on these devices + return HeatingCircuit::Mode::DAY; // no other modes on these devices } return HeatingCircuit::Mode::DAY; @@ -1122,28 +562,33 @@ void Thermostat::process_RC20Set(std::shared_ptr telegram) { if (hc == nullptr) { return; } - changed_ |= telegram->read_value(hc->mode, 23); + has_update(telegram->read_value(hc->mode, 23)); } // type 0xAE - data from the RC20 thermostat (0x17) +// 17 00 AE 00 80 12 2E 00 D0 00 00 64 (#data=8) void Thermostat::process_RC20Monitor_2(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } - changed_ |= telegram->read_bitvalue(hc->mode_type, 0, 7); // day/night MSB 7th bit is day - changed_ |= telegram->read_value(hc->setpoint_roomTemp, 2, 1); // is * 2, force as single byte - changed_ |= telegram->read_value(hc->curr_roomTemp, 3); // is * 10 + has_update(telegram->read_bitvalue(hc->modetype, 0, 7)); // day/night MSB 7th bit is day + has_update(telegram->read_value(hc->setpoint_roomTemp, 2, 1)); // is * 2, force as single byte + has_update(telegram->read_value(hc->curr_roomTemp, 3)); // is * 10 } // 0xAD - for reading the mode from the RC20/ES72 thermostat (0x17) // see https://github.com/proddy/EMS-ESP/issues/334#issuecomment-611698259 +// offset: 01-nighttemp, 02-daytemp, 03-mode, 0B-program(1-9), 0D-setpoint_roomtemp(temporary) void Thermostat::process_RC20Set_2(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } - changed_ |= telegram->read_value(hc->mode, 3); + has_update(telegram->read_value(hc->nighttemp, 1)); // is * 2, + has_update(telegram->read_value(hc->daytemp, 2)); // is * 2, + has_update(telegram->read_value(hc->mode, 3)); + has_update(telegram->read_value(hc->program, 11)); // 1 .. 9 predefined programs } // 0xAF - for reading the roomtemperature from the RC20/ES72 thermostat (0x18, 0x19, ..) @@ -1152,7 +597,7 @@ void Thermostat::process_RC20Remote(std::shared_ptr telegram) { if (hc == nullptr) { return; } - changed_ |= telegram->read_value(hc->curr_roomTemp, 0); + has_update(telegram->read_value(hc->curr_roomTemp, 0)); } // type 0xB1 - data from the RC10 thermostat (0x17) @@ -1161,8 +606,9 @@ void Thermostat::process_RC10Monitor(std::shared_ptr telegram) { if (hc == nullptr) { return; } - changed_ |= telegram->read_value(hc->setpoint_roomTemp, 1, 1); // is * 2, force as single byte - changed_ |= telegram->read_value(hc->curr_roomTemp, 2); // is * 10 + + has_update(telegram->read_value(hc->setpoint_roomTemp, 1, 1)); // is * 2, force as single byte + has_update(telegram->read_value(hc->curr_roomTemp, 2)); // is * 10 } #pragma GCC diagnostic push @@ -1180,29 +626,29 @@ void Thermostat::process_JunkersSet(std::shared_ptr telegram) { if (hc == nullptr) { return; } - changed_ |= telegram->read_value(hc->daytemp, 17); // is * 2 - changed_ |= telegram->read_value(hc->nighttemp, 16); // is * 2 - changed_ |= telegram->read_value(hc->nofrosttemp, 15); // is * 2 + + has_update(telegram->read_value(hc->daytemp, 17)); // is * 2 + has_update(telegram->read_value(hc->nighttemp, 16)); // is * 2 + has_update(telegram->read_value(hc->nofrosttemp, 15)); // is * 2 } + // type 0x0179, ff void Thermostat::process_JunkersSet2(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } - changed_ |= telegram->read_value(hc->daytemp, 7); // is * 2 - changed_ |= telegram->read_value(hc->nighttemp, 6); // is * 2 - changed_ |= telegram->read_value(hc->nofrosttemp, 5); // is * 2 + + has_update(telegram->read_value(hc->daytemp, 7)); // is * 2 + has_update(telegram->read_value(hc->nighttemp, 6)); // is * 2 + has_update(telegram->read_value(hc->nofrosttemp, 5)); // is * 2 } // type 0xA3 - for external temp settings from the the RC* thermostats (e.g. RC35) void Thermostat::process_RCOutdoorTemp(std::shared_ptr telegram) { - changed_ |= telegram->read_value(dampedoutdoortemp_, 0); - if (dampedoutdoortemp_ == 0) { - dampedoutdoortemp_ = EMS_VALUE_INT_NOTSET; // special case for RC20's where the value is always 0 - } - changed_ |= telegram->read_value(tempsensor1_, 3); // sensor 1 - is * 10 - changed_ |= telegram->read_value(tempsensor2_, 5); // sensor 2 - is * 10 + has_update(telegram->read_value(dampedoutdoortemp_, 0)); + has_update(telegram->read_value(tempsensor1_, 3)); // sensor 1 - is * 10 + has_update(telegram->read_value(tempsensor2_, 5)); // sensor 2 - is * 10 } // 0x91 - data from the RC20 thermostat (0x17) - 15 bytes long @@ -1211,8 +657,9 @@ void Thermostat::process_RC20Monitor(std::shared_ptr telegram) { if (hc == nullptr) { return; } - changed_ |= telegram->read_value(hc->setpoint_roomTemp, 1, 1); // is * 2, force as single byte - changed_ |= telegram->read_value(hc->curr_roomTemp, 2); // is * 10 + + has_update(telegram->read_value(hc->setpoint_roomTemp, 1, 1)); // is * 2, force as single byte + has_update(telegram->read_value(hc->curr_roomTemp, 2)); // is * 10 } // type 0x0A - data from the Nefit Easy/TC100 thermostat (0x18) - 31 bytes long @@ -1221,27 +668,28 @@ void Thermostat::process_EasyMonitor(std::shared_ptr telegram) { if (hc == nullptr) { return; } - changed_ |= telegram->read_value(hc->curr_roomTemp, 8); // is * 100 - changed_ |= telegram->read_value(hc->setpoint_roomTemp, 10); // is * 100 + + has_update(telegram->read_value(hc->curr_roomTemp, 8)); // is * 100 + has_update(telegram->read_value(hc->setpoint_roomTemp, 10)); // is * 100 } // Settings Parameters - 0xA5 - RC30_1 void Thermostat::process_IBASettings(std::shared_ptr telegram) { // 22 - display line on RC35 - changed_ |= - telegram->read_value(ibaMainDisplay_, - 0); // display on Thermostat: 0 int. temp, 1 int. setpoint, 2 ext. temp., 3 burner temp., 4 ww temp, 5 functioning mode, 6 time, 7 data, 8 smoke temp - changed_ |= telegram->read_value(ibaLanguage_, 1); // language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian - changed_ |= telegram->read_value(ibaCalIntTemperature_, 2); // offset int. temperature sensor, by * 0.1 Kelvin - changed_ |= telegram->read_value(ibaBuildingType_, 6); // building type: 0 = light, 1 = medium, 2 = heavy - changed_ |= telegram->read_value(ibaMinExtTemperature_, 5); // min ext temp for heating curve, in deg., 0xF6=-10, 0x0 = 0, 0xFF=-1 - changed_ |= telegram->read_value(ibaClockOffset_, 12); // offset (in sec) to clock, 0xff = -1 s, 0x02 = 2 s + + // display on Thermostat: 0 int. temp, 1 int. setpoint, 2 ext. temp., 3 burner temp., 4 ww temp, 5 functioning mode, 6 time, 7 data, 8 smoke temp + has_update(telegram->read_value(ibaMainDisplay_, 0)); + has_update(telegram->read_value(ibaLanguage_, 1)); // language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian + has_update(telegram->read_value(ibaCalIntTemperature_, 2)); // offset int. temperature sensor, by * 0.1 Kelvin + has_update(telegram->read_value(ibaBuildingType_, 6)); // building type: 0 = light, 1 = medium, 2 = heavy + has_update(telegram->read_value(ibaMinExtTemperature_, 5)); // min ext temp for heating curve, in deg., 0xF6=-10, 0x0 = 0, 0xFF=-1 + has_update(telegram->read_value(ibaClockOffset_, 12)); // offset (in sec) to clock, 0xff = -1 s, 0x02 = 2 s } // Settings WW 0x37 - RC35 void Thermostat::process_RC35wwSettings(std::shared_ptr telegram) { - changed_ |= telegram->read_value(wwMode_, 2); // 0 off, 1-on, 2-auto - changed_ |= telegram->read_value(wwCircMode_, 3); // 0 off, 1-on, 2-auto + has_update(telegram->read_value(wwMode_, 2)); // 0 off, 1-on, 2-auto + has_update(telegram->read_value(wwCircMode_, 3)); // 0 off, 1-on, 2-auto } // type 0x6F - FR10/FR50/FR100/FR110/FR120 Junkers @@ -1255,11 +703,12 @@ void Thermostat::process_JunkersMonitor(std::shared_ptr telegram if (hc == nullptr) { return; } - changed_ |= telegram->read_value(hc->curr_roomTemp, 4); // value is * 10 - changed_ |= telegram->read_value(hc->setpoint_roomTemp, 2); // value is * 10 - changed_ |= telegram->read_value(hc->mode_type, 0); // 1 = nofrost, 2 = eco, 3 = heat - changed_ |= telegram->read_value(hc->mode, 1); // 1 = manual, 2 = auto + has_update(telegram->read_value(hc->curr_roomTemp, 4)); // value is * 10 + has_update(telegram->read_value(hc->setpoint_roomTemp, 2)); // value is * 10 + + has_update(telegram->read_value(hc->modetype, 0)); // 1 = nofrost, 2 = eco, 3 = heat + has_update(telegram->read_value(hc->mode, 1)); // 1 = manual, 2 = auto } // type 0x02A5 - data from the Nefit RC1010/3000 thermostat (0x18) and RC300/310s on 0x10 @@ -1268,10 +717,10 @@ void Thermostat::process_RC300Monitor(std::shared_ptr telegram) if (hc == nullptr) { return; } - changed_ |= telegram->read_value(hc->curr_roomTemp, 0); // is * 10 - changed_ |= telegram->read_bitvalue(hc->mode_type, 10, 1); - changed_ |= telegram->read_bitvalue(hc->mode, 10, 0); // bit 1, mode (auto=1 or manual=0) + has_update(telegram->read_value(hc->curr_roomTemp, 0)); // is * 10 + has_update(telegram->read_bitvalue(hc->modetype, 10, 1)); + has_update(telegram->read_bitvalue(hc->mode, 10, 0)); // bit 1, mode (auto=1 or manual=0) // if manual, take the current setpoint temp at pos 6 // if auto, take the next setpoint temp at pos 7 @@ -1280,9 +729,9 @@ void Thermostat::process_RC300Monitor(std::shared_ptr telegram) // pos 3 actual setpoint (optimized), i.e. changes with temporary change, summer/holiday-modes // pos 6 actual setpoint according to programmed changes eco/comfort // pos 7 next setpoint in the future, time to next setpoint in pos 8/9 - changed_ |= telegram->read_value(hc->setpoint_roomTemp, 3, 1); // is * 2, force as single byte - changed_ |= telegram->read_bitvalue(hc->summer_mode, 2, 4); - changed_ |= telegram->read_value(hc->targetflowtemp, 4); + has_update(telegram->read_value(hc->setpoint_roomTemp, 3, 1)); // is * 2, force as single byte + has_update(telegram->read_bitvalue(hc->summermode, 2, 4)); + has_update(telegram->read_value(hc->targetflowtemp, 4)); } // type 0x02B9 EMS+ for reading from RC300/RC310 thermostat @@ -1291,20 +740,22 @@ void Thermostat::process_RC300Set(std::shared_ptr telegram) { if (hc == nullptr) { return; } + // NOTE when setting the room temp we pick from two values, hopefully one is correct! // manual is position 10 // comfort is position 2, there are 3 levels in pos 3, 2, 1 // eco is position 4 // auto is position 8, temporary until next switch // actual setpoint taken from RC300Monitor (Michael 12.06.2020) - // changed_ |= telegram->read_value(hc->setpoint_roomTemp, 8, 1); // single byte conversion, value is * 2 - auto? - // changed_ |= telegram->read_value(hc->setpoint_roomTemp, 10, 1); // single byte conversion, value is * 2 - manual + // has_update(telegram->read_value(hc->setpoint_roomTemp, 8, 1); // single byte conversion, value is * 2 - auto? + // has_update(telegram->read_value(hc->setpoint_roomTemp, 10, 1); // single byte conversion, value is * 2 - manual // check why mode is both in the Monitor and Set for the RC300. It'll be read twice! - // changed_ |= telegram->read_value(hc->mode, 0); // Auto = xFF, Manual = x00 eg. 10 00 FF 08 01 B9 FF - changed_ |= telegram->read_value(hc->daytemp, 2); // is * 2 - changed_ |= telegram->read_value(hc->nighttemp, 4); // is * 2 - changed_ |= telegram->read_value(hc->manualtemp, 10); // is * 2 + // has_update(telegram->read_value(hc->mode, 0); // Auto = xFF, Manual = x00 eg. 10 00 FF 08 01 B9 FF + has_update(telegram->read_value(hc->daytemp, 2)); // is * 2 + has_update(telegram->read_value(hc->nighttemp, 4)); // is * 2 + has_update(telegram->read_value(hc->manualtemp, 10)); // is * 2 + has_update(telegram->read_value(hc->program, 11)); // timer program 1 or 2 } // types 0x2AF ff @@ -1313,16 +764,19 @@ void Thermostat::process_RC300Summer(std::shared_ptr telegram) { if (hc == nullptr) { return; } - changed_ |= telegram->read_value(hc->roominfluence, 0); - changed_ |= telegram->read_value(hc->offsettemp, 2); - changed_ |= telegram->read_value(hc->summertemp, 6); - changed_ |= telegram->read_value(hc->summer_setmode, 7); + + has_update(telegram->read_value(hc->roominfluence, 0)); + has_update(telegram->read_value(hc->offsettemp, 2)); + has_update(telegram->read_value(hc->summertemp, 6)); + has_update(telegram->read_value(hc->summer_setmode, 7)); + if (hc->heatingtype < 3) { - changed_ |= telegram->read_value(hc->designtemp, 4); + has_update(telegram->read_value(hc->designtemp, 4)); } else { - changed_ |= telegram->read_value(hc->designtemp, 5); + has_update(telegram->read_value(hc->designtemp, 5)); } - changed_ |= telegram->read_value(hc->minflowtemp, 8); + + has_update(telegram->read_value(hc->minflowtemp, 8)); } // types 0x29B ff @@ -1331,57 +785,61 @@ void Thermostat::process_RC300Curve(std::shared_ptr telegram) { if (hc == nullptr) { return; } - changed_ |= telegram->read_value(hc->heatingtype, 1); // 1=radiator, 2=convector, 3=floor - changed_ |= telegram->read_value(hc->nofrosttemp, 6); + + has_update(telegram->read_value(hc->controlmode, 0)); // 1-outdoor, 2-simple, 3-MPC, 4-room, 5-power, 6-const + has_update(telegram->read_value(hc->heatingtype, 1)); // 1=radiator, 2=convector, 3=floor + has_update(telegram->read_value(hc->nofrosttemp, 6)); + if (hc->heatingtype < 3) { - changed_ |= telegram->read_value(hc->maxflowtemp, 8); + has_update(telegram->read_value(hc->maxflowtemp, 8)); } else { - changed_ |= telegram->read_value(hc->maxflowtemp, 7); + has_update(telegram->read_value(hc->maxflowtemp, 7)); } } // types 0x31B (and 0x31C?) void Thermostat::process_RC300WWtemp(std::shared_ptr telegram) { - changed_ |= telegram->read_value(wwTemp_, 0); - changed_ |= telegram->read_value(wwTempLow_, 1); + has_update(telegram->read_value(wwTemp_, 0)); + has_update(telegram->read_value(wwTempLow_, 1)); } // type 02F5 void Thermostat::process_RC300WWmode(std::shared_ptr telegram) { // circulation pump see: https://github.com/Th3M3/buderus_ems-wiki/blob/master/Einstellungen%20der%20Bedieneinheit%20RC310.md - changed_ |= telegram->read_value(wwCircPump_, 1); // FF=off, 0=on ? - changed_ |= telegram->read_value(wwMode_, 2); // 0=off, 1=low, 2=high, 3=auto, 4=own prog - changed_ |= telegram->read_value(wwCircMode_, 3); // 0=off, 1=on, 2=auto, 4=own? + has_update(telegram->read_value(wwCircPump_, 1)); // FF=off, 0=on ? + + has_update(telegram->read_value(wwMode_, 2)); // 0=off, 1=low, 2=high, 3=auto, 4=own prog + has_update(telegram->read_value(wwCircMode_, 3)); // 0=off, 1=on, 2=auto, 4=own? } // types 0x31D and 0x31E void Thermostat::process_RC300WWmode2(std::shared_ptr telegram) { // 0x31D for WW system 1, 0x31E for WW system 2 - if (telegram->type_id == 0x031D) { - changed_ |= telegram->read_value(wwExtra1_, 0); // 0=no, 1=yes - } else { - changed_ |= telegram->read_value(wwExtra2_, 0); // 0=no, 1=yes - } // pos 1 = holiday mode // pos 2 = current status of DHW setpoint // pos 3 = current status of DHW circulation pump + if (telegram->type_id == 0x031D) { + has_update(telegram->read_value(wwExtra1_, 0)); // 0=no, 1=yes + } else { + has_update(telegram->read_value(wwExtra2_, 0)); // 0=no, 1=yes + } } // 0x23A damped outdoor temp void Thermostat::process_RC300OutdoorTemp(std::shared_ptr telegram) { - changed_ |= telegram->read_value(dampedoutdoortemp2_, 0); // is *10 + has_update(telegram->read_value(dampedoutdoortemp2_, 0)); // is *10 } // 0x240 RC300 parameter void Thermostat::process_RC300Settings(std::shared_ptr telegram) { - changed_ |= telegram->read_value(ibaBuildingType_, 9); // 1=light, 2=medium, 3=heavy - changed_ |= telegram->read_value(ibaMinExtTemperature_, 10); + has_update(telegram->read_value(ibaBuildingType_, 9)); // 1=light, 2=medium, 3=heavy + has_update(telegram->read_value(ibaMinExtTemperature_, 10)); } // 0x267 RC300 floordrying void Thermostat::process_RC300Floordry(std::shared_ptr telegram) { - changed_ |= telegram->read_value(floordrystatus_, 0); - changed_ |= telegram->read_value(floordrytemp_, 1); + has_update(telegram->read_value(floordrystatus_, 0)); + has_update(telegram->read_value(floordrytemp_, 1)); } // type 0x41 - data from the RC30 thermostat(0x10) - 14 bytes long @@ -1390,8 +848,9 @@ void Thermostat::process_RC30Monitor(std::shared_ptr telegram) { if (hc == nullptr) { return; } - changed_ |= telegram->read_value(hc->setpoint_roomTemp, 1, 1); // is * 2, force as single byte - changed_ |= telegram->read_value(hc->curr_roomTemp, 2); + + has_update(telegram->read_value(hc->setpoint_roomTemp, 1, 1)); // is * 2, force as single byte + has_update(telegram->read_value(hc->curr_roomTemp, 2)); } // type 0xA7 - for reading the mode from the RC30 thermostat (0x10) @@ -1400,7 +859,8 @@ void Thermostat::process_RC30Set(std::shared_ptr telegram) { if (hc == nullptr) { return; } - changed_ |= telegram->read_value(hc->mode, 23); + + has_update(telegram->read_value(hc->mode, 23)); } // type 0x3E (HC1), 0x48 (HC2), 0x52 (HC3), 0x5C (HC4) - data from the RC35 thermostat (0x10) - 16 bytes @@ -1416,14 +876,15 @@ void Thermostat::process_RC35Monitor(std::shared_ptr telegram) { if (hc == nullptr) { return; } - changed_ |= telegram->read_value(hc->setpoint_roomTemp, 2, 1); // is * 2, force to single byte, is 0 in summermode - changed_ |= telegram->read_value(hc->curr_roomTemp, 3); // is * 10 - or 0x7D00 if thermostat is mounted on boiler - changed_ |= telegram->read_bitvalue(hc->mode_type, 1, 1); - changed_ |= telegram->read_bitvalue(hc->summer_mode, 1, 0); - changed_ |= telegram->read_bitvalue(hc->holiday_mode, 0, 5); + has_update(telegram->read_value(hc->setpoint_roomTemp, 2, 1)); // is * 2, force to single byte, is 0 in summermode + has_update(telegram->read_value(hc->curr_roomTemp, 3)); // is * 10 - or 0x7D00 if thermostat is mounted on boiler - changed_ |= telegram->read_value(hc->targetflowtemp, 14); + has_update(telegram->read_bitvalue(hc->modetype, 1, 1)); + has_update(telegram->read_bitvalue(hc->summermode, 1, 0)); + has_update(telegram->read_bitvalue(hc->holidaymode, 0, 5)); + + has_update(telegram->read_value(hc->targetflowtemp, 14)); } // type 0x3D (HC1), 0x47 (HC2), 0x51 (HC3), 0x5B (HC4) - Working Mode Heating - for reading the mode from the RC35 thermostat (0x10) @@ -1437,43 +898,57 @@ void Thermostat::process_RC35Set(std::shared_ptr telegram) { if (hc == nullptr) { return; } - changed_ |= telegram->read_value(hc->heatingtype, 0); // 0- off, 1-radiator, 2-convector, 3-floor - changed_ |= telegram->read_value(hc->nighttemp, 1); // is * 2 - changed_ |= telegram->read_value(hc->daytemp, 2); // is * 2 - changed_ |= telegram->read_value(hc->holidaytemp, 3); // is * 2 - changed_ |= telegram->read_value(hc->roominfluence, 4); // is * 1 - changed_ |= telegram->read_value(hc->offsettemp, 6); // is * 2 - changed_ |= telegram->read_value(hc->mode, 7); // night, day, auto - - changed_ |= telegram->read_value(hc->summertemp, 22); // is * 1 - changed_ |= telegram->read_value(hc->nofrosttemp, 23); // is * 1 - changed_ |= telegram->read_value(hc->flowtempoffset, 24); // is * 1, only in mixed circuits - changed_ |= telegram->read_value(hc->minflowtemp, 16); + + has_update(telegram->read_value(hc->heatingtype, 0)); // 0- off, 1-radiator, 2-convector, 3-floor + has_update(telegram->read_value(hc->nighttemp, 1)); // is * 2 + has_update(telegram->read_value(hc->daytemp, 2)); // is * 2 + has_update(telegram->read_value(hc->holidaytemp, 3)); // is * 2 + has_update(telegram->read_value(hc->roominfluence, 4)); // is * 1 + has_update(telegram->read_value(hc->offsettemp, 6)); // is * 2 + has_update(telegram->read_value(hc->mode, 7)); // night, day, auto + + has_update(telegram->read_value(hc->summertemp, 22)); // is * 1 + has_update(telegram->read_value(hc->nofrosttemp, 23)); // is * 1 + has_update(telegram->read_value(hc->flowtempoffset, 24)); // is * 1, only in mixed circuits + has_update(telegram->read_value(hc->reducemode, 25)); // 0-nofrost, 1-reduce, 2-roomhold, 3-outdoorhold + has_update(telegram->read_value(hc->controlmode, 33)); // 0-outdoortemp, 1-roomtemp + // has_update(telegram->read_value(hc->noreducetemp, 38)); // outdoor temperature for no reduce + has_update(telegram->read_value(hc->minflowtemp, 16)); if (hc->heatingtype == 3) { - changed_ |= telegram->read_value(hc->designtemp, 36); // is * 1 - changed_ |= telegram->read_value(hc->maxflowtemp, 35); // is * 1 + has_update(telegram->read_value(hc->designtemp, 36)); // is * 1 + has_update(telegram->read_value(hc->maxflowtemp, 35)); // is * 1 } else { - changed_ |= telegram->read_value(hc->designtemp, 17); // is * 1 - changed_ |= telegram->read_value(hc->maxflowtemp, 15); // is * 1 + has_update(telegram->read_value(hc->designtemp, 17)); // is * 1 + has_update(telegram->read_value(hc->maxflowtemp, 15)); // is * 1 } } +// type 0x3F (HC1), 0x49 (HC2), 0x53 (HC3), 0x5D (HC4) - timer setting +void Thermostat::process_RC35Timer(std::shared_ptr telegram) { + std::shared_ptr hc = heating_circuit(telegram); + if (hc == nullptr) { + return; + } + + has_update(telegram->read_value(hc->program, 84)); // 0 .. 10, 0-userprogram 1, 10-userprogram 2 +} + // process_RCTime - type 0x06 - date and time from a thermostat - 14 bytes long void Thermostat::process_RCTime(std::shared_ptr telegram) { if (flags() == EMS_DEVICE_FLAG_EASY) { return; // not supported } + if (telegram->message_length < 7) { return; } + if (telegram->message_data[7] & 0x0C) { // date and time not valid set_datetime("ntp", -1); // set from NTP return; } - if (datetime_.empty()) { - datetime_.resize(25, '\0'); - } - auto timeold = datetime_; + + auto timeold = dateTime_; // render time to HH:MM:SS DD/MM/YYYY // had to create separate buffers because of how printf works char buf1[6]; @@ -1482,8 +957,8 @@ void Thermostat::process_RCTime(std::shared_ptr telegram) { char buf4[6]; char buf5[6]; char buf6[6]; - snprintf_P(&datetime_[0], - datetime_.capacity() + 1, + snprintf_P(dateTime_, + sizeof(dateTime_), PSTR("%s:%s:%s %s/%s/%s"), Helpers::smallitoa(buf1, telegram->message_data[2]), // hour Helpers::smallitoa(buf2, telegram->message_data[4]), // minute @@ -1492,28 +967,24 @@ void Thermostat::process_RCTime(std::shared_ptr telegram) { Helpers::smallitoa(buf5, telegram->message_data[1]), // month Helpers::itoa(buf6, telegram->message_data[0] + 2000) // year ); - if (timeold != datetime_) { - changed_ = true; - } + + has_update((strcmp(timeold, dateTime_) != 0)); } -// process_RCError - type 0xA2 - error maeesage - 14 bytes long +// process_RCError - type 0xA2 - error message - 14 bytes long // 10 00 A2 00 41 32 32 03 30 00 02 00 00 00 00 00 00 02 CRC // A 2 2 816 void Thermostat::process_RCError(std::shared_ptr telegram) { - if (errorCode_.empty()) { - errorCode_.resize(10, '\0'); - } char buf[4]; buf[0] = telegram->message_data[0]; buf[1] = telegram->message_data[1]; buf[2] = telegram->message_data[2]; buf[3] = 0; - changed_ |= telegram->read_value(errorNumber_, 3); - - snprintf_P(&errorCode_[0], errorCode_.capacity() + 1, PSTR("%s(%d)"), buf, errorNumber_); + has_update(telegram->read_value(errorNumber_, 3)); + snprintf_P(errorCode_, sizeof(errorCode_), PSTR("%s(%d)"), buf, errorNumber_); } -// 0x12 + +// 0x12 error log void Thermostat::process_RCErrorMessage(std::shared_ptr telegram) { // data: displaycode(2), errornumber(2), year, month, hour, day, minute, duration(2), src-addr if (telegram->message_data[4] & 0x80) { // valid date @@ -1670,6 +1141,7 @@ bool Thermostat::set_control(const char * value, const int8_t id) { // sets the thermostat ww working mode, where mode is a string, ems and ems+ bool Thermostat::set_wwmode(const char * value, const int8_t id) { uint8_t set = 0xFF; + if ((model() == EMS_DEVICE_FLAG_RC300) || (model() == EMS_DEVICE_FLAG_RC100)) { if (!Helpers::value2enum(value, set, {F("off"), F("low"), F("high"), F("auto"), F("own")})) { LOG_WARNING(F("Set warm water mode: Invalid mode")); @@ -1688,7 +1160,7 @@ bool Thermostat::set_wwmode(const char * value, const int8_t id) { return true; } -// Set wwhigh temperature, ems+ +// Set ww temperature, ems+ bool Thermostat::set_wwtemp(const char * value, const int8_t id) { int t = 0; if (!Helpers::value2number(value, t)) { @@ -1729,6 +1201,7 @@ bool Thermostat::set_wwonetime(const char * value, const int8_t id) { // sets the thermostat ww circulation working mode, where mode is a string bool Thermostat::set_wwcircmode(const char * value, const int8_t id) { uint8_t set = 0xFF; + if ((model() == EMS_DEVICE_FLAG_RC300) || (model() == EMS_DEVICE_FLAG_RC100)) { if (!Helpers::value2enum(value, set, {F("off"), F("on"), F("auto"), F("own")})) { LOG_WARNING(F("Set warm water circulation mode: Invalid mode")); @@ -2025,6 +1498,81 @@ bool Thermostat::set_summermode(const char * value, const int8_t id) { return true; } +// sets the thermostat reducemode for RC35 +bool Thermostat::set_reducemode(const char * value, const int8_t id) { + uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; + std::shared_ptr hc = heating_circuit(hc_num); + if (hc == nullptr) { + LOG_WARNING(F("Setting reduce mode: Heating Circuit %d not found or activated"), hc_num); + return false; + } + uint8_t set = 0xFF; + if (!Helpers::value2enum(value, set, {F("nofrost"), F("reduce"), F("room"), F("outdoor")})) { + LOG_WARNING(F("Setting reduce mode: Invalid mode")); + return false; + } + LOG_INFO(F("Setting reduce mode to %s for heating circuit %d"), value, hc->hc_num()); + write_command(set_typeids[hc->hc_num() - 1], 25, set, set_typeids[hc->hc_num() - 1]); + return true; +} + +// sets the thermostat controlmode for RC35, RC300 +bool Thermostat::set_controlmode(const char * value, const int8_t id) { + uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; + std::shared_ptr hc = heating_circuit(hc_num); + if (hc == nullptr) { + LOG_WARNING(F("Setting control mode: Heating Circuit %d not found or activated"), hc_num); + return false; + } + uint8_t set = 0xFF; + if (model() == EMS_DEVICE_FLAG_RC300 || model() == EMS_DEVICE_FLAG_RC100) { + if (Helpers::value2enum(value, set, {F("off"), F("outdoor"), F("simple"), F("MPC"), F("room"), F("power"), F("const")})) { + LOG_INFO(F("Setting control mode to %d for heating circuit %d"), set, hc->hc_num()); + write_command(curve_typeids[hc->hc_num() - 1], 0, set, curve_typeids[hc->hc_num() - 1]); + return true; + } + } else if (model() == EMS_DEVICE_FLAG_RC35 || model() == EMS_DEVICE_FLAG_RC30_1) { + if (Helpers::value2enum(value, set, {F("outdoor"), F("room")})) { + LOG_INFO(F("Setting control mode to %d for heating circuit %d"), set, hc->hc_num()); + write_command(set_typeids[hc->hc_num() - 1], 33, set, set_typeids[hc->hc_num() - 1]); + return true; + } + } + LOG_WARNING(F("Setting control mode: Invalid mode")); + return false; +} + +// sets the thermostat program for RC35 and RC20 +bool Thermostat::set_program(const char * value, const int8_t id) { + uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; + std::shared_ptr hc = heating_circuit(hc_num); + if (hc == nullptr) { + LOG_WARNING(F("Setting program: Heating Circuit %d not found or activated"), hc_num); + return false; + } + + int set = 0xFF; + if (!Helpers::value2number(value, set)) { + LOG_WARNING(F("Setting program: Invalid number")); + return false; + } + + if (set < 0 || set > 11) { + LOG_WARNING(F("Setting program: Invalid number")); + return false; + } + + LOG_INFO(F("Setting program to %d for heating circuit %d"), set, hc->hc_num()); + if (model() == EMS_DEVICE_FLAG_RC20_2 && set > 0 && set < 10) { + write_command(set_typeids[hc->hc_num() - 1], 11, set, set_typeids[hc->hc_num() - 1]); + } else if ((model() == EMS_DEVICE_FLAG_RC35) || (model() == EMS_DEVICE_FLAG_RC30_1)) { + write_command(timer_typeids[hc->hc_num() - 1], 84, set, timer_typeids[hc->hc_num() - 1]); + } else if ((model() == EMS_DEVICE_FLAG_RC300 || model() == EMS_DEVICE_FLAG_RC100) && (set == 0 || set == 1)) { + write_command(set_typeids[hc->hc_num() - 1], 11, set, set_typeids[hc->hc_num() - 1]); + } + + return true; +} // sets the thermostat temp, where mode is a string bool Thermostat::set_temperature(const float temperature, const std::string & mode, const uint8_t hc_num) { @@ -2167,7 +1715,7 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co break; default: case HeatingCircuit::Mode::AUTO: - uint8_t mode_ = hc->get_mode(flags()); + uint8_t mode_ = hc->get_mode(); if (mode_ == HeatingCircuit::Mode::MANUAL) { offset = 0x0A; // manual offset } else { @@ -2187,8 +1735,8 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co break; default: case HeatingCircuit::Mode::AUTO: // automatic selection, if no type is defined, we use the standard code - uint8_t mode_type = hc->get_mode_type(flags()); - offset = (mode_type == HeatingCircuit::Mode::NIGHT) ? EMS_OFFSET_RC20_2_Set_temp_night : EMS_OFFSET_RC20_2_Set_temp_day; + uint8_t modetype = hc->get_mode_type(); + offset = (modetype == HeatingCircuit::Mode::NIGHT) ? EMS_OFFSET_RC20_2_Set_temp_night : EMS_OFFSET_RC20_2_Set_temp_day; break; } @@ -2247,7 +1795,7 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co case HeatingCircuit::Mode::AUTO: // automatic selection, if no type is defined, we use the standard code validate_typeid = monitor_typeids[hc->hc_num() - 1]; //get setpoint roomtemp back if (model == EMS_DEVICE_FLAG_RC35) { - uint8_t mode_ = hc->get_mode(flags()); + uint8_t mode_ = hc->get_mode(); if (mode_ == HeatingCircuit::Mode::NIGHT) { offset = EMS_OFFSET_RC35Set_temp_night; } else if (mode_ == HeatingCircuit::Mode::DAY) { @@ -2256,8 +1804,8 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co offset = EMS_OFFSET_RC35Set_seltemp; // https://github.com/proddy/EMS-ESP/issues/310 } } else { - uint8_t mode_type = hc->get_mode_type(flags()); - offset = (mode_type == HeatingCircuit::Mode::NIGHT) ? EMS_OFFSET_RC35Set_temp_night : EMS_OFFSET_RC35Set_temp_day; + uint8_t modetype = hc->get_mode_type(); + offset = (modetype == HeatingCircuit::Mode::NIGHT) ? EMS_OFFSET_RC35Set_temp_night : EMS_OFFSET_RC35Set_temp_day; } break; } @@ -2281,10 +1829,10 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co break; default: case HeatingCircuit::Mode::AUTO: // automatic selection, if no type is defined, we use the standard code - uint8_t mode_type = hc->get_mode_type(flags()); - if (mode_type == HeatingCircuit::Mode::NIGHT || mode_type == HeatingCircuit::Mode::ECO) { + uint8_t modetype = hc->get_mode_type(); + if (modetype == HeatingCircuit::Mode::NIGHT || modetype == HeatingCircuit::Mode::ECO) { offset = EMS_OFFSET_JunkersSetMessage_night_temp; - } else if (mode_type == HeatingCircuit::Mode::DAY || mode_type == HeatingCircuit::Mode::HEAT) { + } else if (modetype == HeatingCircuit::Mode::DAY || modetype == HeatingCircuit::Mode::HEAT) { offset = EMS_OFFSET_JunkersSetMessage_day_temp; } else { offset = EMS_OFFSET_JunkersSetMessage_no_frost_temp; @@ -2308,10 +1856,10 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co break; default: case HeatingCircuit::Mode::AUTO: // automatic selection, if no type is defined, we use the standard code - uint8_t mode_type = hc->get_mode_type(flags()); - if (mode_type == HeatingCircuit::Mode::NIGHT || mode_type == HeatingCircuit::Mode::ECO) { + uint8_t modetype = hc->get_mode_type(); + if (modetype == HeatingCircuit::Mode::NIGHT || modetype == HeatingCircuit::Mode::ECO) { offset = EMS_OFFSET_JunkersSetMessage2_eco_temp; - } else if (mode_type == HeatingCircuit::Mode::DAY || mode_type == HeatingCircuit::Mode::HEAT) { + } else if (modetype == HeatingCircuit::Mode::DAY || modetype == HeatingCircuit::Mode::HEAT) { offset = EMS_OFFSET_JunkersSetMessage2_heat_temp; } else { offset = EMS_OFFSET_JunkersSetMessage2_no_frost_temp; @@ -2426,8 +1974,7 @@ void Thermostat::add_commands() { register_mqtt_cmd(F("mode"), [&](const char * value, const int8_t id) { return set_mode(value, id); }); register_mqtt_cmd(F("datetime"), [&](const char * value, const int8_t id) { return set_datetime(value, id); }); - uint8_t model = this->model(); - switch (model) { + switch (model()) { case EMS_DEVICE_FLAG_RC100: case EMS_DEVICE_FLAG_RC300: register_mqtt_cmd(F("manualtemp"), [&](const char * value, const int8_t id) { return set_manualtemp(value, id); }); @@ -2448,10 +1995,13 @@ void Thermostat::add_commands() { register_mqtt_cmd(F("maxflowtemp"), [&](const char * value, const int8_t id) { return set_maxflowtemp(value, id); }); register_mqtt_cmd(F("minexttemp"), [&](const char * value, const int8_t id) { return set_minexttemp(value, id); }); register_mqtt_cmd(F("roominfluence"), [&](const char * value, const int8_t id) { return set_roominfluence(value, id); }); + register_mqtt_cmd(F("program"), [&](const char * value, const int8_t id) { return set_program(value, id); }); + register_mqtt_cmd(F("controlmode"), [&](const char * value, const int8_t id) { return set_controlmode(value, id); }); break; case EMS_DEVICE_FLAG_RC20_2: register_mqtt_cmd(F("nighttemp"), [&](const char * value, const int8_t id) { return set_nighttemp(value, id); }); register_mqtt_cmd(F("daytemp"), [&](const char * value, const int8_t id) { return set_daytemp(value, id); }); + register_mqtt_cmd(F("program"), [&](const char * value, const int8_t id) { return set_program(value, id); }); break; case EMS_DEVICE_FLAG_RC30_1: // only RC30_1 register_mqtt_cmd(F("clockoffset"), [&](const char * value, const int8_t id) { return set_clockoffset(value, id); }); @@ -2479,6 +2029,9 @@ void Thermostat::add_commands() { register_mqtt_cmd(F("flowtempoffset"), [&](const char * value, const int8_t id) { return set_flowtempoffset(value, id); }); register_mqtt_cmd(F("minflowtemp"), [&](const char * value, const int8_t id) { return set_minflowtemp(value, id); }); register_mqtt_cmd(F("maxflowtemp"), [&](const char * value, const int8_t id) { return set_maxflowtemp(value, id); }); + register_mqtt_cmd(F("reducemode"), [&](const char * value, const int8_t id) { return set_reducemode(value, id); }); + register_mqtt_cmd(F("program"), [&](const char * value, const int8_t id) { return set_program(value, id); }); + register_mqtt_cmd(F("controlmode"), [&](const char * value, const int8_t id) { return set_controlmode(value, id); }); break; case EMS_DEVICE_FLAG_JUNKERS: register_mqtt_cmd(F("nofrosttemp"), [&](const char * value, const int8_t id) { return set_nofrosttemp(value, id); }); @@ -2490,4 +2043,247 @@ void Thermostat::add_commands() { } } +// register main device values, top level for all thermostats (non heating circuit) +void Thermostat::register_device_values() { + uint8_t model = this->model(); + + std::string empty(""); + + // Common for all thermostats + register_device_value(empty, &dateTime_, DeviceValueType::TEXT, {}, F("dateTime"), F("Date/Time"), DeviceValueUOM::NONE); + register_device_value(empty, &errorCode_, DeviceValueType::TEXT, {}, F("errorCode"), F("Error code"), DeviceValueUOM::NONE); + register_device_value(empty, &lastCode_, DeviceValueType::TEXT, {}, F("lastCode"), F("Last error"), DeviceValueUOM::NONE); + register_device_value(empty, &wwTemp_, DeviceValueType::UINT, {}, F("wwTemp"), F("Warm water high temperature"), DeviceValueUOM::DEGREES); + register_device_value(empty, &wwTempLow_, DeviceValueType::UINT, {}, F("wwTempLow"), F("Warm water low temperature"), DeviceValueUOM::DEGREES); + register_device_value(empty, &wwExtra1_, DeviceValueType::UINT, {}, F("wwExtra1"), F("Warm water circuit 1 extra"), DeviceValueUOM::DEGREES); + register_device_value(empty, &wwExtra2_, DeviceValueType::UINT, {}, F("wwExtra2"), F("Warm water circuit 2 extra"), DeviceValueUOM::DEGREES); + register_device_value(empty, &tempsensor1_, DeviceValueType::USHORT, {F("10")}, F("inttemp1"), F("Temperature sensor 1"), DeviceValueUOM::DEGREES); + register_device_value(empty, &tempsensor2_, DeviceValueType::USHORT, {F("10")}, F("inttemp2"), F("Temperature sensor 2"), DeviceValueUOM::DEGREES); + register_device_value(empty, &ibaCalIntTemperature_, DeviceValueType::INT, {F("2")}, F("intoffset"), F("Offset int. temperature"), DeviceValueUOM::DEGREES); + register_device_value(empty, + &ibaMinExtTemperature_, + DeviceValueType::INT, + {}, + F("minexttemp"), + F("Min ext. temperature"), + DeviceValueUOM::DEGREES); // min ext temp for heating curve, in deg. + + // RC30 only + if (model == EMSdevice::EMS_DEVICE_FLAG_RC30_1) { + register_device_value(empty, + &ibaMainDisplay_, + DeviceValueType::ENUM, + {F("internal temperature"), + F("internal setpoint"), + F("external temperature"), + F("burner temperature"), + F("WW temperature"), + F("functioning mode"), + F("time"), + F("date"), + F("smoke temperature")}, + F("ibaMainDisplay"), + F("Display"), + DeviceValueUOM::NONE); + register_device_value(empty, + &ibaLanguage_, + DeviceValueType::ENUM, + {F("German"), F("Dutch"), F("French"), F("Italian")}, + F("ibaLanguage"), + F("Language"), + DeviceValueUOM::NONE); + register_device_value(empty, + &ibaClockOffset_, + DeviceValueType::UINT, + {}, + F("ibaClockOffset"), + F("Clock offset"), + DeviceValueUOM::NONE); // offset (in sec) to clock, 0xff=-1s, 0x02=2s + } + + // RC300 and RC100 + if (model == EMS_DEVICE_FLAG_RC300 || model == EMS_DEVICE_FLAG_RC100) { + register_device_value(empty, + &floordrystatus_, + DeviceValueType::ENUM, + {F("off"), F("start"), F("heat"), F("hold"), F("cool"), F("end")}, + F("floordry"), + F("Floor drying"), + DeviceValueUOM::NONE); + register_device_value(empty, &dampedoutdoortemp2_, DeviceValueType::SHORT, {F("10")}, F("dampedtemp"), F("Damped outdoor temperature"), DeviceValueUOM::DEGREES); + register_device_value(empty, &floordrytemp_, DeviceValueType::UINT, {}, F("floordrytemp"), F("Floor drying temperature"), DeviceValueUOM::DEGREES); + register_device_value( + empty, &ibaBuildingType_, DeviceValueType::ENUM, {F(""), F("light"), F("medium"), F("heavy")}, F("building"), F("Building"), DeviceValueUOM::NONE); + register_device_value(empty, + &wwMode_, + DeviceValueType::ENUM, + {F("off"), F("low"), F("high"), F("auto"), F("own_prog")}, + F("wwmode"), + F("Warm water mode"), + DeviceValueUOM::NONE); + register_device_value(empty, + &wwCircMode_, + DeviceValueType::ENUM, + {F("off"), F("on"), F("auto"), F("own_prog")}, + F("wwcircmode"), + F("Warm water circulation mode"), + DeviceValueUOM::NONE); + } + + // RC30 and RC35 + if (model == EMS_DEVICE_FLAG_RC35 || model == EMS_DEVICE_FLAG_RC30_1) { + register_device_value(empty, &dampedoutdoortemp_, DeviceValueType::SHORT, {}, F("dampedtemp"), F("Damped outdoor temperature"), DeviceValueUOM::DEGREES); + register_device_value( + empty, &ibaBuildingType_, DeviceValueType::ENUM, {F("light"), F("medium"), F("heavy")}, F("building"), F("Building"), DeviceValueUOM::NONE); + register_device_value(empty, &wwMode_, DeviceValueType::ENUM, {F("off"), F("on"), F("auto")}, F("wwmode"), F("Warm water mode"), DeviceValueUOM::NONE); + register_device_value(empty, + &wwCircMode_, + DeviceValueType::ENUM, + {F("off"), F("on"), F("auto")}, + F("wwcircmode"), + F("Warm water circulation mode"), + DeviceValueUOM::NONE); + } +} + +// registers the values for a heating circuit +void Thermostat::register_device_values_hc(std::shared_ptr hc) { + uint8_t model = hc->get_model(); + + // heatcontroller name + std::string hc_name(10, '\0'); // hc{1-4} + snprintf_P(&hc_name[0], 10, PSTR("hc%d"), hc->hc_num()); + + // different logic on how temperature values are stored, depending on model + flash_string_vector setpoint_temp_divider, curr_temp_divider; + if (model == EMS_DEVICE_FLAG_EASY) { + setpoint_temp_divider = flash_string_vector{F("100")}; + curr_temp_divider = flash_string_vector{F("100")}; + } else if (model == EMS_DEVICE_FLAG_JUNKERS) { + setpoint_temp_divider = flash_string_vector{F("10")}; + curr_temp_divider = flash_string_vector{F("10")}; + } else { + setpoint_temp_divider = flash_string_vector{F("2")}; + curr_temp_divider = flash_string_vector{F("10")}; + } + register_device_value( + hc_name, &hc->setpoint_roomTemp, DeviceValueType::SHORT, setpoint_temp_divider, F("seltemp"), F("Setpoint room temperature"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->curr_roomTemp, DeviceValueType::SHORT, curr_temp_divider, F("currtemp"), F("Current room temperature"), DeviceValueUOM::DEGREES); + + // special handling for Home Assistant + // we create special values called hatemp and hamode, which have empty fullnames so not shown in the web or console + if (Mqtt::ha_enabled()) { + uint8_t option = Mqtt::ha_climate_format(); + if (option == Mqtt::HA_Climate_Format::CURRENT) { + register_device_value(hc_name, &hc->curr_roomTemp, DeviceValueType::SHORT, curr_temp_divider, F("hatemp"), nullptr, DeviceValueUOM::DEGREES); + } else if (option == Mqtt::HA_Climate_Format::SETPOINT) { + register_device_value(hc_name, + &hc->setpoint_roomTemp, + DeviceValueType::SHORT, + setpoint_temp_divider, + F("hatemp"), + F("HA current room temperature"), + DeviceValueUOM::DEGREES); + } else if (option == Mqtt::HA_Climate_Format::ZERO) { + register_device_value(hc_name, &zero_value_, DeviceValueType::UINT, {}, F("hatemp"), nullptr, DeviceValueUOM::DEGREES); + } + + // if we're sending to HA the only valid mode types are heat, auto and off + // manual & day = heat + // night & off = off + // everything else auto + register_device_value(hc_name, + &hc->mode, + DeviceValueType::ENUM, + {F("off"), F("heat"), F("auto"), F("heat"), F("off"), F("heat"), F("auto"), F("auto"), F("auto"), F("auto")}, + F("hamode"), + nullptr, + DeviceValueUOM::NONE); + } + + if (model == EMSdevice::EMS_DEVICE_FLAG_RC300 || model == EMSdevice::EMS_DEVICE_FLAG_RC100) { + register_device_value(hc_name, &hc->mode, DeviceValueType::ENUM, {F("manual"), F("auto")}, F("mode"), F("Mode"), DeviceValueUOM::NONE); + register_device_value(hc_name, &hc->modetype, DeviceValueType::ENUM, {F("eco"), F("comfort")}, F("modetype"), F("Mode type"), DeviceValueUOM::NONE); + + register_device_value(hc_name, &hc->nighttemp, DeviceValueType::UINT, {F("2")}, F("ecotemp"), F("Eco temperature"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->manualtemp, DeviceValueType::UINT, {F("2")}, F("manualtemp"), F("Manual temperature"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->daytemp, DeviceValueType::UINT, {F("2")}, F("comforttemp"), F("Comfort temperature"), DeviceValueUOM::DEGREES); + + register_device_value(hc_name, &hc->summertemp, DeviceValueType::UINT, {}, F("summertemp"), F("Summer temperature"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->designtemp, DeviceValueType::UINT, {}, F("designtemp"), F("Design temperature"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->offsettemp, DeviceValueType::INT, {}, F("offsettemp"), F("Offset temperature"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->minflowtemp, DeviceValueType::UINT, {}, F("minflowtemp"), F("Min flow temperature"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->maxflowtemp, DeviceValueType::UINT, {}, F("maxflowtemp"), F("Max flow temperature"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->roominfluence, DeviceValueType::UINT, {}, F("roominfluence"), F("Room influence"), DeviceValueUOM::NONE); + register_device_value(hc_name, &hc->nofrosttemp, DeviceValueType::INT, {}, F("nofrosttemp"), F("Nofrost temperature"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->targetflowtemp, DeviceValueType::UINT, {}, F("targetflowtemp"), F("Target flow temperature"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->heatingtype, DeviceValueType::UINT, {}, F("heatingtype"), F("Heating type"), DeviceValueUOM::NONE); + register_device_value(hc_name, + &hc->summer_setmode, + DeviceValueType::ENUM, + {F("summer"), F("auto"), F("winter")}, + F("summermode"), + F("Summer mode"), + DeviceValueUOM::NONE); + register_device_value(hc_name, + &hc->controlmode, + DeviceValueType::ENUM, + {F("off"), F("outdoor"), F("simple"), F("MPC"), F("room"), F("power"), F("const.")}, + F("controlmode"), + F("Control mode"), + DeviceValueUOM::NONE); + register_device_value(hc_name, &hc->program, DeviceValueType::UINT, {}, F("program"), F("Program"), DeviceValueUOM::NONE); + } + + if (model == EMS_DEVICE_FLAG_RC20) { + register_device_value(hc_name, &hc->mode, DeviceValueType::ENUM, {F("off"), F("manual"), F("auto")}, F("mode"), F("Mode"), DeviceValueUOM::NONE); + register_device_value(hc_name, &hc->modetype, DeviceValueType::ENUM, {F("day")}, F("modetype"), F("Mode type"), DeviceValueUOM::NONE); + } + + if (model == EMS_DEVICE_FLAG_RC20_2) { + register_device_value(hc_name, &hc->mode, DeviceValueType::ENUM, {F("off"), F("manual"), F("auto")}, F("mode"), F("Mode"), DeviceValueUOM::NONE); + register_device_value(hc_name, &hc->modetype, DeviceValueType::ENUM, {F("day")}, F("modetype"), F("Mode type"), DeviceValueUOM::NONE); + register_device_value(hc_name, &hc->daytemp, DeviceValueType::UINT, {F("2")}, F("daytemp"), F("Day temperature"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->nighttemp, DeviceValueType::UINT, {F("2")}, F("nighttemp"), F("Night temperature"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->program, DeviceValueType::UINT, {}, F("program"), F("Program"), DeviceValueUOM::NONE); + } + + if (model == EMS_DEVICE_FLAG_RC35 || model == EMS_DEVICE_FLAG_RC30_1) { + register_device_value(hc_name, &hc->mode, DeviceValueType::ENUM, {F("night"), F("day"), F("auto")}, F("mode"), F("Mode"), DeviceValueUOM::NONE); + register_device_value(hc_name, &hc->modetype, DeviceValueType::ENUM, {F("night"), F("day")}, F("modetype"), F("Mode type"), DeviceValueUOM::NONE); + register_device_value(hc_name, &hc->daytemp, DeviceValueType::UINT, {F("2")}, F("daytemp"), F("Day temperature"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->nighttemp, DeviceValueType::UINT, {F("2")}, F("nighttemp"), F("Night temperature"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->designtemp, DeviceValueType::UINT, {}, F("designtemp"), F("Design temperature"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->offsettemp, DeviceValueType::INT, {F("2")}, F("offsettemp"), F("Offset temperature"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->holidaytemp, DeviceValueType::UINT, {F("2")}, F("holidaytemp"), F("Holiday temperature"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->targetflowtemp, DeviceValueType::UINT, {}, F("targetflowtemp"), F("Target flow temperature"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->summertemp, DeviceValueType::UINT, {}, F("summertemp"), F("Summer temperature"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->summermode, DeviceValueType::BOOL, {}, F("summermode"), F("Summer mode"), DeviceValueUOM::NONE); + register_device_value(hc_name, &hc->holidaymode, DeviceValueType::BOOL, {}, F("holidaymode"), F("Holiday mode"), DeviceValueUOM::NONE); + register_device_value(hc_name, &hc->nofrosttemp, DeviceValueType::INT, {}, F("nofrosttemp"), F("Nofrost temperature"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->roominfluence, DeviceValueType::UINT, {}, F("roominfluence"), F("Room influence"), DeviceValueUOM::NONE); + register_device_value(hc_name, &hc->minflowtemp, DeviceValueType::UINT, {}, F("minflowtemp"), F("Min flow temperature"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->maxflowtemp, DeviceValueType::UINT, {}, F("maxflowtemp"), F("Max flow temperature"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->flowtempoffset, DeviceValueType::UINT, {}, F("flowtempoffset"), F("Flow temperature offset"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->heatingtype, DeviceValueType::UINT, {}, F("heatingtype"), F("Heating type"), DeviceValueUOM::NONE); + register_device_value(hc_name, + &hc->reducemode, + DeviceValueType::ENUM, + {F("nofrost"), F("reduce"), F("room"), F("outdoor")}, + F("reducemode"), + F("Reduce mode"), + DeviceValueUOM::NONE); + register_device_value(hc_name, &hc->controlmode, DeviceValueType::ENUM, {F("outdoor"), F("room")}, F("controlmode"), F("Control mode"), DeviceValueUOM::NONE); + register_device_value(hc_name, &hc->program, DeviceValueType::UINT, {}, F("program"), F("Program"), DeviceValueUOM::NONE); + } + + if (model == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) { + register_device_value(hc_name, &hc->mode, DeviceValueType::ENUM, {F("manual"), F("auto"), F("holiday")}, F("mode"), F("Mode"), DeviceValueUOM::NONE); + register_device_value(hc_name, &hc->modetype, DeviceValueType::ENUM, {F("heat"), F("eco"), F("nofrost")}, F("modetype"), F("Mode type"), DeviceValueUOM::NONE); + register_device_value(hc_name, &hc->daytemp, DeviceValueType::UINT, {F("2")}, F("heattemp"), F("Heat temperature"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->nighttemp, DeviceValueType::UINT, {F("2")}, F("ecotemp"), F("Eco temperature"), DeviceValueUOM::DEGREES); + register_device_value(hc_name, &hc->nofrosttemp, DeviceValueType::INT, {F("2")}, F("nofrosttemp"), F("Nofrost temperature"), DeviceValueUOM::DEGREES); + } +} + } // namespace emsesp diff --git a/src/devices/thermostat.h b/src/devices/thermostat.h index e3065a07..d62eda83 100644 --- a/src/devices/thermostat.h +++ b/src/devices/thermostat.h @@ -40,44 +40,43 @@ class Thermostat : public EMSdevice { Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); class HeatingCircuit { public: - HeatingCircuit(const uint8_t hc_num) + HeatingCircuit(const uint8_t hc_num, const uint8_t model) : hc_num_(hc_num) - , ha_registered_(false) { + , model_(model) { } ~HeatingCircuit() = default; - int16_t setpoint_roomTemp = EMS_VALUE_SHORT_NOTSET; - int16_t curr_roomTemp = EMS_VALUE_SHORT_NOTSET; - uint8_t mode = EMS_VALUE_UINT_NOTSET; - uint8_t mode_type = EMS_VALUE_UINT_NOTSET; - uint8_t summer_mode = EMS_VALUE_UINT_NOTSET; - uint8_t holiday_mode = EMS_VALUE_UINT_NOTSET; - uint8_t daytemp = EMS_VALUE_UINT_NOTSET; - uint8_t nighttemp = EMS_VALUE_UINT_NOTSET; - uint8_t holidaytemp = EMS_VALUE_UINT_NOTSET; - uint8_t heatingtype = EMS_VALUE_UINT_NOTSET; // type of heating: 1 radiator, 2 convectors, 3 floors, 4 room supply - uint8_t targetflowtemp = EMS_VALUE_UINT_NOTSET; - uint8_t summertemp = EMS_VALUE_UINT_NOTSET; - int8_t nofrosttemp = EMS_VALUE_INT_NOTSET; // signed -20°C to +10°C - uint8_t designtemp = EMS_VALUE_UINT_NOTSET; // heating curve design temp at MinExtTemp - int8_t offsettemp = EMS_VALUE_INT_NOTSET; // heating curve offest temp at roomtemp signed! - uint8_t manualtemp = EMS_VALUE_UINT_NOTSET; - uint8_t summer_setmode = EMS_VALUE_UINT_NOTSET; - uint8_t roominfluence = EMS_VALUE_UINT_NOTSET; - uint8_t flowtempoffset = EMS_VALUE_UINT_NOTSET; - uint8_t minflowtemp = EMS_VALUE_UINT_NOTSET; - uint8_t maxflowtemp = EMS_VALUE_UINT_NOTSET; + int16_t setpoint_roomTemp; + int16_t curr_roomTemp; + uint8_t mode; + uint8_t modetype; + uint8_t summermode; + uint8_t holidaymode; + uint8_t daytemp; + uint8_t nighttemp; + uint8_t holidaytemp; + uint8_t heatingtype; // type of heating: 1 radiator, 2 convectors, 3 floors, 4 room supply + uint8_t targetflowtemp; + uint8_t summertemp; + int8_t nofrosttemp; // signed -20°C to +10°C + uint8_t designtemp; // heating curve design temp at MinExtTemp + int8_t offsettemp; // heating curve offest temp at roomtemp signed! + uint8_t manualtemp; + uint8_t summer_setmode; + uint8_t roominfluence; + uint8_t flowtempoffset; + uint8_t minflowtemp; + uint8_t maxflowtemp; + uint8_t reducemode; + uint8_t program; + uint8_t controlmode; uint8_t hc_num() const { return hc_num_; } - bool ha_registered() const { - return ha_registered_; - } - - void ha_registered(bool b) { - ha_registered_ = b; + uint8_t get_model() const { + return model_; } // determines if the heating circuit is actually present and has data @@ -85,11 +84,10 @@ class Thermostat : public EMSdevice { return Helpers::hasValue(setpoint_roomTemp); } - uint8_t get_mode(uint8_t model) const; - uint8_t get_mode_type(uint8_t model) const; + uint8_t get_mode() const; + uint8_t get_mode_type() const; enum Mode : uint8_t { - UNKNOWN, OFF, MANUAL, AUTO, @@ -106,7 +104,8 @@ class Thermostat : public EMSdevice { FLOWOFFSET, MINFLOW, MAXFLOW, - ROOMINFLUENCE + ROOMINFLUENCE, + UNKNOWN }; // for sorting based on hc number @@ -115,31 +114,21 @@ class Thermostat : public EMSdevice { } private: - uint8_t hc_num_; // heating circuit number 1..10 - bool ha_registered_; // whether it has been registered for HA MQTT Discovery + uint8_t hc_num_; // heating circuit number 1..10 + uint8_t model_; // the model type }; static std::string mode_tostring(uint8_t mode); - virtual void publish_values(JsonObject & json, bool force); - virtual bool export_values(JsonObject & json); - virtual void device_info_web(JsonArray & root); - virtual bool updated_values(); + virtual bool publish_ha_config(); private: static uuid::log::Logger logger_; void add_commands(); - bool export_values_main(JsonObject & doc); - bool export_values_hc(uint8_t mqtt_format, JsonObject & doc); - - bool ha_registered() const { - return ha_registered_; - } - void ha_registered(bool b) { - ha_registered_ = b; - } + void register_device_values(); + void register_device_values(uint8_t hc_num); // specific thermostat characteristics, stripping the top 4 bits inline uint8_t model() const { @@ -153,40 +142,38 @@ class Thermostat : public EMSdevice { std::vector summer_typeids; std::vector curve_typeids; - std::string datetime_; // date and time stamp - std::string errorCode_; // code from 0xA2 as string i.e. "A22(816)" - - bool changed_ = false; - bool ha_registered_ = false; + char dateTime_[25]; // date and time stamp + char errorCode_[15]; // code from 0xA2 as string i.e. "A22(816)" // Installation parameters - uint8_t ibaMainDisplay_ = - EMS_VALUE_UINT_NOTSET; // display on Thermostat: 0 int temp, 1 int setpoint, 2 ext temp, 3 burner temp, 4 ww temp, 5 functioning mode, 6 time, 7 data, 9 smoke temp - uint8_t ibaLanguage_ = EMS_VALUE_UINT_NOTSET; // language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian - int8_t ibaCalIntTemperature_ = EMS_VALUE_INT_NOTSET; // offset int. temperature sensor, by * 0.1 Kelvin (-5.0 to 5.0K) - int8_t ibaMinExtTemperature_ = EMS_VALUE_INT_NOTSET; // min ext temp for heating curve, in deg., 0xF6=-10, 0x0 = 0, 0xFF=-1 - uint8_t ibaBuildingType_ = EMS_VALUE_UINT_NOTSET; // building type: 0 = light, 1 = medium, 2 = heavy - uint8_t ibaClockOffset_ = EMS_VALUE_UINT_NOTSET; // offset (in sec) to clock, 0xff = -1 s, 0x02 = 2 s - - uint16_t errorNumber_ = EMS_VALUE_USHORT_NOTSET; - char lastCode_[30] = {'\0'}; - int8_t dampedoutdoortemp_ = EMS_VALUE_INT_NOTSET; - uint16_t tempsensor1_ = EMS_VALUE_USHORT_NOTSET; - uint16_t tempsensor2_ = EMS_VALUE_USHORT_NOTSET; - int16_t dampedoutdoortemp2_ = EMS_VALUE_SHORT_NOTSET; - uint8_t floordrystatus_ = EMS_VALUE_UINT_NOTSET; - uint8_t floordrytemp_ = EMS_VALUE_UINT_NOTSET; - - uint8_t wwExtra1_ = EMS_VALUE_UINT_NOTSET; // wwExtra active for wwSystem 1 - uint8_t wwExtra2_ = EMS_VALUE_UINT_NOTSET; - uint8_t wwMode_ = EMS_VALUE_UINT_NOTSET; - uint8_t wwCircPump_ = EMS_VALUE_UINT_NOTSET; - uint8_t wwCircMode_ = EMS_VALUE_UINT_NOTSET; - uint8_t wwTemp_ = EMS_VALUE_UINT_NOTSET; - uint8_t wwTempLow_ = EMS_VALUE_UINT_NOTSET; + uint8_t ibaMainDisplay_; // display on Thermostat: 0 int temp, 1 int setpoint, 2 ext temp, 3 burner temp, 4 ww temp, 5 functioning mode, 6 time, 7 data, 9 smoke temp + uint8_t ibaLanguage_; // language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian + int8_t ibaCalIntTemperature_; // offset int. temperature sensor, by * 0.1 Kelvin (-5.0 to 5.0K) + int8_t ibaMinExtTemperature_; // min ext temp for heating curve, in deg., 0xF6=-10, 0x0 = 0, 0xFF=-1 + uint8_t ibaBuildingType_; // building type: 0 = light, 1 = medium, 2 = heavy + uint8_t ibaClockOffset_; // offset (in sec) to clock, 0xff = -1 s, 0x02 = 2 s + + uint16_t errorNumber_; + char lastCode_[30]; + int8_t dampedoutdoortemp_; + uint16_t tempsensor1_; + uint16_t tempsensor2_; + int16_t dampedoutdoortemp2_; + uint8_t floordrystatus_; + uint8_t floordrytemp_; + + uint8_t wwExtra1_; // wwExtra active for wwSystem 1 + uint8_t wwExtra2_; + uint8_t wwMode_; + uint8_t wwCircPump_; + uint8_t wwCircMode_; + uint8_t wwTemp_; + uint8_t wwTempLow_; std::vector> heating_circuits_; // each thermostat can have multiple heating circuits + uint8_t zero_value_ = 0; // for fixing current room temperature to 0 for HA + // Generic Types static constexpr uint16_t EMS_TYPE_RCTime = 0x06; // time static constexpr uint16_t EMS_TYPE_RCOutdoorTemp = 0xA3; // is an automatic thermostat broadcast, outdoor external temp @@ -266,9 +253,9 @@ class Thermostat : public EMSdevice { std::shared_ptr heating_circuit(std::shared_ptr telegram); std::shared_ptr heating_circuit(const uint8_t hc_num); - void register_mqtt_ha_config(); - void register_mqtt_ha_config(uint8_t hc_num); - bool ha_config(bool force = false); + void register_mqtt_ha_config_hc(uint8_t hc_num); + void register_device_values_hc(std::shared_ptr hc); + bool thermostat_ha_cmd(const char * message, uint8_t hc_num); void process_RCOutdoorTemp(std::shared_ptr telegram); @@ -279,6 +266,7 @@ class Thermostat : public EMSdevice { void process_RC35wwSettings(std::shared_ptr telegram); void process_RC35Monitor(std::shared_ptr telegram); void process_RC35Set(std::shared_ptr telegram); + void process_RC35Timer(std::shared_ptr telegram); void process_RC30Monitor(std::shared_ptr telegram); void process_RC30Set(std::shared_ptr telegram); void process_RC20Monitor(std::shared_ptr telegram); @@ -335,6 +323,9 @@ class Thermostat : public EMSdevice { bool set_flowtempoffset(const char * value, const int8_t id); bool set_minflowtemp(const char * value, const int8_t id); bool set_maxflowtemp(const char * value, const int8_t id); + bool set_reducemode(const char * value, const int8_t id); + bool set_program(const char * value, const int8_t id); + bool set_controlmode(const char * value, const int8_t id); // set functions - these don't use the id/hc, the parameters are ignored bool set_wwmode(const char * value, const int8_t id); @@ -353,4 +344,4 @@ class Thermostat : public EMSdevice { } // namespace emsesp -#endif \ No newline at end of file +#endif diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index 4299052e..b731b7a7 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -21,6 +21,32 @@ namespace emsesp { +// mapping of UOM, to match order in DeviceValueUOM enum +static const __FlashStringHelper * DeviceValueUOM_s[(uint8_t)10] __attribute__((__aligned__(sizeof(int)))) PROGMEM = { + + F_(degrees), + F_(percent), + F_(lmin), + F_(kwh), + F_(wh), + F_(hours), + F_(minutes), + F_(ua), + F_(bar) + +}; + +const __FlashStringHelper * EMSdevice::uom_to_string(uint8_t uom) { + if (uom == DeviceValueUOM::NONE) { + return nullptr; + } + return DeviceValueUOM_s[uom]; +} + +const std::vector EMSdevice::devicevalues() const { + return devicevalues_; +} + std::string EMSdevice::brand_to_string() const { switch (brand_) { case EMSdevice::Brand::BOSCH: @@ -276,7 +302,6 @@ char * EMSdevice::show_telegram_handlers(char * result) { return result; } - // list all the mqtt handlers for this device void EMSdevice::show_mqtt_handlers(uuid::console::Shell & shell) { Mqtt::show_topic_handlers(shell, device_type_); @@ -296,6 +321,317 @@ void EMSdevice::register_telegram_type(const uint16_t telegram_type_id, const __ telegram_functions_.emplace_back(telegram_type_id, telegram_type_name, fetch, f); } +// add to device value library +// arguments are: +// tag: to be used to group mqtt together, either as separate topics as a nested object +// value: pointer to the value from the .h file +// type: one of DeviceValueType +// options: options for enum or a divider for int (e.g. F("10")) +// short_name: used in Mqtt as keys +// full name: used in Web and Console +// uom: unit of measure from DeviceValueUOM +// icon (optional): the HA mdi icon to use, from locale_*.h file +void EMSdevice::register_device_value(std::string & tag, + void * value_p, + uint8_t type, + const flash_string_vector & options, + const __FlashStringHelper * short_name, + const __FlashStringHelper * full_name, + uint8_t uom, + const __FlashStringHelper * icon) { + // init the value depending on it's type + if (type == DeviceValueType::TEXT) { + *(char *)(value_p) = {'\0'}; + } else if (type == DeviceValueType::INT) { + *(int8_t *)(value_p) = EMS_VALUE_INT_NOTSET; + } else if (type == DeviceValueType::SHORT) { + *(int16_t *)(value_p) = EMS_VALUE_SHORT_NOTSET; + } else if (type == DeviceValueType::USHORT) { + *(uint16_t *)(value_p) = EMS_VALUE_USHORT_NOTSET; + } else if ((type == DeviceValueType::ULONG) || (type == DeviceValueType::TIME)) { + *(uint32_t *)(value_p) = EMS_VALUE_ULONG_NOTSET; + } else { + // enums, uint8_t, bool behave as uint8_t + *(uint8_t *)(value_p) = EMS_VALUE_UINT_NOTSET; + } + + // add to our library + devicevalues_.emplace_back(device_type_, tag, value_p, type, options, short_name, full_name, uom, icon); +} + +// looks up the uom (suffix) for a given key from the device value table +std::string EMSdevice::get_value_uom(const char * key) { + // the key may have a suffix at the start which is between brackets. remove it. + char new_key[80]; + strncpy(new_key, key, sizeof(new_key)); + char * p = new_key; + if (key[0] == '(') { + while ((*p++ != ')') && (*p != '\0')) + ; + p++; + } + + for (const auto & dv : devicevalues_) { + if (dv.full_name != nullptr) { + if (uuid::read_flash_string(dv.full_name) == p) { + // ignore TIME since "minutes" is already included + if ((dv.uom == DeviceValueUOM::NONE) || (dv.uom == DeviceValueUOM::MINUTES)) { + break; + } + return uuid::read_flash_string(EMSdevice::uom_to_string(dv.uom)); + } + } + } + + return {}; // not found +} + +// prepare array of device values, as 3 elements serialized (name, value, uom) in array to send to Web UI +// returns number of elements +bool EMSdevice::generate_values_json_web(JsonObject & json) { + json["name"] = to_string_short(); + JsonArray data = json.createNestedArray("data"); + + uint8_t num_elements = 0; + for (const auto & dv : devicevalues_) { + // ignore if full_name empty + if (dv.full_name != nullptr) { + // handle Booleans (true, false) + if ((dv.type == DeviceValueType::BOOL) && Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) { + // see if we have options for the bool's + if (dv.options.size() == 2) { + data.add(*(uint8_t *)(dv.value_p) ? dv.options[0] : dv.options[1]); + } else { + // see how to render the value depending on the setting + if (Helpers::bool_format() == BOOL_FORMAT_ONOFF) { + // on or off as strings + data.add(*(uint8_t *)(dv.value_p) ? F_(on) : F_(off)); + } else if (Helpers::bool_format() == BOOL_FORMAT_TRUEFALSE) { + // true or false values (not strings) + data.add((bool)(*(uint8_t *)(dv.value_p)) ? true : false); + } else { + // 1 or 0 + data.add((uint8_t)(*(uint8_t *)(dv.value_p)) ? 1 : 0); + } + } + } + + // handle TEXT strings + else if ((dv.type == DeviceValueType::TEXT) && (Helpers::hasValue((char *)(dv.value_p)))) { + data.add((char *)(dv.value_p)); + } + + // handle ENUMs + else if ((dv.type == DeviceValueType::ENUM) && Helpers::hasValue(*(uint8_t *)(dv.value_p))) { + if (*(uint8_t *)(dv.value_p) < dv.options.size()) { + data.add(dv.options[*(uint8_t *)(dv.value_p)]); + } + } + + else { + // handle Integers and Floats + // If a divider is specified, do the division to 2 decimals places and send back as double/float + // otherwise force as an integer whole + // the nested if's is necessary due to the way the ArduinoJson templates are pre-processed by the compiler + uint8_t divider = (dv.options.size() == 1) ? Helpers::atoint(uuid::read_flash_string(dv.options[0]).c_str()) : 0; + + // INT + if ((dv.type == DeviceValueType::INT) && Helpers::hasValue(*(int8_t *)(dv.value_p))) { + if (divider) { + data.add(Helpers::round2(*(int8_t *)(dv.value_p), divider)); + } else { + data.add(*(int8_t *)(dv.value_p)); + } + } else if ((dv.type == DeviceValueType::UINT) && Helpers::hasValue(*(uint8_t *)(dv.value_p))) { + if (divider) { + data.add(Helpers::round2(*(uint8_t *)(dv.value_p), divider)); + } else { + data.add(*(uint8_t *)(dv.value_p)); + } + } else if ((dv.type == DeviceValueType::SHORT) && Helpers::hasValue(*(int16_t *)(dv.value_p))) { + if (divider) { + data.add(Helpers::round2(*(int16_t *)(dv.value_p), divider)); + } else { + data.add(*(int16_t *)(dv.value_p)); + } + } else if ((dv.type == DeviceValueType::USHORT) && Helpers::hasValue(*(uint16_t *)(dv.value_p))) { + if (divider) { + data.add(Helpers::round2(*(uint16_t *)(dv.value_p), divider)); + } else { + data.add(*(uint16_t *)(dv.value_p)); + } + } else if ((dv.type == DeviceValueType::ULONG) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) { + if (divider) { + data.add(Helpers::round2(*(uint32_t *)(dv.value_p), divider)); + } else { + data.add(*(uint32_t *)(dv.value_p)); + } + } else if ((dv.type == DeviceValueType::TIME) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) { + uint32_t time_value = *(uint32_t *)(dv.value_p); + time_value = (divider) ? time_value / divider : time_value; // sometimes we need to divide by 60 + char time_s[40]; + snprintf_P(time_s, 40, PSTR("%d days %d hours %d minutes"), (time_value / 1440), ((time_value % 1440) / 60), (time_value % 60)); + data.add(time_s); + } + } + + // check if we've added a data element by comparing the size + // then add the remaining elements + uint8_t sz = data.size(); + if (sz > num_elements) { + // add the unit of measure (uom) + if (dv.uom == DeviceValueUOM::MINUTES) { + data.add(nullptr); + } else { + data.add(uom_to_string(dv.uom)); + } + + // add name, prefixing the tag if it exists + // if we're a boiler, ignore the tag + if (dv.tag.empty() || (device_type_ == DeviceType::BOILER)) { + data.add(dv.full_name); + } else { + char name[50]; + snprintf_P(name, sizeof(name), "(%s) %s", dv.tag.c_str(), uuid::read_flash_string(dv.full_name).c_str()); + data.add(name); + } + num_elements = sz + 2; + } + } + } + + return (num_elements != 0); +} + +// For each value in the device create the json object pair and add it to given json +// return false if empty +bool EMSdevice::generate_values_json(JsonObject & root, const std::string & tag_filter, const bool verbose) { + bool has_value = false; // to see if we've added a value. it's faster than doing a json.size() at the end + std::string old_tag(40, '\0'); + JsonObject json = root; + + for (const auto & dv : devicevalues_) { + // only show if tag is either empty or matches a value, and don't show if full_name is empty unless we're outputing for mqtt payloads + if (((tag_filter.empty()) || (tag_filter == dv.tag)) && (dv.full_name != nullptr || !verbose)) { + bool have_tag = (!dv.tag.empty() && (dv.device_type != DeviceType::BOILER)); + char name[80]; + if (verbose) { + // prefix the tag in brackets, unless it's Boiler because we're naughty and use tag for the MQTT topic + if (have_tag) { + snprintf_P(name, 80, "(%s) %s", dv.tag.c_str(), uuid::read_flash_string(dv.full_name).c_str()); + } else { + strcpy(name, uuid::read_flash_string(dv.full_name).c_str()); // use full name + } + } else { + strcpy(name, uuid::read_flash_string(dv.short_name).c_str()); // use short name + + // if we have a tag, and its different to the last one create a nested object + if (have_tag && (dv.tag != old_tag)) { + old_tag = dv.tag; + json = root.createNestedObject(dv.tag); + } + } + + // handle Booleans (true, false) + if ((dv.type == DeviceValueType::BOOL) && Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) { + // see if we have options for the bool's + if (dv.options.size() == 2) { + json[name] = *(uint8_t *)(dv.value_p) ? dv.options[0] : dv.options[1]; + has_value = true; + } else { + // see how to render the value depending on the setting + if (Helpers::bool_format() == BOOL_FORMAT_ONOFF) { + // on or off as strings + json[name] = *(uint8_t *)(dv.value_p) ? F_(on) : F_(off); + has_value = true; + } else if (Helpers::bool_format() == BOOL_FORMAT_TRUEFALSE) { + // true or false values (not strings) + json[name] = (bool)(*(uint8_t *)(dv.value_p)) ? true : false; + has_value = true; + } else { + // 1 or 0 + json[name] = (uint8_t)(*(uint8_t *)(dv.value_p)) ? 1 : 0; + has_value = true; + } + } + } + + // handle TEXT strings + else if ((dv.type == DeviceValueType::TEXT) && (Helpers::hasValue((char *)(dv.value_p)))) { + json[name] = (char *)(dv.value_p); + has_value = true; + } + + // handle ENUMs + else if ((dv.type == DeviceValueType::ENUM) && Helpers::hasValue(*(uint8_t *)(dv.value_p))) { + if (*(uint8_t *)(dv.value_p) < dv.options.size()) { + json[name] = dv.options[*(uint8_t *)(dv.value_p)]; + has_value = true; + } + } + + // handle Integers and Floats + else { + // If a divider is specified, do the division to 2 decimals places and send back as double/float + // otherwise force as an integer whole + // the nested if's is necessary due to the way the ArduinoJson templates are pre-processed by the compiler + uint8_t divider = (dv.options.size() == 1) ? Helpers::atoint(uuid::read_flash_string(dv.options[0]).c_str()) : 0; + + // INT + if ((dv.type == DeviceValueType::INT) && Helpers::hasValue(*(int8_t *)(dv.value_p))) { + if (divider) { + json[name] = Helpers::round2(*(int8_t *)(dv.value_p), divider); + } else { + json[name] = *(int8_t *)(dv.value_p); + } + has_value = true; + } else if ((dv.type == DeviceValueType::UINT) && Helpers::hasValue(*(uint8_t *)(dv.value_p))) { + if (divider) { + json[name] = Helpers::round2(*(uint8_t *)(dv.value_p), divider); + } else { + json[name] = *(uint8_t *)(dv.value_p); + } + has_value = true; + } else if ((dv.type == DeviceValueType::SHORT) && Helpers::hasValue(*(int16_t *)(dv.value_p))) { + if (divider) { + json[name] = Helpers::round2(*(int16_t *)(dv.value_p), divider); + } else { + json[name] = *(int16_t *)(dv.value_p); + } + has_value = true; + } else if ((dv.type == DeviceValueType::USHORT) && Helpers::hasValue(*(uint16_t *)(dv.value_p))) { + if (divider) { + json[name] = Helpers::round2(*(uint16_t *)(dv.value_p), divider); + } else { + json[name] = *(uint16_t *)(dv.value_p); + } + has_value = true; + } else if ((dv.type == DeviceValueType::ULONG) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) { + if (divider) { + json[name] = Helpers::round2(*(uint32_t *)(dv.value_p), divider); + } else { + json[name] = *(uint32_t *)(dv.value_p); + } + has_value = true; + } else if ((dv.type == DeviceValueType::TIME) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) { + uint32_t time_value = *(uint32_t *)(dv.value_p); + time_value = (divider) ? time_value / divider : time_value; // sometimes we need to divide by 60 + if (verbose) { + char time_s[40]; + snprintf_P(time_s, sizeof(time_s), PSTR("%d days %d hours %d minutes"), (time_value / 1440), ((time_value % 1440) / 60), (time_value % 60)); + json[name] = time_s; + } else { + json[name] = time_value; + } + has_value = true; + } + } + } + } + + return has_value; +} + // return the name of the telegram type std::string EMSdevice::telegram_type_name(std::shared_ptr telegram) { // see if it's one of the common ones, like Version @@ -354,50 +690,4 @@ void EMSdevice::read_command(const uint16_t type_id) { EMSESP::send_read_request(type_id, device_id()); } -// create json key/value pair -void EMSdevice::create_value_json(JsonArray & root, - const __FlashStringHelper * key, - const __FlashStringHelper * prefix, - const __FlashStringHelper * name, - const __FlashStringHelper * suffix, - JsonObject & json) { - JsonVariant data = json[uuid::read_flash_string(key)]; - if (data == nullptr) { - return; // doesn't exist - } - - // add prefix to name - if (prefix != nullptr) { - char name_text[100]; - snprintf_P(name_text, sizeof(name_text), PSTR("%s%s"), uuid::read_flash_string(prefix).c_str(), uuid::read_flash_string(name).c_str()); - root.add(name_text); - } else { - root.add(name); - } - - // convert to string and add the suffix, this is to save space when sending to the web as json - // which is why we use n and v instead of name and value - std::string suffix_string(10, '\0'); - if (suffix == nullptr) { - suffix_string = ""; - } else { - suffix_string = " " + uuid::read_flash_string(suffix); - } - - char data_string[40]; - if (data.is()) { - snprintf_P(data_string, sizeof(data_string), PSTR("%s%s"), data.as(), suffix_string.c_str()); - } else if (data.is()) { - snprintf_P(data_string, sizeof(data_string), PSTR("%d%s"), data.as(), suffix_string.c_str()); - } else if (data.is()) { - char s[10]; - snprintf_P(data_string, sizeof(data_string), PSTR("%s%s"), Helpers::render_value(s, (float)data.as(), 1), suffix_string.c_str()); - } else if (data.is()) { - char s[10]; - snprintf_P(data_string, sizeof(data_string), PSTR("%s%s"), Helpers::render_boolean(s, data.as()), suffix_string.c_str()); - } - - root.add(data_string); -} - } // namespace emsesp diff --git a/src/emsdevice.h b/src/emsdevice.h index 59a3ee1c..851b8206 100644 --- a/src/emsdevice.h +++ b/src/emsdevice.h @@ -30,10 +30,43 @@ namespace emsesp { +enum DeviceValueType : uint8_t { + BOOL, + INT, + UINT, + SHORT, + USHORT, + ULONG, + TIME, // same as ULONG + ENUM, + TEXT + +}; + +// Unit Of Measurement mapping +enum DeviceValueUOM : uint8_t { + DEGREES, + PERCENT, + LMIN, + KWH, + WH, + HOURS, + MINUTES, + UA, + BAR, + NONE + +}; + class EMSdevice { public: + virtual ~EMSdevice() = default; // destructor of base class must always be virtual because it's a polymorphic class + static constexpr uint8_t EMS_DEVICES_MAX_TELEGRAMS = 20; + // virtual functions overrules by derived classes + virtual bool publish_ha_config() = 0; + // device_type defines which derived class to use, e.g. BOILER, THERMOSTAT etc.. EMSdevice(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand) : device_type_(device_type) @@ -45,8 +78,6 @@ class EMSdevice { , brand_(brand) { } - virtual ~EMSdevice() = default; // destructor of base class must always be virtual because it's a polymorphic class - inline uint8_t device_id() const { return device_id_; } @@ -55,6 +86,8 @@ class EMSdevice { static std::string device_type_2_device_name(const uint8_t device_type); static uint8_t device_name_2_device_type(const char * topic); + static const __FlashStringHelper * uom_to_string(uint8_t uom); + inline uint8_t product_id() const { return product_id_; } @@ -117,6 +150,14 @@ class EMSdevice { unique_id_ = unique_id; } + inline bool has_update() const { + return has_update_; + } + + inline void has_update(bool has_update) { + has_update_ |= has_update; + } + std::string brand_to_string() const; static uint8_t decode_brand(uint8_t value); @@ -131,6 +172,19 @@ class EMSdevice { void register_telegram_type(const uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, process_function_p cb); bool handle_telegram(std::shared_ptr telegram); + std::string get_value_uom(const char * key); + bool generate_values_json(JsonObject & json, const std::string & tag_filter, const bool verbose = false); + bool generate_values_json_web(JsonObject & json); + + void register_device_value(std::string & tag, + void * value_p, + uint8_t type, + const flash_string_vector & options, + const __FlashStringHelper * short_name, + const __FlashStringHelper * full_name, + uint8_t uom, + const __FlashStringHelper * icon = nullptr); + void write_command(const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid); void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid); void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value); @@ -139,12 +193,6 @@ class EMSdevice { void register_mqtt_topic(const std::string & topic, mqtt_subfunction_p f); void register_mqtt_cmd(const __FlashStringHelper * cmd, cmdfunction_p f); - // virtual functions overrules by derived classes - virtual void publish_values(JsonObject & json, bool force = false) = 0; - virtual bool export_values(JsonObject & json) = 0; - virtual bool updated_values() = 0; - virtual void device_info_web(JsonArray & root) = 0; - std::string telegram_type_name(std::shared_ptr telegram); void fetch_values(); @@ -155,12 +203,13 @@ class EMSdevice { telegram_functions_.reserve(n); } - static void create_value_json(JsonArray & root, - const __FlashStringHelper * key, - const __FlashStringHelper * prefix, - const __FlashStringHelper * name, - const __FlashStringHelper * suffix, - JsonObject & json); + bool ha_config_done() const { + return ha_config_done_; + } + + void ha_config_done(const bool v) { + ha_config_done_ = v; + } enum Brand : uint8_t { NO_BRAND = 0, // 0 @@ -214,14 +263,47 @@ class EMSdevice { static constexpr uint8_t EMS_DEVICE_FLAG_EASY = 1; static constexpr uint8_t EMS_DEVICE_FLAG_RC10 = 2; static constexpr uint8_t EMS_DEVICE_FLAG_RC20 = 3; - static constexpr uint8_t EMS_DEVICE_FLAG_RC20_2 = 4; // Variation on RC20, Older, like ES72? - static constexpr uint8_t EMS_DEVICE_FLAG_RC30_1 = 5; // variation on RC30, Newer? + static constexpr uint8_t EMS_DEVICE_FLAG_RC20_2 = 4; // Variation on RC20, Older, like ES72 + static constexpr uint8_t EMS_DEVICE_FLAG_RC30_1 = 5; // variation on RC30, Newer models static constexpr uint8_t EMS_DEVICE_FLAG_RC30 = 6; static constexpr uint8_t EMS_DEVICE_FLAG_RC35 = 7; static constexpr uint8_t EMS_DEVICE_FLAG_RC300 = 8; static constexpr uint8_t EMS_DEVICE_FLAG_RC100 = 9; static constexpr uint8_t EMS_DEVICE_FLAG_JUNKERS = 10; + struct DeviceValue { + uint8_t device_type; // EMSdevice::DeviceType + const std::string tag; // MQTT topic or ID + void * value_p; // pointer to variable of any type + uint8_t type; // DeviceValueType::* + const flash_string_vector options; // list of options for ENUM, or divider + const __FlashStringHelper * short_name; // used in MQTT + const __FlashStringHelper * full_name; // used in Web and Console + uint8_t uom; // DeviceValueUOM::* + const __FlashStringHelper * icon; // HA icon + + DeviceValue(uint8_t device_type, + const std::string & tag, + void * value_p, + uint8_t type, + const flash_string_vector options, + const __FlashStringHelper * short_name, + const __FlashStringHelper * full_name, + uint8_t uom, + const __FlashStringHelper * icon) + : device_type(device_type) + , tag(tag) + , value_p(value_p) + , type(type) + , options(options) + , short_name(short_name) + , full_name(full_name) + , uom(uom) + , icon(icon) { + } + }; + const std::vector devicevalues() const; + private: uint8_t unique_id_; uint8_t device_type_ = DeviceType::SYSTEM; @@ -232,6 +314,9 @@ class EMSdevice { uint8_t flags_ = 0; uint8_t brand_ = Brand::NO_BRAND; + bool ha_config_done_ = false; + bool has_update_ = false; + struct TelegramFunction { uint16_t telegram_type_id_; // it's type_id const __FlashStringHelper * telegram_type_name_; // e.g. RC20Message @@ -246,6 +331,8 @@ class EMSdevice { } }; std::vector telegram_functions_; // each EMS device has its own set of registered telegram types + + std::vector devicevalues_; }; } // namespace emsesp diff --git a/src/emsesp.cpp b/src/emsesp.cpp index b0e18478..e8fe9d4e 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -41,7 +41,7 @@ WebAPIService EMSESP::webAPIService = WebAPIService(&webServer); using DeviceFlags = emsesp::EMSdevice; using DeviceType = emsesp::EMSdevice::DeviceType; std::vector> EMSESP::emsdevices; // array of all the detected EMS devices -std::vector EMSESP::device_library_; // libary of all our known EMS devices so far +std::vector EMSESP::device_library_; // library of all our known EMS devices so far uuid::log::Logger EMSESP::logger_{F_(emsesp), uuid::log::Facility::KERN}; @@ -256,7 +256,7 @@ void EMSESP::show_ems(uuid::console::Shell & shell) { shell.println(); } -// show EMS device values +// show EMS device values to the shell console void EMSESP::show_device_values(uuid::console::Shell & shell) { if (emsdevices.empty()) { shell.printfln(F("No EMS devices detected. Try using 'scan devices' from the ems menu.")); @@ -264,8 +264,6 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) { return; } - DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_MAX_DYN); - // do this in the order of factory classes to keep a consistent order when displaying for (const auto & device_class : EMSFactory::device_handlers()) { for (const auto & emsdevice : emsdevices) { @@ -273,16 +271,40 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) { // print header shell.printfln(F("%s: %s"), emsdevice->device_type_name().c_str(), emsdevice->to_string().c_str()); - doc.clear(); // clear so we can re-use for each device - JsonArray root = doc.to(); - emsdevice->device_info_web(root); // create array + DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN); // use max size + JsonObject json = doc.to(); + emsdevice->generate_values_json(json, "", true); // verbose mode + + // print line + uint8_t id = 0; + for (JsonPair p : json) { + const char * key = p.key().c_str(); + shell.printf(" %s: ", key); + JsonVariant data = p.value(); + shell.print(COLOR_BRIGHT_GREEN); + if (data.is()) { + shell.print(data.as()); + } else if (data.is()) { + shell.print(data.as()); + } else if (data.is()) { + char s[10]; + shell.print(Helpers::render_value(s, (float)data.as(), 1)); + } else if (data.is()) { + char s[10]; + shell.print(Helpers::render_boolean(s, data.as())); + } - // iterate values and print to shell - uint8_t key_value = 0; - for (const JsonVariant & value : root) { - shell.printf((++key_value & 1) ? " %s: " : "%s\r\n", value.as()); - } + // if there is a uom print it + std::string uom = emsdevice->get_value_uom(key); + if (!uom.empty()) { + shell.print(' '); + shell.print(uom); + } + shell.print(COLOR_RESET); + shell.println(); + id++; + } shell.println(); } } @@ -311,12 +333,12 @@ void EMSESP::publish_all(bool force) { return; } if (Mqtt::connected()) { - publish_device_values(EMSdevice::DeviceType::BOILER, false); - publish_device_values(EMSdevice::DeviceType::THERMOSTAT, false); - publish_device_values(EMSdevice::DeviceType::SOLAR, false); - publish_device_values(EMSdevice::DeviceType::MIXER, false); + publish_device_values(EMSdevice::DeviceType::BOILER); + publish_device_values(EMSdevice::DeviceType::THERMOSTAT); + publish_device_values(EMSdevice::DeviceType::SOLAR); + publish_device_values(EMSdevice::DeviceType::MIXER); publish_other_values(); - publish_sensor_values(true, false); + publish_sensor_values(true); system_.send_heartbeat(); } } @@ -327,29 +349,31 @@ void EMSESP::publish_all_loop() { if (!Mqtt::connected() || !publish_all_idx_) { return; } - // every HA-sensor takes 20 ms, wait ~2 sec to finish (boiler have ~70 sensors) + + // every HA-sensor takes 20 ms, wait ~2 sec to finish (boiler has ~70 sensors) if ((uuid::get_uptime() - last < 2000)) { return; } + last = uuid::get_uptime(); switch (publish_all_idx_++) { case 1: - publish_device_values(EMSdevice::DeviceType::BOILER, true); + publish_device_values(EMSdevice::DeviceType::BOILER); break; case 2: - publish_device_values(EMSdevice::DeviceType::THERMOSTAT, true); + publish_device_values(EMSdevice::DeviceType::THERMOSTAT); break; case 3: - publish_device_values(EMSdevice::DeviceType::SOLAR, true); + publish_device_values(EMSdevice::DeviceType::SOLAR); break; case 4: - publish_device_values(EMSdevice::DeviceType::MIXER, true); + publish_device_values(EMSdevice::DeviceType::MIXER); break; case 5: publish_other_values(); break; case 6: - publish_sensor_values(true, true); + publish_sensor_values(true); break; case 7: system_.send_heartbeat(); @@ -362,37 +386,61 @@ void EMSESP::publish_all_loop() { } // create json doc for the devices values and add to MQTT publish queue -// special case for Mixer units, since we want to bundle all devices together into one payload -void EMSESP::publish_device_values(uint8_t device_type, bool force) { - if (device_type == EMSdevice::DeviceType::MIXER && Mqtt::mqtt_format() != Mqtt::Format::SINGLE) { - // DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_LARGE); - StaticJsonDocument doc; - JsonObject json = doc.to(); - for (const auto & emsdevice : emsdevices) { - if (emsdevice && (emsdevice->device_type() == device_type)) { - emsdevice->publish_values(json, force); - } - } - Mqtt::publish("mixer_data", doc.as()); - return; - } +void EMSESP::publish_device_values(uint8_t device_type) { + DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN); // use max size + JsonObject json = doc.to(); + bool has_value = false; for (const auto & emsdevice : emsdevices) { if (emsdevice && (emsdevice->device_type() == device_type)) { - JsonObject dummy; - emsdevice->publish_values(dummy, force); + // if we're using HA and it's not already done, send the config topics first. only do this once + if (Mqtt::ha_enabled() && (!emsdevice->ha_config_done())) { + // create the configs for each value as a sensor + for (const auto & dv : emsdevice->devicevalues()) { + if (dv.device_type == device_type) { + Mqtt::register_mqtt_ha_sensor(dv.type, dv.tag.c_str(), dv.full_name, device_type, dv.short_name, dv.uom, dv.icon); + } + } + + // create HA device + // if this is done early, it may fail for some reason + emsdevice->ha_config_done(emsdevice->publish_ha_config()); + } + + // if its a boiler, generate json for each group and publish it + if (device_type == DeviceType::BOILER) { + emsdevice->generate_values_json(json, "boiler_data"); + Mqtt::publish("boiler_data", json); + json.clear(); + emsdevice->generate_values_json(json, "boiler_data_ww"); + Mqtt::publish("boiler_data_ww", json); + json.clear(); + emsdevice->generate_values_json(json, "boiler_data_info"); + Mqtt::publish("boiler_data_info", json); + return; + } + + // for all other devices add the values to the json, without verbose mode + has_value |= emsdevice->generate_values_json(json, ""); } } + + // if there is nothing to publish, exit + if (!has_value) { + return; + } + + // publish it under a single topic + char topic[20]; + snprintf_P(topic, sizeof(topic), PSTR("%s_data"), EMSdevice::device_type_2_device_name(device_type).c_str()); + + Mqtt::publish(topic, json); } +// call the devices that don't need special attention void EMSESP::publish_other_values() { - for (const auto & emsdevice : emsdevices) { - if (emsdevice && (emsdevice->device_type() != EMSdevice::DeviceType::BOILER) && (emsdevice->device_type() != EMSdevice::DeviceType::THERMOSTAT) - && (emsdevice->device_type() != EMSdevice::DeviceType::SOLAR) && (emsdevice->device_type() != EMSdevice::DeviceType::MIXER)) { - JsonObject dummy; - emsdevice->publish_values(dummy); - } - } + publish_device_values(EMSdevice::DeviceType::SWITCH); + publish_device_values(EMSdevice::DeviceType::HEATPUMP); } void EMSESP::publish_sensor_values(const bool time, const bool force) { @@ -407,7 +455,7 @@ void EMSESP::publish_response(std::shared_ptr telegram) { return; } - StaticJsonDocument doc; + StaticJsonDocument doc; char buffer[100]; doc["src"] = Helpers::hextoa(buffer, telegram->src); @@ -641,7 +689,7 @@ bool EMSESP::process_telegram(std::shared_ptr telegram) { // match device_id and type_id // calls the associated process function for that EMS device // returns false if the device_id doesn't recognize it - // after the telegram has been processed, call the updated_values() function to see if we need to force an MQTT publish + // after the telegram has been processed, call see if there have been values changed and we need to do a MQTT publish bool found = false; for (const auto & emsdevice : emsdevices) { if (emsdevice) { @@ -649,10 +697,11 @@ bool EMSESP::process_telegram(std::shared_ptr telegram) { found = emsdevice->handle_telegram(telegram); // if we correctly processes the telegram follow up with sending it via MQTT if needed if (found && Mqtt::connected()) { - if ((mqtt_.get_publish_onchange(emsdevice->device_type()) && emsdevice->updated_values()) || telegram->type_id == publish_id_) { + if ((mqtt_.get_publish_onchange(emsdevice->device_type()) && emsdevice->has_update()) || telegram->type_id == publish_id_) { if (telegram->type_id == publish_id_) { publish_id_ = 0; } + emsdevice->has_update(false); // reset flag publish_device_values(emsdevice->device_type()); // publish to MQTT if we explicitly have too } } @@ -671,21 +720,6 @@ bool EMSESP::process_telegram(std::shared_ptr telegram) { return found; } -// calls the device handler's function to populate a json doc with device info -// to be used in the Web UI. The unique_id is the unique record ID from the Web table to identify which device to load -void EMSESP::device_info_web(const uint8_t unique_id, JsonObject & root) { - for (const auto & emsdevice : emsdevices) { - if (emsdevice) { - if (emsdevice->unique_id() == unique_id) { - root["name"] = emsdevice->to_string_short(); // can't use c_str() because of scope - JsonArray data = root.createNestedArray("data"); - emsdevice->device_info_web(data); - return; - } - } - } -} - // return true if we have this device already registered bool EMSESP::device_exists(const uint8_t device_id) { for (const auto & emsdevice : emsdevices) { @@ -813,14 +847,14 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std:: // export all values to info command // value and id are ignored bool EMSESP::command_info(uint8_t device_type, JsonObject & json) { - bool ok = false; + bool has_value = false; for (const auto & emsdevice : emsdevices) { if (emsdevice && (emsdevice->device_type() == device_type)) { - ok |= emsdevice->export_values(json); + has_value |= emsdevice->generate_values_json(json, "", true); // verbose mode } } - return ok; + return has_value; } // send a read request, passing it into to the Tx Service, with offset @@ -972,8 +1006,8 @@ void EMSESP::start() { webSettingsService.begin(); // load EMS-ESP specific settings } - // Load our library of known devices. Names are stored in Flash mem. - device_library_.reserve(80); + // Load our library of known devices into stack mem. Names are stored in Flash mem. + // device_library_.reserve(80); device_library_ = { #include "device_library.h" }; @@ -985,13 +1019,8 @@ void EMSESP::start() { dallassensor_.start(); // dallas external sensors webServer.begin(); // start web server - emsdevices.reserve(5); // reserve space for initially 5 devices to avoid mem - + emsdevices.reserve(5); // reserve space for initially 5 devices to avoid mem frag issues LOG_INFO(F("EMS Device library loaded with %d records"), device_library_.size()); - -#if defined(EMSESP_STANDALONE) - mqtt_.on_connect(); // simulate an MQTT connection -#endif } // main loop calling all services diff --git a/src/emsesp.h b/src/emsesp.h index 4fc851b2..ba2bc05e 100644 --- a/src/emsesp.h +++ b/src/emsesp.h @@ -50,15 +50,22 @@ #include "roomcontrol.h" #include "command.h" +#include "devices/boiler.h" + #define WATCH_ID_NONE 0 // no watch id set -#define EMSESP_MAX_JSON_SIZE_HA_CONFIG 384 // for small HA config payloads, using StaticJsonDocument -#define EMSESP_MAX_JSON_SIZE_SMALL 256 // for smaller json docs, using StaticJsonDocument -#define EMSESP_MAX_JSON_SIZE_MEDIUM 768 // for medium json docs from ems devices, using StaticJsonDocument -#define EMSESP_MAX_JSON_SIZE_LARGE 1024 // for large json docs from ems devices, like boiler or thermostat data, using StaticJsonDocument -#define EMSESP_MAX_JSON_SIZE_MEDIUM_DYN 1024 // for large json docs, using DynamicJsonDocument -#define EMSESP_MAX_JSON_SIZE_LARGE_DYN 2048 // for very large json docs, using DynamicJsonDocument -#define EMSESP_MAX_JSON_SIZE_MAX_DYN 4096 // for very very large json docs, using DynamicJsonDocument +#define EMSESP_JSON_SIZE_HA_CONFIG 768 // for HA config payloads, using StaticJsonDocument +#define EMSESP_JSON_SIZE_SMALL 256 // for smaller json docs, using StaticJsonDocument +#define EMSESP_JSON_SIZE_MEDIUM 768 // for medium json docs from ems devices, using StaticJsonDocument +#define EMSESP_JSON_SIZE_LARGE 1024 // for large json docs from ems devices, like boiler or thermostat data, using StaticJsonDocument +#define EMSESP_JSON_SIZE_MEDIUM_DYN 1024 // for large json docs, using DynamicJsonDocument +#define EMSESP_JSON_SIZE_LARGE_DYN 2048 // for very large json docs, using DynamicJsonDocument + +#if defined(EMSESP_STANDALONE) +#define EMSESP_JSON_SIZE_XLARGE_DYN 7000 // for very very large json docs, using DynamicJsonDocument +#else +#define EMSESP_JSON_SIZE_XLARGE_DYN 4096 // for very very large json docs, using DynamicJsonDocument +#endif namespace emsesp { @@ -69,7 +76,7 @@ class EMSESP { static void start(); static void loop(); - static void publish_device_values(uint8_t device_type, bool force = false); + static void publish_device_values(uint8_t device_type); static void publish_other_values(); static void publish_sensor_values(const bool time, const bool force = false); static void publish_all(bool force = false); @@ -98,8 +105,6 @@ class EMSESP { static void send_raw_telegram(const char * data); static bool device_exists(const uint8_t device_id); - static void device_info_web(const uint8_t unique_id, JsonObject & root); - static uint8_t count_devices(const uint8_t device_type); static uint8_t actual_master_thermostat(); @@ -193,7 +198,6 @@ class EMSESP { static void process_version(std::shared_ptr telegram); static void publish_response(std::shared_ptr telegram); static void publish_all_loop(); - static bool command_info(uint8_t device_type, JsonObject & json); static constexpr uint32_t EMS_FETCH_FREQUENCY = 60000; // check every minute @@ -205,7 +209,6 @@ class EMSESP { const __FlashStringHelper * name; uint8_t flags; }; - static std::vector device_library_; static uint8_t actual_master_thermostat_; diff --git a/src/helpers.cpp b/src/helpers.cpp index 99762d31..583c65e5 100644 --- a/src/helpers.cpp +++ b/src/helpers.cpp @@ -136,28 +136,8 @@ char * Helpers::render_boolean(char * result, bool value) { return result; } -// depending on format render a number or a string -char * Helpers::render_enum(char * result, const std::vector & value, const uint8_t no) { - if (no >= value.size()) { - return nullptr; // out of bounds - } - - strcpy(result, uuid::read_flash_string(value[no]).c_str()); - if (bool_format() == BOOL_FORMAT_TRUEFALSE) { - if (no == 0 && uuid::read_flash_string(value[0]) == "off") { - strlcpy(result, "false", 7); - } else if (no == 1 && uuid::read_flash_string(value[1]) == "on") { - strlcpy(result, "true", 6); - } - } else if (bool_format() == BOOL_FORMAT_NUMBERS) { - itoa(result, no); - } - - return result; -} - // render for native char strings -char * Helpers::render_value(char * result, const char * value, uint8_t format) { +char * Helpers::render_value(char * result, const char * value, uint8_t format __attribute__((unused))) { strcpy(result, value); return result; } @@ -293,13 +273,6 @@ char * Helpers::render_value(char * result, const uint32_t value, const uint8_t } result[0] = '\0'; - - // check if we're converting from minutes to a time string - if (format == EMS_VALUE_TIME) { - snprintf_P(result, 40, PSTR("%d days %d hours %d minutes"), (value / 1440), ((value % 1440) / 60), (value % 60)); - return result; - } - char s[20]; #ifndef EMSESP_STANDALONE @@ -378,8 +351,8 @@ uint16_t Helpers::atoint(const char * value) { // rounds a number to 2 decimal places // example: round2(3.14159) -> 3.14 -double Helpers::round2(double value) { - return (int)(value * 100 + 0.5) / 100.0; +double Helpers::round2(double value, const uint8_t divider) { + return (int)((value / divider) * 100 + 0.5) / 100.0; } bool Helpers::check_abs(const int32_t i) { @@ -398,6 +371,14 @@ bool Helpers::hasValue(const int8_t & v) { return (v != EMS_VALUE_INT_NOTSET); } +bool Helpers::hasValue(char * v) { + if ((v == nullptr) || (strlen(v) == 0)) { + return false; + } + + return (v[0] != '\0'); +} + // for short these are typically 0x8300, 0x7D00 and sometimes 0x8000 bool Helpers::hasValue(const int16_t & v) { return (abs(v) < EMS_VALUE_USHORT_NOTSET); @@ -471,7 +452,7 @@ bool Helpers::value2bool(const char * v, bool & value) { } // checks to see if a string is member of a vector and return the index, also allow true/false for on/off -bool Helpers::value2enum(const char * v, uint8_t & value, const std::vector & strs) { +bool Helpers::value2enum(const char * v, uint8_t & value, const flash_string_vector & strs) { if ((v == nullptr) || (strlen(v) == 0)) { return false; } @@ -485,4 +466,7 @@ bool Helpers::value2enum(const char * v, uint8_t & value, const std::vector -#include #include "telegram.h" // for EMS_VALUE_* settings -#define BOOL_FORMAT_ONOFF 1 -#define BOOL_FORMAT_TRUEFALSE 2 -#define BOOL_FORMAT_NUMBERS 3 +enum { BOOL_FORMAT_ONOFF = 1, BOOL_FORMAT_TRUEFALSE, BOOL_FORMAT_NUMBERS }; // matches Web UI settings // #define FJSON(x) x #define FJSON(x) F(x) namespace emsesp { +using flash_string_vector = std::vector; + class Helpers { public: static char * render_value(char * result, const float value, const uint8_t format); // format is the precision @@ -43,7 +42,6 @@ class Helpers { static char * render_value(char * result, const int16_t value, const uint8_t format); static char * render_value(char * result, const char * value, uint8_t format); static char * render_boolean(char * result, bool value); - static char * render_enum(char * result, const std::vector & value, const uint8_t no); static char * hextoa(char * result, const uint8_t value); static std::string data_to_hex(const uint8_t * data, const uint8_t length); @@ -53,7 +51,7 @@ class Helpers { static uint32_t hextoint(const char * hex); static uint16_t atoint(const char * value); static bool check_abs(const int32_t i); - static double round2(double value); + static double round2(double value, const uint8_t divider); static std::string toLower(std::string const & s); static bool hasValue(const uint8_t & v, const uint8_t isBool = 0); @@ -61,12 +59,13 @@ class Helpers { static bool hasValue(const int16_t & v); static bool hasValue(const uint16_t & v); static bool hasValue(const uint32_t & v); + static bool hasValue(char * v); static bool value2number(const char * v, int & value); static bool value2float(const char * v, float & value); static bool value2bool(const char * v, bool & value); static bool value2string(const char * v, std::string & value); - static bool value2enum(const char * v, uint8_t & value, const std::vector & strs); + static bool value2enum(const char * v, uint8_t & value, const flash_string_vector & strs); static void bool_format(uint8_t bool_format) { bool_format_ = bool_format; diff --git a/src/locale_EN.h b/src/locale_EN.h index 5422a7b1..9509d39f 100644 --- a/src/locale_EN.h +++ b/src/locale_EN.h @@ -62,10 +62,6 @@ MAKE_PSTR_WORD(users) MAKE_PSTR_WORD(master) MAKE_PSTR_WORD(pin) MAKE_PSTR_WORD(publish) -MAKE_PSTR_WORD(bar) -MAKE_PSTR_WORD(min) -MAKE_PSTR_WORD(hours) -MAKE_PSTR_WORD(uA) MAKE_PSTR_WORD(timeout) // for commands @@ -94,10 +90,6 @@ MAKE_PSTR_WORD(generic) MAKE_PSTR_WORD(dallassensor) MAKE_PSTR_WORD(unknown) -MAKE_PSTR(1space, " ") -MAKE_PSTR(2spaces, " ") -MAKE_PSTR(kwh, "kWh") -MAKE_PSTR(wh, "Wh") MAKE_PSTR(EMSESP, "EMS-ESP") MAKE_PSTR(master_thermostat_fmt, "Master Thermostat Device ID = %s") MAKE_PSTR(host_fmt, "Host = %s") @@ -115,8 +107,26 @@ MAKE_PSTR(watchid_optional, "[ID]") MAKE_PSTR(watch_format_optional, "[off | on | raw | unknown]") MAKE_PSTR(invalid_watch, "Invalid watch type") MAKE_PSTR(data_mandatory, "\"XX XX ...\"") + +// uom - also used with HA MAKE_PSTR(percent, "%") MAKE_PSTR(degrees, "°C") +MAKE_PSTR(kwh, "kWh") +MAKE_PSTR(wh, "Wh") +MAKE_PSTR(bar, "bar") +MAKE_PSTR(minutes, "minutes") +MAKE_PSTR(hours, "hours") +MAKE_PSTR(ua, "uA") +MAKE_PSTR(lmin, "l/min") + +// Home Assistant icons (https://materialdesignicons.com/) +MAKE_PSTR(icontemperature, "mdi:temperature-celsius") +MAKE_PSTR(iconpercent, "mdi:percent-outline") +MAKE_PSTR(iconfire, "mdi:fire") +MAKE_PSTR(iconfan, "mdi:fan") +MAKE_PSTR(iconflame, "mdi:flash") +MAKE_PSTR(iconvalve, "mdi:valve") + MAKE_PSTR(asterisks, "********") MAKE_PSTR(n_mandatory, "") MAKE_PSTR(id_optional, "[id|hc]") @@ -134,193 +144,3 @@ MAKE_PSTR(new_password_prompt1, "Enter new password: ") MAKE_PSTR(new_password_prompt2, "Retype new password: ") MAKE_PSTR(password_prompt, "Password: ") MAKE_PSTR(unset, "") - -// boiler -MAKE_PSTR(heatingActive, "Heating active") -MAKE_PSTR(tapwaterActive, "Warm water/DHW active") -MAKE_PSTR(serviceCode, "Service code") -MAKE_PSTR(serviceCodeNumber, "Service code number") -MAKE_PSTR(lastCode, "Last error") -MAKE_PSTR(wWSelTemp, "Warm water selected temperature") -MAKE_PSTR(wWSetTemp, "Warm water set temperature") -MAKE_PSTR(wWDisinfectionTemp, "Warm water disinfection temperature") -MAKE_PSTR(selFlowTemp, "Selected flow temperature") -MAKE_PSTR(selBurnPow, "Burner selected max power") -MAKE_PSTR(curBurnPow, "Burner current power") -MAKE_PSTR(pumpMod, "Pump modulation") -MAKE_PSTR(pumpMod2, "Heat pump modulation") -MAKE_PSTR(wWType, "Warm water type") -MAKE_PSTR(wWChargeType, "Warm water charging type") -MAKE_PSTR(wWCircPump, "Warm water circulation pump available") -MAKE_PSTR(wWCircPumpMode, "Warm water circulation pump freq") -MAKE_PSTR(wWCirc, "Warm water circulation active") -MAKE_PSTR(outdoorTemp, "Outside temperature") -MAKE_PSTR(wWCurTemp, "Warm water current temperature (intern)") -MAKE_PSTR(wWCurTemp2, "Warm water current temperature (extern)") -MAKE_PSTR(wWCurFlow, "Warm water current tap water flow") -MAKE_PSTR(curFlowTemp, "Current flow temperature") -MAKE_PSTR(retTemp, "Return temperature") -MAKE_PSTR(switchTemp, "Mixer switch temperature") -MAKE_PSTR(sysPress, "System pressure") -MAKE_PSTR(boilTemp, "Max temperature") -MAKE_PSTR(wwStorageTemp1, "Warm water storage temperature (intern)") -MAKE_PSTR(wwStorageTemp2, "Warm water storage temperature (extern)") -MAKE_PSTR(exhaustTemp, "Exhaust temperature") -MAKE_PSTR(wWActivated, "Warm water activated") -MAKE_PSTR(wWOneTime, "Warm water one time charging") -MAKE_PSTR(wWDisinfecting, "Warm water disinfecting") -MAKE_PSTR(wWCharging, "Warm water charging") -MAKE_PSTR(wWRecharging, "Warm water recharging") -MAKE_PSTR(wWTempOK, "Warm water temperature ok") -MAKE_PSTR(wWActive, "Warm water active") -MAKE_PSTR(burnGas, "Gas") -MAKE_PSTR(flameCurr, "Flame current") -MAKE_PSTR(heatPump, "Pump") -MAKE_PSTR(fanWork, "Fan") -MAKE_PSTR(ignWork, "Ignition") -MAKE_PSTR(wWHeat, "Warm water heating") -MAKE_PSTR(heatingActivated, "Heating activated") -MAKE_PSTR(heatingTemp, "Heating temperature setting") -MAKE_PSTR(pumpModMax, "Circuit pump modulation max power") -MAKE_PSTR(pumpModMin, "Circuit pump modulation min power") -MAKE_PSTR(pumpDelay, "Circuit pump delay time") -MAKE_PSTR(burnMinPeriod, "Burner min period") -MAKE_PSTR(burnMinPower, "Burner min power") -MAKE_PSTR(burnMaxPower, "Burner max power") -MAKE_PSTR(boilHystOn, "Temperature hysteresis on") -MAKE_PSTR(boilHystOff, "Temperature hysteresis off") -MAKE_PSTR(setFlowTemp, "Set flow temperature") -MAKE_PSTR(wWSetPumpPower, "Warm water pump set power") -MAKE_PSTR(wwMixTemperature, "Warm water mix temperature") -MAKE_PSTR(wwBufferTemperature, "Warm water buffer temperature") -MAKE_PSTR(wWStarts, "Warm water starts") -MAKE_PSTR(wWWorkM, "Warm water active time") -MAKE_PSTR(setBurnPow, "Burner set power") -MAKE_PSTR(burnStarts, "Burner starts") -MAKE_PSTR(burnWorkMin, "Burner active time") -MAKE_PSTR(heatWorkMin, "Heating active time") -MAKE_PSTR(UBAuptime, "Boiler total uptime") - -MAKE_PSTR(upTimeControl, "Operating time control") -MAKE_PSTR(upTimeCompHeating, "Operating time compressor heating") -MAKE_PSTR(upTimeCompCooling, "Operating time compressor cooling") -MAKE_PSTR(upTimeCompWw, "Operating time compressor warm water") -MAKE_PSTR(heatingStarts, "Heating starts (control)") -MAKE_PSTR(coolingStarts, "Cooling starts (control)") -MAKE_PSTR(wWStarts2, "Warm water starts (control)") -MAKE_PSTR(nrgConsTotal, "Energy consumption total") -MAKE_PSTR(auxElecHeatNrgConsTotal, "Auxiliary electrical heater energy consumption total") -MAKE_PSTR(auxElecHeatNrgConsHeating, "Auxiliary electrical heater energy consumption heating") -MAKE_PSTR(auxElecHeatNrgConsDHW, "Auxiliary electrical heater energy consumption DHW") -MAKE_PSTR(nrgConsCompTotal, "Energy consumption compressor total") -MAKE_PSTR(nrgConsCompHeating, "Energy consumption compressor heating") -MAKE_PSTR(nrgConsCompWw, "Energy consumption compressor warm water") -MAKE_PSTR(nrgConsCompCooling, "Energy consumption compressor total") -MAKE_PSTR(nrgSuppTotal, "Energy supplied total") -MAKE_PSTR(nrgSuppHeating, "Energy supplied heating") -MAKE_PSTR(nrgSuppWw, "Energy supplied warm water") -MAKE_PSTR(nrgSuppCooling, "Energy supplied cooling") -MAKE_PSTR(maintenanceMessage, "Maintenance message") -MAKE_PSTR(maintenance, "Scheduled maintenance") -MAKE_PSTR(maintenanceTime, "Next maintenance in") -MAKE_PSTR(maintenanceDate, "Next maintenance on") - -// solar -MAKE_PSTR(collectorTemp, "Collector temperature (TS1)") -MAKE_PSTR(tankBottomTemp, "Bottom temperature (TS2)") -MAKE_PSTR(tankBottomTemp2, "Bottom temperature (TS5)") -MAKE_PSTR(tank1MaxTempCurrent, "Maximum Tank temperature") -MAKE_PSTR(heatExchangerTemp, "Heat exchanger temperature (TS6)") -MAKE_PSTR(solarPumpModulation, "Solar pump modulation (PS1)") -MAKE_PSTR(cylinderPumpModulation, "Cylinder pump modulation (PS5)") -MAKE_PSTR(pumpWorkMin, "Pump working time (min)") -MAKE_PSTR(pumpWorkMintxt, "Pump working time") -MAKE_PSTR(energyLastHour, "Energy last hour") -MAKE_PSTR(energyToday, "Energy today") -MAKE_PSTR(energyTotal, "Energy total") -MAKE_PSTR(solarPump, "Solar pump (PS1) active") -MAKE_PSTR(valveStatus, "Valve status") -MAKE_PSTR(tankHeated, "Tank heated") -MAKE_PSTR(collectorShutdown, "Collector shutdown") - -// mixer -MAKE_PSTR(ww_hc, " Warm water circuit %d:") -MAKE_PSTR(wwTemp, "Current warm water temperature") -MAKE_PSTR(pumpStatus, "Current pump status") -MAKE_PSTR(tempStatus, "Current temperature status") -MAKE_PSTR(hc, " Heating circuit %d:") -MAKE_PSTR(flowTemp, "Current flow temperature") -MAKE_PSTR(flowSetTemp, "Setpoint flow temperature") - -// thermostat -MAKE_PSTR(time, "Time") -MAKE_PSTR(error, "Error code") -MAKE_PSTR(display, "Display") -MAKE_PSTR(language, "Language") -MAKE_PSTR(offsetclock, "Offset clock") -MAKE_PSTR(dampedtemp, "Damped outdoor temperature") -MAKE_PSTR(inttemp1, "Temperature sensor 1") -MAKE_PSTR(inttemp2, "Temperature sensor 2") -MAKE_PSTR(intoffset, "Offset int. temperature") -MAKE_PSTR(minexttemp, "Min ext. temperature") -MAKE_PSTR(building, "Building") -MAKE_PSTR(floordry, "Floordrying") -MAKE_PSTR(floordrytemp, "Floordrying temperature") - -MAKE_PSTR(wwmode, "Warm water mode") -MAKE_PSTR(wwtemp, "Warm water high temperature") -MAKE_PSTR(wwtemplow, "Warm water low temperature") -MAKE_PSTR(wwextra1, "Warm water circuit 1 extra") -MAKE_PSTR(wwextra2, "Warm water circuit 2 extra") -MAKE_PSTR(wwcircmode, "Warm water circulation mode") - -// thermostat - per heating circuit -MAKE_PSTR(seltemp, "Setpoint room temperature") -MAKE_PSTR(currtemp, "Current room temperature") -MAKE_PSTR(heattemp, "Heat temperature") -MAKE_PSTR(comforttemp, "Comfort temperature") -MAKE_PSTR(daytemp, "Day temperature") -MAKE_PSTR(ecotemp, "Eco temperature") -MAKE_PSTR(nighttemp, "Night temperature") -MAKE_PSTR(manualtemp, "Manual temperature") -MAKE_PSTR(holidaytemp, "Holiday temperature") -MAKE_PSTR(nofrosttemp, "Nofrost temperature") -MAKE_PSTR(heatingtype, "Heating type") -MAKE_PSTR(targetflowtemp, "Target flow temperature") -MAKE_PSTR(offsettemp, "Offset temperature") -MAKE_PSTR(designtemp, "Design temperature") -MAKE_PSTR(summertemp, "Summer temperature") -MAKE_PSTR(summermode, "Summer mode") -MAKE_PSTR(roominfluence, "Room influence") -MAKE_PSTR(flowtempoffset, "Flow temperature offset") -MAKE_PSTR(minflowtemp, "Min. flow temperature") -MAKE_PSTR(maxflowtemp, "Max. flow temperature") -MAKE_PSTR(mode, "Mode") -MAKE_PSTR(modetype, "Mode type") - -// heat pump -MAKE_PSTR(airHumidity, "Relative air humidity") -MAKE_PSTR(dewTemperature, "Dew point temperature") - -// other -MAKE_PSTR(activated, "Switch activated") -MAKE_PSTR(status, "Switch status") - - -// Home Assistant icons -MAKE_PSTR(icontemperature, "mdi:coolant-temperature") -MAKE_PSTR(iconpercent, "mdi:sine-wave") -MAKE_PSTR(iconfire, "mdi:fire") -MAKE_PSTR(iconfan, "mdi:fan") -MAKE_PSTR(iconflash, "mdi:flash") -MAKE_PSTR(iconwaterpump, "mdi:water-pump") -MAKE_PSTR(iconexport, "mdi:home-export-outline") -MAKE_PSTR(iconimport, "mdi:home-import-outline") -MAKE_PSTR(iconcruise, "mdi:car-cruise-control") -MAKE_PSTR(iconvalve, "mdi:valve") -MAKE_PSTR(iconpower, "mdi:power-cycle") -MAKE_PSTR(iconthermostat, "mdi:home-thermometer-outline") -MAKE_PSTR(iconheatpump, "mdi:water-pump") - -// MQTT topic suffix -MAKE_PSTR(mqtt_suffix_ww, "_ww") diff --git a/src/mqtt.cpp b/src/mqtt.cpp index e711cef9..7c692b01 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -32,10 +32,12 @@ uint32_t Mqtt::publish_time_boiler_; uint32_t Mqtt::publish_time_thermostat_; uint32_t Mqtt::publish_time_solar_; uint32_t Mqtt::publish_time_mixer_; -uint32_t Mqtt::publish_time_other_; uint32_t Mqtt::publish_time_sensor_; -uint8_t Mqtt::mqtt_format_; +uint32_t Mqtt::publish_time_other_; bool Mqtt::mqtt_enabled_; +uint8_t Mqtt::dallas_format_; +uint8_t Mqtt::ha_climate_format_; +bool Mqtt::ha_enabled_; std::vector Mqtt::mqtt_subfunctions_; @@ -84,7 +86,7 @@ void Mqtt::subscribe(const uint8_t device_type, const std::string & topic, mqtt_ } // subscribe to the command topic if it doesn't exist yet -void Mqtt::register_command(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cb) { +void Mqtt::register_command(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb) { std::string cmd_topic = EMSdevice::device_type_2_device_name(device_type); bool exists = false; @@ -149,11 +151,6 @@ void Mqtt::loop() { EMSESP::publish_device_values(EMSdevice::DeviceType::MIXER); } - if (publish_time_other_ && (currentMillis - last_publish_other_ > publish_time_other_)) { - last_publish_other_ = currentMillis; - EMSESP::publish_other_values(); - } - if (currentMillis - last_publish_sensor_ > publish_time_sensor_) { last_publish_sensor_ = currentMillis; EMSESP::publish_sensor_values(publish_time_sensor_ != 0); @@ -249,8 +246,8 @@ void Mqtt::on_message(const char * topic, const char * payload, size_t len) { } // empty function. It's a command then. Find the command from the json and call it directly. - StaticJsonDocument doc; - DeserializationError error = deserializeJson(doc, message); + StaticJsonDocument doc; + DeserializationError error = deserializeJson(doc, message); if (error) { LOG_ERROR(F("MQTT error: payload %s, error %s"), message, error.c_str()); return; @@ -356,8 +353,10 @@ void Mqtt::start() { publish_time_sensor_ = mqttSettings.publish_time_sensor * 1000; mqtt_qos_ = mqttSettings.mqtt_qos; mqtt_retain_ = mqttSettings.mqtt_retain; - mqtt_format_ = mqttSettings.mqtt_format; mqtt_enabled_ = mqttSettings.enabled; + ha_enabled_ = mqttSettings.ha_enabled; + ha_climate_format_ = mqttSettings.ha_climate_format; + dallas_format_ = mqttSettings.dallas_format; }); // if MQTT disabled, quit @@ -406,6 +405,10 @@ void Mqtt::start() { // create space for command buffer, to avoid heap memory fragmentation mqtt_subfunctions_.reserve(10); + +#if defined(EMSESP_STANDALONE) + on_connect(); // simulate an MQTT connection +#endif } void Mqtt::set_publish_time_boiler(uint16_t publish_time) { @@ -455,19 +458,8 @@ bool Mqtt::get_publish_onchange(uint8_t device_type) { return false; } -void Mqtt::set_qos(uint8_t mqtt_qos) { - mqtt_qos_ = mqtt_qos; -} - -void Mqtt::set_retain(bool mqtt_retain) { - mqtt_retain_ = mqtt_retain; -} - -void Mqtt::set_format(uint8_t mqtt_format) { - mqtt_format_ = mqtt_format; -} - -// MQTT onConnect - when a connect is established +// MQTT onConnect - when an MQTT connect is established +// send out some inital MQTT messages void Mqtt::on_connect() { if (connecting_) { return; @@ -479,7 +471,7 @@ void Mqtt::on_connect() { // first time to connect if (connectcount_ == 1) { // send info topic appended with the version information as JSON - StaticJsonDocument doc; + StaticJsonDocument doc; doc["event"] = FJSON("start"); doc["version"] = EMSESP_APP_VERSION; #ifndef EMSESP_STANDALONE @@ -488,9 +480,14 @@ void Mqtt::on_connect() { publish(F_(info), doc.as()); // create the EMS-ESP device in HA, which is MQTT retained - if (mqtt_format() == Format::HA) { + if (ha_enabled()) { ha_status(); } + + // send initial MQTT messages for some of our services + EMSESP::shower_.send_mqtt_stat(false); // Send shower_activated as false + EMSESP::system_.send_heartbeat(); // send heatbeat + } else { // we doing a re-connect from a TCP break // only re-subscribe again to all MQTT topics @@ -504,19 +501,18 @@ void Mqtt::on_connect() { } // Home Assistant Discovery - the main master Device -// homeassistant/sensor/ems-esp/status/config +// e.g. homeassistant/sensor/ems-esp/status/config // all the values from the heartbeat payload will be added as attributes to the entity state void Mqtt::ha_status() { - StaticJsonDocument doc; + StaticJsonDocument doc; - doc["name"] = FJSON("EMS-ESP status"); - doc["uniq_id"] = FJSON("status"); - doc["~"] = System::hostname(); // ems-esp - doc["avty_t"] = FJSON("~/status"); + doc["uniq_id"] = FJSON("status"); + doc["~"] = System::hostname(); // default ems-esp + // doc["avty_t"] = FJSON("~/status"); doc["json_attr_t"] = FJSON("~/heartbeat"); doc["stat_t"] = FJSON("~/heartbeat"); + doc["name"] = FJSON("EMS-ESP status"); doc["val_tpl"] = FJSON("{{value_json['status']}}"); - doc["ic"] = FJSON("mdi:home-thermometer-outline"); JsonObject dev = doc.createNestedObject("dev"); dev["name"] = FJSON("EMS-ESP"); @@ -526,7 +522,9 @@ void Mqtt::ha_status() { JsonArray ids = dev.createNestedArray("ids"); ids.add("ems-esp"); - Mqtt::publish_ha(F("homeassistant/sensor/ems-esp/status/config"), doc.as()); // publish the config payload with retain flag + char topic[100]; + snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/status/config"), System::hostname().c_str()); + Mqtt::publish_ha(topic, doc.as()); // publish the config payload with retain flag } // add sub or pub task to the queue. @@ -560,7 +558,7 @@ std::shared_ptr Mqtt::queue_message(const uint8_t operation, // add MQTT message to queue, payload is a string std::shared_ptr Mqtt::queue_publish_message(const std::string & topic, const std::string & payload, bool retain) { - if (!enabled() || !connected()) { + if (!enabled() || !connecting_) { return nullptr; }; return queue_message(Operation::PUBLISH, topic, payload, retain); @@ -625,7 +623,7 @@ void Mqtt::publish_ha(const __FlashStringHelper * topic, const JsonObject & payl // publish a Home Assistant config topic and payload, with retain flag off. // for ESP32 its added to the queue, for ESP8266 is sent immediatelty void Mqtt::publish_ha(const std::string & topic, const JsonObject & payload) { - if (!enabled() || !payload.size()) { + if (!enabled()) { return; } @@ -638,20 +636,14 @@ void Mqtt::publish_ha(const std::string & topic, const JsonObject & payload) { #if defined(EMSESP_STANDALONE) LOG_DEBUG(F("Publishing HA topic=%s, payload=%s"), topic.c_str(), payload_text.c_str()); -#else - LOG_DEBUG(F("Publishing HA topic %s"), topic.c_str()); #endif -#if defined(ESP32) - bool queued = true; // queue MQTT publish -#else - bool queued = false; // publish immediately +#if defined(EMSESP_DEBUG) + LOG_DEBUG(F("[debug] Publishing HA topic=%s, payload=%s"), topic.c_str(), payload_text.c_str()); #endif - // if MQTT is not connected, then we have to queue the msg until the MQTT is online - if (!connected()) { - queued = true; // override - } + // queue messages if the MQTT connection is not yet established. to ensure we don't miss messages + bool queued = !connected(); if (queued) { queue_publish_message(topic, payload_text, true); // with retain true @@ -661,6 +653,7 @@ void Mqtt::publish_ha(const std::string & topic, const JsonObject & payload) { // send immediately and then wait a while if (!mqttClient_->publish(topic.c_str(), 0, true, payload_text.c_str())) { LOG_ERROR(F("Failed to publish topic %s"), topic.c_str()); + mqtt_publish_fails_++; // increment failure counter } delay(MQTT_HA_PUBLISH_DELAY); // enough time to send the short message out @@ -734,103 +727,65 @@ void Mqtt::process_queue() { mqtt_messages_.pop_front(); // remove the message from the queue } -// HA config for a binary_sensor -void Mqtt::register_mqtt_ha_binary_sensor(const __FlashStringHelper * name, const uint8_t device_type, const char * entity) { - if (mqtt_format() != Format::HA) { - return; - } - - // StaticJsonDocument doc; - DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_HA_CONFIG); - - doc["name"] = name; - doc["uniq_id"] = entity; - - char state_t[50]; - snprintf_P(state_t, sizeof(state_t), PSTR("%s/%s"), hostname_.c_str(), entity); - doc["stat_t"] = state_t; - - EMSESP::webSettingsService.read([&](WebSettings & settings) { - if (settings.bool_format == BOOL_FORMAT_ONOFF) { - doc[F("payload_on")] = FJSON("on"); - doc[F("payload_off")] = FJSON("off"); - } else if (settings.bool_format == BOOL_FORMAT_TRUEFALSE) { - doc[F("payload_on")] = FJSON("true"); - doc[F("payload_off")] = FJSON("false"); - } else { - doc[F("payload_on")] = FJSON("1"); - doc[F("payload_off")] = FJSON("0"); - } - }); - - JsonObject dev = doc.createNestedObject("dev"); - JsonArray ids = dev.createNestedArray("ids"); - char ha_device[40]; - snprintf_P(ha_device, sizeof(ha_device), PSTR("ems-esp-%s"), EMSdevice::device_type_2_device_name(device_type).c_str()); - ids.add(ha_device); - - char topic[MQTT_TOPIC_MAX_SIZE]; - snprintf_P(topic, sizeof(topic), PSTR("homeassistant/binary_sensor/ems-esp/%s/config"), entity); - - publish_ha(topic, doc.as()); -} - -// HA config for a normal 'sensor' type -// entity must match the key/value pair in the _data topic +// HA config for a sensor and binary_sensor entity +// entity must match the key/value pair in the *_data topic // some string copying here into chars, it looks messy but does help with heap fragmentation issues -void Mqtt::register_mqtt_ha_sensor(const char * prefix, - const __FlashStringHelper * suffix, +void Mqtt::register_mqtt_ha_sensor(uint8_t type, // device value type + const char * prefix, const __FlashStringHelper * name, const uint8_t device_type, - const char * entity, - const __FlashStringHelper * uom, + const __FlashStringHelper * entity, + const uint8_t uom, const __FlashStringHelper * icon) { - if (mqtt_format() != Format::HA) { + // ignore if name (fullname) is empty + if (name == nullptr) { return; } - // StaticJsonDocument doc; - DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_HA_CONFIG); + // DynamicJsonDocument doc(EMSESP_JSON_SIZE_HA_CONFIG); + StaticJsonDocument doc; // TODO see if this crashes ESP8266? + + bool have_prefix = ((prefix[0] != '\0') && (device_type != EMSdevice::DeviceType::BOILER)); - // create entity by prefixing any given prefix + // create entity by inserting any given prefix + // we ignore the prefix (tag) if BOILER char new_entity[50]; - if (prefix != nullptr) { - snprintf_P(new_entity, sizeof(new_entity), PSTR("%s.%s"), prefix, entity); + // special case for boiler - don't use the prefix + if (have_prefix) { + snprintf_P(new_entity, sizeof(new_entity), PSTR("%s.%s"), prefix, uuid::read_flash_string(entity).c_str()); } else { - strncpy(new_entity, entity, sizeof(new_entity)); + snprintf_P(new_entity, sizeof(new_entity), PSTR("%s"), uuid::read_flash_string(entity).c_str()); } + // device name char device_name[50]; strncpy(device_name, EMSdevice::device_type_2_device_name(device_type).c_str(), sizeof(device_name)); - // build unique identifier, replacing all . with _ as not to break HA + // build unique identifier which will be used in the topic + // and replacing all . with _ as not to break HA std::string uniq(50, '\0'); snprintf_P(&uniq[0], uniq.capacity() + 1, PSTR("%s_%s"), device_name, new_entity); std::replace(uniq.begin(), uniq.end(), '.', '_'); // topic char topic[MQTT_TOPIC_MAX_SIZE]; - snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/ems-esp/%s/config"), uniq.c_str()); // state topic + // if its a boiler we use the tag char stat_t[MQTT_TOPIC_MAX_SIZE]; - if (suffix != nullptr) { - snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s_data%s"), hostname_.c_str(), device_name, uuid::read_flash_string(suffix).c_str()); + if (device_type == EMSdevice::DeviceType::BOILER) { + snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s"), hostname_.c_str(), prefix); } else { snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s_data"), hostname_.c_str(), device_name); } - // value template - char val_tpl[50]; - snprintf_P(val_tpl, sizeof(val_tpl), PSTR("{{value_json.%s}}"), new_entity); - // ha device char ha_device[40]; snprintf_P(ha_device, sizeof(ha_device), PSTR("ems-esp-%s"), device_name); // name char new_name[50]; - if (prefix != nullptr) { + if (have_prefix) { snprintf_P(new_name, sizeof(new_name), PSTR("%s %s %s"), device_name, prefix, uuid::read_flash_string(name).c_str()); } else { snprintf_P(new_name, sizeof(new_name), PSTR("%s %s"), device_name, uuid::read_flash_string(name).c_str()); @@ -839,13 +794,55 @@ void Mqtt::register_mqtt_ha_sensor(const char * prefix, doc["name"] = new_name; doc["uniq_id"] = uniq; - if (uom != nullptr) { - doc["unit_of_meas"] = uom; - } doc["stat_t"] = stat_t; - doc["val_tpl"] = val_tpl; - if (icon != nullptr) { - doc["ic"] = icon; + + // look at the device value type + if (type != DeviceValueType::BOOL) { + // + // normal HA sensor + // + + // topic + snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/%s/config"), System::hostname().c_str(), uniq.c_str()); + + // value template + char val_tpl[50]; + snprintf_P(val_tpl, sizeof(val_tpl), PSTR("{{value_json.%s}}"), new_entity); + doc["val_tpl"] = val_tpl; + + // unit of measure + if (uom != DeviceValueUOM::NONE) { + doc["unit_of_meas"] = EMSdevice::uom_to_string(uom); + } + + // if there was no icon supplied, resort to the default one + if (icon == nullptr) { + switch (uom) { + case DeviceValueUOM::DEGREES: + doc["ic"] = F_(icontemperature); + break; + case DeviceValueUOM::PERCENT: + doc["ic"] = F_(iconpercent); + break; + case DeviceValueUOM::NONE: + default: + break; + } + } else { + doc["ic"] = icon; // must be prefixed with mdi: + } + } else { + // + // binary sensor + // + snprintf_P(topic, sizeof(topic), PSTR("homeassistant/binary_sensor/%s/%s/config"), System::hostname().c_str(), uniq.c_str()); + + // boolean + EMSESP::webSettingsService.read([&](WebSettings & settings) { + char result[10]; + doc[F("payload_on")] = Helpers::render_boolean(result, true); + doc[F("payload_off")] = Helpers::render_boolean(result, false); + }); } JsonObject dev = doc.createNestedObject("dev"); diff --git a/src/mqtt.h b/src/mqtt.h index 03b469c0..3ef4e34c 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -71,14 +71,12 @@ class Mqtt { void set_publish_time_mixer(uint16_t publish_time); void set_publish_time_other(uint16_t publish_time); void set_publish_time_sensor(uint16_t publish_time); - void set_qos(uint8_t mqtt_qos); - void set_retain(bool mqtt_retain); - void set_format(uint8_t mqtt_format); bool get_publish_onchange(uint8_t device_type); enum Operation { PUBLISH, SUBSCRIBE }; - enum Format : uint8_t { NONE = 0, SINGLE, NESTED, HA }; + enum Dallas_Format : uint8_t { SENSORID = 1, NUMBER }; + enum HA_Climate_Format : uint8_t { CURRENT = 1, SETPOINT, ZERO }; static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 128; // note this should really match the user setting in mqttSettings.maxTopicLength @@ -100,15 +98,14 @@ class Mqtt { static void publish_ha(const std::string & topic, const JsonObject & payload); static void publish_ha(const __FlashStringHelper * topic, const JsonObject & payload); - static void register_mqtt_ha_binary_sensor(const __FlashStringHelper * name, const uint8_t device_type, const char * entity); - static void register_mqtt_ha_sensor(const char * prefix, - const __FlashStringHelper * suffix, + static void register_mqtt_ha_sensor(uint8_t type, + const char * prefix, const __FlashStringHelper * name, const uint8_t device_type, - const char * entity, - const __FlashStringHelper * uom, + const __FlashStringHelper * entity, + const uint8_t uom, const __FlashStringHelper * icon); - static void register_command(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cb); + static void register_command(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb); static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type); static void show_mqtt(uuid::console::Shell & shell); @@ -129,6 +126,10 @@ class Mqtt { #endif } + static AsyncMqttClient * client() { + return mqttClient_; + } + static bool enabled() { return mqtt_enabled_; } @@ -141,12 +142,36 @@ class Mqtt { mqtt_publish_fails_ = 0; } - static uint8_t mqtt_format() { - return mqtt_format_; + static uint8_t ha_climate_format() { + return ha_climate_format_; } - static AsyncMqttClient * client() { - return mqttClient_; + static uint8_t dallas_format() { + return dallas_format_; + } + + static bool ha_enabled() { + return ha_enabled_; + } + + static void ha_climate_format(uint8_t ha_climate_format) { + ha_climate_format_ = ha_climate_format; + } + + static void dallas_format(uint8_t dallas_format) { + dallas_format_ = dallas_format; + } + + static void ha_enabled(bool ha_enabled) { + ha_enabled_ = ha_enabled; + } + + void set_qos(uint8_t mqtt_qos) { + mqtt_qos_ = mqtt_qos; + } + + void set_retain(bool mqtt_retain) { + mqtt_retain_ = mqtt_retain; } private: @@ -232,8 +257,10 @@ class Mqtt { static uint32_t publish_time_mixer_; static uint32_t publish_time_other_; static uint32_t publish_time_sensor_; - static uint8_t mqtt_format_; static bool mqtt_enabled_; + static uint8_t dallas_format_; + static uint8_t ha_climate_format_; + static bool ha_enabled_; }; } // namespace emsesp diff --git a/src/shower.cpp b/src/shower.cpp index 4d1a2559..6f6b7dcf 100644 --- a/src/shower.cpp +++ b/src/shower.cpp @@ -27,10 +27,6 @@ void Shower::start() { shower_timer_ = settings.shower_timer; shower_alert_ = settings.shower_alert; }); - - if (Mqtt::enabled()) { - send_mqtt_stat(false); // send first MQTT publish - } } void Shower::loop() { @@ -100,14 +96,26 @@ void Shower::send_mqtt_stat(bool state) { return; } - // if we're in HA mode make sure we've first sent out the HA MQTT Discovery config topic - if ((Mqtt::mqtt_format() == Mqtt::Format::HA) && (!ha_config_)) { - Mqtt::register_mqtt_ha_binary_sensor(F("Shower Active"), EMSdevice::DeviceType::BOILER, "shower_active"); - ha_config_ = true; - } - char s[7]; Mqtt::publish(F("shower_active"), Helpers::render_boolean(s, state)); + + // if we're in HA mode make sure we've first sent out the HA MQTT Discovery config topic + if ((Mqtt::ha_enabled()) && (!ha_configdone_)) { + ha_configdone_ = true; + + StaticJsonDocument doc; + doc["name"] = FJSON("Shower Active"); + doc["uniq_id"] = FJSON("shower_active"); + doc["~"] = System::hostname(); // default ems-esp + doc["stat_t"] = FJSON("~/shower_active"); + JsonObject dev = doc.createNestedObject("dev"); + JsonArray ids = dev.createNestedArray("ids"); + ids.add("ems-esp"); + + char topic[100]; + snprintf_P(topic, sizeof(topic), PSTR("homeassistant/binary_sensor/%s/shower_active/config"), System::hostname().c_str()); + Mqtt::publish_ha(topic, doc.as()); // publish the config payload with retain flag + } } // turn back on the hot water for the shower @@ -134,7 +142,7 @@ void Shower::shower_alert_start() { // Publish shower data // returns true if added to MQTT queue went ok void Shower::publish_values() { - StaticJsonDocument doc; + StaticJsonDocument doc; char s[50]; doc["shower_timer"] = Helpers::render_boolean(s, shower_timer_); diff --git a/src/shower.h b/src/shower.h index 2498e4bc..6983f229 100644 --- a/src/shower.h +++ b/src/shower.h @@ -37,6 +37,8 @@ class Shower { void start(); void loop(); + void send_mqtt_stat(bool state); + bool shower_alert() const { return shower_alert_; } @@ -65,11 +67,10 @@ class Shower { void publish_values(); void shower_alert_start(); void shower_alert_stop(); - void send_mqtt_stat(bool state); - bool shower_timer_; // true if we want to report back on shower times - bool shower_alert_; // true if we want the alert of cold water - bool ha_config_ = false; // for HA MQTT Discovery + bool shower_timer_; // true if we want to report back on shower times + bool shower_alert_; // true if we want the alert of cold water + bool ha_configdone_ = false; // for HA MQTT Discovery bool shower_on_; uint32_t timer_start_; // ms uint32_t timer_pause_; // ms diff --git a/src/system.cpp b/src/system.cpp index 2634a1ab..529fdb96 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -34,7 +34,7 @@ uuid::syslog::SyslogService System::syslog_; #endif // init statics -uint32_t System::heap_start_ = 0; +uint32_t System::heap_start_ = 1; // avoid using 0 to divide-by-zero later bool System::upload_status_ = false; bool System::hide_led_ = false; uint8_t System::led_gpio_ = 0; @@ -45,7 +45,7 @@ std::string System::hostname_; // send on/off to a gpio pin // value: true = HIGH, false = LOW -// http://ems-esp/api?device=system&cmd=pin&data=1&id=2 +// e.g. http://ems-esp/api?device=system&cmd=pin&data=1&id=2 bool System::command_pin(const char * value, const int8_t id) { if (id < 0) { return false; @@ -184,7 +184,7 @@ void System::syslog_init() { // first call. Sets memory and starts up the UART Serial bridge void System::start() { // set the inital free mem - if (heap_start_ == 0) { + if (heap_start_ < 2) { #ifndef EMSESP_STANDALONE heap_start_ = ESP.getFreeHeap(); #else @@ -225,7 +225,7 @@ void System::other_init() { void System::init() { led_init(); // init LED - other_init(); + other_init(); // boolean format and analog setting syslog_init(); // init SysLog @@ -319,7 +319,7 @@ void System::send_heartbeat() { uint8_t frag_memory = ESP.getHeapFragmentation(); #endif - StaticJsonDocument doc; + StaticJsonDocument doc; uint8_t ems_status = EMSESP::bus_status(); if (ems_status == EMSESP::BUS_STATUS_TX_ERRORS) { @@ -378,10 +378,13 @@ void System::set_led_speed(uint32_t speed) { led_monitor(); } +void System::reset_system_check() { + last_system_check_ = 0; // force the LED to go from fast flash to pulse + send_heartbeat(); +} + // check health of system, done every few seconds void System::system_check() { - static uint32_t last_system_check_ = 0; - if (!last_system_check_ || ((uint32_t)(uuid::get_uptime() - last_system_check_) >= SYSTEM_CHECK_FREQUENCY)) { last_system_check_ = uuid::get_uptime(); @@ -551,11 +554,11 @@ void System::show_system(uuid::console::Shell & shell) { shell.printfln(F("Syslog: disabled")); } else { shell.printfln(F("Syslog:")); - shell.print(F_(1space)); + shell.print(F(" ")); shell.printfln(F_(host_fmt), !settings.syslog_host.isEmpty() ? settings.syslog_host.c_str() : uuid::read_flash_string(F_(unset)).c_str()); - shell.print(F_(1space)); + shell.print(F(" ")); shell.printfln(F_(log_level_fmt), uuid::log::format_level_lowercase(static_cast(settings.syslog_level))); - shell.print(F_(1space)); + shell.print(F(" ")); shell.printfln(F_(mark_interval_fmt), settings.syslog_mark_interval); } }); @@ -688,18 +691,18 @@ void System::console_commands(Shell & shell, unsigned int context) { flash_string_vector{F_(set)}, [](Shell & shell, const std::vector & arguments __attribute__((unused))) { EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & wifiSettings) { - shell.print(F_(1space)); + shell.print(F(" ")); shell.printfln(F_(hostname_fmt), wifiSettings.hostname.isEmpty() ? uuid::read_flash_string(F_(unset)).c_str() : wifiSettings.hostname.c_str()); }); EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & wifiSettings) { - shell.print(F_(1space)); + shell.print(F(" ")); shell.printfln(F_(wifi_ssid_fmt), wifiSettings.ssid.isEmpty() ? uuid::read_flash_string(F_(unset)).c_str() : wifiSettings.ssid.c_str()); - shell.print(F_(1space)); + shell.print(F(" ")); shell.printfln(F_(wifi_password_fmt), wifiSettings.ssid.isEmpty() ? F_(unset) : F_(asterisks)); }); }); @@ -724,7 +727,7 @@ bool System::check_upgrade() { if (LittleFS.begin()) { #if defined(EMSESP_FORCE_SERIAL) Serial.begin(115200); - Serial.println(F("FS is Littlefs")); + Serial.println(F("FS is already LittleFS")); Serial.end(); #endif return false; @@ -750,10 +753,10 @@ bool System::check_upgrade() { Serial.begin(115200); - bool failed = false; - File file; - JsonObject network, general, mqtt, custom_settings; - StaticJsonDocument doc; + bool failed = false; + File file; + JsonObject network, general, mqtt, custom_settings; + StaticJsonDocument doc; // open the system settings: // { @@ -816,7 +819,6 @@ bool System::check_upgrade() { EMSESP::esp8266React.getMqttSettingsService()->update( [&](MqttSettings & mqttSettings) { mqttSettings.host = mqtt["ip"] | FACTORY_MQTT_HOST; - mqttSettings.mqtt_format = (mqtt["nestedjson"] ? Mqtt::Format::NESTED : Mqtt::Format::SINGLE); mqttSettings.mqtt_qos = mqtt["qos"] | 0; mqttSettings.mqtt_retain = mqtt["retain"]; mqttSettings.username = mqtt["user"] | ""; @@ -899,26 +901,25 @@ bool System::check_upgrade() { Serial.end(); delay(1000); restart(); - return true; + return true; // will never get here #else return false; #endif } // export all settings to JSON text -// http://ems-esp/api?device=system&cmd=settings +// e.g. http://ems-esp/api?device=system&cmd=settings // value and id are ignored bool System::command_settings(const char * value, const int8_t id, JsonObject & json) { #ifdef EMSESP_STANDALONE json["test"] = "testing system info command"; #else EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & settings) { - char s[7]; JsonObject node = json.createNestedObject("WIFI"); node["ssid"] = settings.ssid; // node["password"] = settings.password; node["hostname"] = settings.hostname; - node["static_ip_config"] = Helpers::render_boolean(s, settings.staticIPConfig); + node["static_ip_config"] = settings.staticIPConfig; JsonUtils::writeIP(node, "local_ip", settings.localIP); JsonUtils::writeIP(node, "gateway_ip", settings.gatewayIP); JsonUtils::writeIP(node, "subnet_mask", settings.subnetMask); @@ -954,7 +955,9 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject & node["publish_time_mixer"] = settings.publish_time_mixer; node["publish_time_other"] = settings.publish_time_other; node["publish_time_sensor"] = settings.publish_time_sensor; - node["mqtt_format"] = settings.mqtt_format; + node["dallas_format"] = settings.dallas_format; + node["ha_climate_format"] = settings.ha_climate_format; + node["ha_enabled"] = settings.ha_enabled; node["mqtt_qos"] = settings.mqtt_qos; node["mqtt_retain"] = Helpers::render_boolean(s, settings.mqtt_retain); }); @@ -1004,7 +1007,7 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject & } // export status information including some basic settings -// http://ems-esp/api?device=system&cmd=info +// e.g. http://ems-esp/api?device=system&cmd=info bool System::command_info(const char * value, const int8_t id, JsonObject & json) { JsonObject node; @@ -1028,7 +1031,9 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & json node["publish_time_mixer"] = settings.publish_time_mixer; node["publish_time_other"] = settings.publish_time_other; node["publish_time_sensor"] = settings.publish_time_sensor; - node["mqtt_format"] = settings.mqtt_format; + node["dallas_format"] = settings.dallas_format; + node["ha_enabled"] = settings.ha_enabled; + node["ha_climate_format"] = settings.ha_climate_format; node["mqtt_qos"] = settings.mqtt_qos; node["mqtt_retain"] = Helpers::render_boolean(s, settings.mqtt_retain); }); @@ -1095,10 +1100,10 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & json } #if defined(EMSESP_TEST) -// run a test -// e.g. http://ems-esp/api?device=system&cmd=test&data=boiler +// run a test, e.g. http://ems-esp/api?device=system&cmd=test&data=boiler bool System::command_test(const char * value, const int8_t id) { - return (Test::run_test(value, id)); + Test::run_test(value, id); + return true; } #endif diff --git a/src/system.h b/src/system.h index 040def67..f01d8afe 100644 --- a/src/system.h +++ b/src/system.h @@ -63,11 +63,11 @@ class System { static void upload_status(bool in_progress); static bool upload_status(); static void show_mem(const char * note); - - static void init(); - static void led_init(); - static void syslog_init(); - static void other_init(); + void reset_system_check(); + static void init(); + static void led_init(); + static void syslog_init(); + static void other_init(); bool check_upgrade(); void send_heartbeat(); @@ -106,9 +106,10 @@ class System { static void wifi_reconnect(); static int8_t wifi_quality(); - bool system_healthy_ = false; - uint32_t led_flash_speed_ = LED_WARNING_BLINK_FAST; // default boot flashes quickly - uint32_t last_heartbeat_ = 0; + bool system_healthy_ = false; + uint32_t led_flash_speed_ = LED_WARNING_BLINK_FAST; // default boot flashes quickly + uint32_t last_heartbeat_ = 0; + uint32_t last_system_check_ = 0; static bool upload_status_; // true if we're in the middle of a OTA firmware upload static uint32_t heap_start_; diff --git a/src/telegram.cpp b/src/telegram.cpp index d0167945..33316551 100644 --- a/src/telegram.cpp +++ b/src/telegram.cpp @@ -73,6 +73,7 @@ Telegram::Telegram(const uint8_t operation, , offset(offset) , message_length(message_length) { // copy complete telegram data over, preventing buffer overflow + // faster than using std::move() for (uint8_t i = 0; ((i < message_length) && (i < EMS_MAX_TELEGRAM_MESSAGE_LENGTH)); i++) { message_data[i] = data[i]; } diff --git a/src/telegram.h b/src/telegram.h index 706b57f7..ac2b4d9c 100644 --- a/src/telegram.h +++ b/src/telegram.h @@ -40,8 +40,6 @@ static constexpr uint8_t EMS_VALUE_BOOL = 0xFF; // used to mark that somethi static constexpr uint8_t EMS_VALUE_BOOL_OFF = 0x00; // boolean false static constexpr uint8_t EMS_VALUE_BOOL_ON = 0x01; // boolean true. True can be 0x01 or 0xFF sometimes -static constexpr uint8_t EMS_VALUE_TIME = 0xFD; // for converting uint32 to time strings - static constexpr uint8_t EMS_VALUE_BOOL_NOTSET = 0xFE; // random number for booleans, that's not 0, 1 or FF static constexpr uint8_t EMS_VALUE_UINT_NOTSET = 0xFF; // for 8-bit unsigned ints/bytes static constexpr int8_t EMS_VALUE_INT_NOTSET = 0x7F; // for signed 8-bit ints/bytes diff --git a/src/test/test.cpp b/src/test/test.cpp index b677539b..f538050a 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -105,8 +105,8 @@ bool Test::run_test(const char * command, int8_t id) { // add controller add_device(0x09, 114); - add_device(0x28, 160); // MM100, WWC - add_device(0x29, 161); // MM200, WWC + add_device(0x28, 160); // MM100 + add_device(0x29, 161); // MM200 add_device(0x20, 160); // MM100 // WWC1 on 0x29 @@ -115,6 +115,9 @@ bool Test::run_test(const char * command, int8_t id) { // WWC2 on 0x28 uart_telegram({0xA8, 0x00, 0xFF, 0x00, 0x02, 0x31, 0x02, 0x35, 0x00, 0x3C, 0x00, 0x3C, 0x3C, 0x46, 0x02, 0x03, 0x03, 0x00, 0x3C}); + // HC1 on 0x20 + uart_telegram({0xA0, 00, 0xFF, 00, 01, 0xD7, 00, 00, 00, 0x80, 00, 00, 00, 00, 03, 0xC5}); + return true; } @@ -149,28 +152,26 @@ bool Test::run_test(const char * command, int8_t id) { if (strcmp(command, "solar") == 0) { EMSESP::logger().info(F("Testing solar...")); - EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS); add_device(0x30, 163); // SM100 // SM100Monitor - type 0x0362 EMS+ - for SM100 and SM200 - // B0 0B FF 00 02 62 00 44 02 7A 80 00 80 00 80 00 80 00 80 00 80 00 00 7C 80 00 80 00 80 00 80 - rx_telegram({0xB0, 0x0B, 0xFF, 00, 0x02, 0x62, 00, 0x44, 0x02, 0x7A, 0x80, 00, 0x80, 0x00, 0x80, 00, - 0x80, 00, 0x80, 00, 0x80, 00, 00, 0x7C, 0x80, 00, 0x80, 00, 0x80, 00, 0x80}); + uart_telegram({0xB0, 0x0B, 0xFF, 00, 0x02, 0x62, 00, 0x44, 0x02, 0x7A, 0x80, 00, 0x80, 0x00, 0x80, 00, + 0x80, 00, 0x80, 00, 0x80, 00, 00, 0x7C, 0x80, 00, 0x80, 00, 0x80, 00, 0x80}); - rx_telegram({0xB0, 0x0B, 0xFF, 0x00, 0x02, 0x62, 0x01, 0x44, 0x03, 0x30, 0x80, 00, 0x80, 00, 0x80, 00, - 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 0x33}); + uart_telegram({0xB0, 0x0B, 0xFF, 0x00, 0x02, 0x62, 0x01, 0x44, 0x03, 0x30, 0x80, 00, 0x80, 00, 0x80, 00, + 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 0x33}); - rx_telegram({0xB0, 00, 0xFF, 0x18, 02, 0x62, 0x80, 00, 0xB8}); + uart_telegram({0xB0, 00, 0xFF, 0x18, 02, 0x62, 0x80, 00, 0xB8}); - EMSESP::send_raw_telegram("B0 00 FF 18 02 62 80 00 B8"); + uart_telegram("30 00 FF 00 02 64 00 00 00 04 00 00 FF 00 00 1E 0B 09 64 00 00 00 00"); // SM100 modulation return true; } if (strcmp(command, "heatpump") == 0) { EMSESP::logger().info(F("Testing heatpump...")); - EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS); + add_device(0x38, 200); // Enviline module add_device(0x10, 192); // FW120 thermostat @@ -189,13 +190,12 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) { // switch to su shell.add_flags(CommandFlags::ADMIN); - // change MQTT format - EMSESP::esp8266React.getMqttSettingsService()->updateWithoutPropagation([&](MqttSettings & mqttSettings) { - // mqttSettings.mqtt_format = Mqtt::Format::SINGLE; - // mqttSettings.mqtt_format = Mqtt::Format::NESTED; - mqttSettings.mqtt_format = Mqtt::Format::HA; - return StateUpdateResult::CHANGED; - }); + // init stuff + Mqtt::ha_enabled(true); + Mqtt::dallas_format(1); + Mqtt::ha_climate_format(1); + EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS); + emsesp::EMSESP::watch(EMSESP::Watch::WATCH_RAW); // raw std::string command(20, '\0'); if ((cmd.empty()) || (cmd == "default")) { @@ -273,8 +273,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) { if (command == "devices") { shell.printfln(F("Testing devices...")); - EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS); // this is important otherwise nothing will be picked up! - // A fake response - UBADevices(0x07) rx_telegram({0x08, 0x00, 0x07, 0x00, 0x0B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); } @@ -355,19 +353,58 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) { uart_telegram({0x98, 0x00, 0x06, 0x00, 0x00, 0x03, 0x04, 0x0C, 0x02, 0x33, 0x06, 00, 00, 00, 00, 00, 00}); shell.invoke_command("show"); + shell.invoke_command("call boiler info"); - StaticJsonDocument<500> doc; - JsonObject root = doc.to(); - EMSESP::device_info_web(2, root); // show thermostat. use 1 for boiler - serializeJsonPretty(doc, shell); + // test call + DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN); + JsonObject json = doc.to(); + (void)emsesp::Command::call(EMSdevice::DeviceType::BOILER, "info", nullptr, -1, json); +// bool has_data = emsesp::Command::call(EMSdevice::DeviceType::SYSTEM, "test", "boiler", -1, json); +#if defined(EMSESP_STANDALONE) + Serial.print(COLOR_BRIGHT_MAGENTA); + if (json.size() != 0) { + serializeJson(doc, Serial); + } shell.println(); + Serial.print(COLOR_RESET); +#endif + + for (const auto & emsdevice : EMSESP::emsdevices) { + if (emsdevice) { + if (emsdevice->unique_id() == 1) { + DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN); + + JsonObject root = doc.to(); + emsdevice->generate_values_json_web(root); + +#if defined(EMSESP_STANDALONE) + Serial.print(COLOR_BRIGHT_MAGENTA); + Serial.print("memoryUsage="); + Serial.print(doc.memoryUsage()); + Serial.println(); + Serial.print("measureMsgPack="); + Serial.print(measureMsgPack(doc)); + Serial.println(); + Serial.print("measureJson="); + Serial.print(measureJson(doc)); + Serial.println(); + serializeJson(doc, Serial); + Serial.print(COLOR_RESET); + Serial.println(); +#endif + } + } + } + return; } if (command == "boiler") { shell.printfln(F("Testing boiler...")); run_test("boiler"); shell.invoke_command("show"); - // shell.invoke_command("call boiler info"); + shell.invoke_command("call boiler info"); + shell.invoke_command("call system publish"); + shell.invoke_command("show mqtt"); } if (command == "fr120") { @@ -408,11 +445,14 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) { if (command == "solar") { shell.printfln(F("Testing Solar")); run_test("solar"); - uart_telegram("30 00 FF 0A 02 6A 04"); // SM100 pump on 1 - uart_telegram("30 00 FF 00 02 64 00 00 00 04 00 00 FF 00 00 1E 0B 09 64 00 00 00 00"); // SM100 modulation + + uart_telegram("30 00 FF 0A 02 6A 04"); // SM100 pump on (1) EMSESP::show_device_values(shell); - uart_telegram("30 00 FF 0A 02 6A 03"); // SM100 pump off 0 + uart_telegram("30 00 FF 0A 02 6A 03"); // SM100 pump off (0) EMSESP::show_device_values(shell); + shell.invoke_command("call system publish"); + + // EMSESP::send_raw_telegram("B0 00 FF 18 02 62 80 00 B8"); } if (command == "heatpump") { @@ -425,8 +465,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) { if (command == "solar200") { shell.printfln(F("Testing Solar SM200")); - EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS); - add_device(0x30, 164); // SM200 // SM100Monitor - type 0x0362 EMS+ - for SM100 and SM200 @@ -452,10 +490,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) { if (command == "km") { shell.printfln(F("Testing KM200 Gateway")); - emsesp::EMSESP::watch(EMSESP::Watch::WATCH_RAW); // raw - - EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS); - add_device(0x10, 158); // RC300 add_device(0x48, 189); // KM200 @@ -514,10 +548,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) { if (command == "cr100") { shell.printfln(F("Testing CR100")); - emsesp::EMSESP::watch(EMSESP::Watch::WATCH_RAW); // raw - - EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_HT3); // switch to junkers - add_device(0x18, 157); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355 // RCPLUSStatusMessage_HC1(0x01A5) @@ -822,16 +852,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) { if (command == "mixer") { shell.printfln(F("Testing Mixer...")); - // change MQTT format - EMSESP::esp8266React.getMqttSettingsService()->updateWithoutPropagation([&](MqttSettings & mqttSettings) { - // mqttSettings.mqtt_format = Mqtt::Format::SINGLE; - // mqttSettings.mqtt_format = Mqtt::Format::NESTED; - mqttSettings.mqtt_format = Mqtt::Format::HA; - return StateUpdateResult::CHANGED; - }); - - EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS); - run_test("mixer"); // check for error "No telegram type handler found for ID 0x255 (src 0x20)" diff --git a/src/test/test.h b/src/test/test.h index 066ba004..f5df6191 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -35,10 +35,16 @@ #include "telegram.h" #include "mqtt.h" #include "emsesp.h" +#include "command.h" namespace emsesp { -#define EMSESP_TEST_DEFAULT "boiler" +// #define EMSESP_TEST_DEFAULT "thermostat" +// #define EMSESP_TEST_DEFAULT "solar" +// #define EMSESP_TEST_DEFAULT "mixer" +// #define EMSESP_TEST_DEFAULT "web" +#define EMSESP_TEST_DEFAULT "general" +// #define EMSESP_TEST_DEFAULT "boiler" class Test { public: diff --git a/src/uart/emsuart_esp32.cpp b/src/uart/emsuart_esp32.cpp index 0599b181..1f050ab3 100644 --- a/src/uart/emsuart_esp32.cpp +++ b/src/uart/emsuart_esp32.cpp @@ -186,6 +186,9 @@ uint16_t EMSuart::transmit(const uint8_t * buf, const uint8_t len) { if (len == 0 || len >= EMS_MAXBUFFERSIZE) { return EMS_TX_STATUS_ERR; } + if (tx_mode_ == 0) { + return EMS_TX_STATUS_OK; + } if (tx_mode_ > 5) { // timer controlled modes for (uint8_t i = 0; i < len; i++) { diff --git a/src/uart/emsuart_esp8266.cpp b/src/uart/emsuart_esp8266.cpp index e6abdbe9..c416fc7e 100644 --- a/src/uart/emsuart_esp8266.cpp +++ b/src/uart/emsuart_esp8266.cpp @@ -41,7 +41,6 @@ bool EMSuart::sending_ = false; // Important: must not use ICACHE_FLASH_ATTR // void ICACHE_RAM_ATTR EMSuart::emsuart_rx_intr_handler(void * para) { - if (USIS(EMSUART_UART) & ((1 << UIBD))) { // BREAK detection = End of EMS data block USC0(EMSUART_UART) &= ~(1 << UCBRK); // reset tx-brk if (sending_) { // irq tx_mode is interrupted by , should never happen @@ -233,6 +232,9 @@ uint16_t ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) { if (len == 0 || len >= EMS_MAXBUFFERSIZE) { return EMS_TX_STATUS_ERR; // nothing or to much to send } + if (tx_mode_ == 0) { + return EMS_TX_STATUS_OK; + } // timer controlled modes with extra delay if (tx_mode_ >= 5) { diff --git a/src/version.h b/src/version.h index c8d87ebc..ff9b1b88 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "2.1.1b6" +#define EMSESP_APP_VERSION "2.3.0b0"