diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index d5a58947c..6ce0bf3fc 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -18,11 +18,15 @@ For more details go to [docs.emsesp.org](https://docs.emsesp.org/). - disinfection command [#2601](https://github.com/emsesp/EMS-ESP32/issues/2601) - added new board profile for upcoming BBQKees E32V2.2 - set differential pressure entity in Mixer device -- set set climate action cooling/heating in HA +- set set climate action cooling/heating in HA [#2583](https://github.com/emsesp/EMS-ESP32/issues/2583) - Internal sensors of E32V2_2 -- FW200 display options -- CR11 mode settings OFF/MANUAL depends on selTemp +- FW200 display options [#2610](https://github.com/emsesp/EMS-ESP32/discussions/2610) +- CR11 mode settings OFF/MANUAL depends on selTemp [#2437](https://github.com/emsesp/EMS-ESP32/issues/2437) - Fuse settings for BBQKees boards +- Analogsensors for pulse output [#2624](https://github.com/emsesp/EMS-ESP32/discussions/2624) +- Analogsensors frequency input [#2631](https://github.com/emsesp/EMS-ESP32/discussions/2631) +- SRC plus thermostats [#2636](https://github.com/emsesp/EMS-ESP32/issues/2636) +- Greenstar 2000 [#2645](https://github.com/emsesp/EMS-ESP32/issues/2645) ## Fixed @@ -39,9 +43,13 @@ For more details go to [docs.emsesp.org](https://docs.emsesp.org/). - Add pulsed water meter input to V1.3 gateway with Lilygo S3 [#2550](https://github.com/emsesp/EMS-ESP32/issues/2550) - fix missing long 10-second press of Button to perform a factory reset - fix wwMaxPower on Junkers ZBS14 [#2609](https://github.com/emsesp/EMS-ESP32/issues/2609) +- ventilation bypass state from telegram 0x55C [#1197](https://github.com/emsesp/EMS-ESP32/issues/1197) +- set selflowtemp for ems+ boilers [#2641](https://github.com/emsesp/EMS-ESP32/discussions/2641) ## Changed - show console log with ISO date/time [#2533](https://github.com/emsesp/EMS-ESP32/discussions/2533) - remove ESP32 CPU temperature - updated core libraries like AsyncTCP, AsyncWebServer and Modbus +- remove command `scan deep` +- ignore repeated `forceheatingoff` commands [#2641](https://github.com/emsesp/EMS-ESP32/discussions/2641) diff --git a/interface/src/app/main/Sensors.tsx b/interface/src/app/main/Sensors.tsx index 8332f42c5..c8a37fd91 100644 --- a/interface/src/app/main/Sensors.tsx +++ b/interface/src/app/main/Sensors.tsx @@ -439,7 +439,8 @@ const Sensors = () => { {a.n} {AnalogTypeNames[a.t]} {(a.t === AnalogType.DIGITAL_OUT && a.g !== 25 && a.g !== 26) || - a.t === AnalogType.DIGITAL_IN ? ( + a.t === AnalogType.DIGITAL_IN || + a.t === AnalogType.PULSE ? ( {a.v ? LL.ON() : LL.OFF()} ) : ( {a.t ? formatValue(a.v, a.u) : ''} diff --git a/interface/src/app/main/SensorsAnalogDialog.tsx b/interface/src/app/main/SensorsAnalogDialog.tsx index 6ee9e17e3..719505aa3 100644 --- a/interface/src/app/main/SensorsAnalogDialog.tsx +++ b/interface/src/app/main/SensorsAnalogDialog.tsx @@ -132,7 +132,9 @@ const SensorsAnalogDialog = ({ ))} - {editItem.t >= AnalogType.COUNTER && editItem.t <= AnalogType.RATE && ( + {((editItem.t >= AnalogType.COUNTER && editItem.t <= AnalogType.RATE) || + (editItem.t >= AnalogType.FREQ_0 && + editItem.t <= AnalogType.FREQ_2)) && ( )} + {editItem.t === AnalogType.PULSE && ( + <> + + + {LL.ACTIVEHIGH()} + {LL.ACTIVELOW()} + + + + s + ) + }, + htmlInput: { min: '0', max: '10000', step: '0.1' } + }} + /> + + + )} diff --git a/interface/src/app/main/types.ts b/interface/src/app/main/types.ts index 924d57a5a..5c86daf4f 100644 --- a/interface/src/app/main/types.ts +++ b/interface/src/app/main/types.ts @@ -188,7 +188,8 @@ export enum DeviceValueUOM { VOLTS, MBAR, LH, - CTKWH + CTKWH, + HZ } export const DeviceValueUOM_s = [ @@ -218,7 +219,8 @@ export const DeviceValueUOM_s = [ 'V', 'mbar', 'l/h', - 'ct/kWh' + 'ct/kWh', + 'Hz' ]; export enum AnalogType { @@ -234,7 +236,11 @@ export enum AnalogType { PWM_1 = 8, PWM_2 = 9, NTC = 10, - RGB = 11 + RGB = 11, + PULSE = 12, + FREQ_0 = 13, + FREQ_1 = 14, + FREQ_2 = 15 } export const AnalogTypeNames = [ @@ -249,7 +255,11 @@ export const AnalogTypeNames = [ 'PWM 1', 'PWM 2', 'NTC Temp.', - 'RGB Led' + 'RGB Led', + 'Pulse', + 'Freq 0', + 'Freq 1', + 'Freq 2' ]; type BoardProfiles = Record; diff --git a/src/ESP32React/MqttSettingsService.cpp b/src/ESP32React/MqttSettingsService.cpp index 395f5a421..fc1c065a3 100644 --- a/src/ESP32React/MqttSettingsService.cpp +++ b/src/ESP32React/MqttSettingsService.cpp @@ -33,9 +33,9 @@ void MqttSettingsService::begin() { void MqttSettingsService::startClient() { static bool isSecure = false; - if (_mqttClient != nullptr) { + if (_mqttClient) { // do we need to change the client? - if (_state.enabled && ((isSecure && _state.enableTLS) || (!isSecure && !_state.enableTLS))) { + if ((isSecure && _state.enableTLS) || (!isSecure && !_state.enableTLS)) { return; } delete _mqttClient; @@ -79,9 +79,6 @@ void MqttSettingsService::startClient() { } void MqttSettingsService::loop() { - if (!_state.enabled || _mqttClient == nullptr || emsesp::EMSESP::system_.systemStatus() != 0) { - return; - } if (_reconfigureMqtt || (_disconnectedAt && static_cast(uuid::get_uptime() - _disconnectedAt) >= MQTT_RECONNECTION_DELAY)) { // reconfigure MQTT client _disconnectedAt = configureMqtt() ? 0 : uuid::get_uptime(); diff --git a/src/core/analogsensor.cpp b/src/core/analogsensor.cpp index 5ec1188e8..2e3ff3dfe 100644 --- a/src/core/analogsensor.cpp +++ b/src/core/analogsensor.cpp @@ -23,6 +23,31 @@ namespace emsesp { uuid::log::Logger AnalogSensor::logger_{F_(analogsensor), uuid::log::Facility::DAEMON}; +#ifndef EMSESP_STANDALONE +portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; +unsigned long AnalogSensor::edge[] = {0, 0, 0}; +unsigned long AnalogSensor::edgecnt[] = {0, 0, 0}; + +void IRAM_ATTR AnalogSensor::freqIrq0() { + portENTER_CRITICAL_ISR(&mux); + edgecnt[0]++; + edge[0] = micros(); + portEXIT_CRITICAL_ISR(&mux); +} +void IRAM_ATTR AnalogSensor::freqIrq1() { + portENTER_CRITICAL_ISR(&mux); + edgecnt[1]++; + edge[1] = micros(); + portEXIT_CRITICAL_ISR(&mux); +} +void IRAM_ATTR AnalogSensor::freqIrq2() { + portENTER_CRITICAL_ISR(&mux); + edgecnt[2]++; + edge[2] = micros(); + portEXIT_CRITICAL_ISR(&mux); +} +#endif + void AnalogSensor::start(const bool factory_settings) { // if (factory_settings && EMSESP::nvs_.getString("boot").equals("E32V2_2") && EMSESP::nvs_.getString("hwrevision").equals("3.0")) { if (factory_settings && analogReadMilliVolts(39) > 700) { // core voltage > 2.6V @@ -92,7 +117,7 @@ void AnalogSensor::reload(bool get_nvs) { for (const auto & sensor : settings.analogCustomizations) { // search customlist if (sensor_.gpio() == sensor.gpio) { // for output sensors set value to new start-value - if (sensor.type >= AnalogType::DIGITAL_OUT + if (sensor.type >= AnalogType::DIGITAL_OUT && sensor.type <= AnalogType::PWM_2 && (sensor_.type() != sensor.type || sensor_.offset() != sensor.offset || sensor_.factor() != sensor.factor)) { sensor_.set_value(sensor.offset); } @@ -134,7 +159,7 @@ void AnalogSensor::reload(bool get_nvs) { } } if (sensor.type == AnalogType::COUNTER || (sensor.type >= AnalogType::DIGITAL_OUT && sensor.type <= AnalogType::PWM_2) - || sensor.type == AnalogType::RGB) { + || sensor.type == AnalogType::RGB || sensor.type == AnalogType::PULSE) { Command::add( EMSdevice::DeviceType::ANALOGSENSOR, sensor.name.c_str(), @@ -142,6 +167,7 @@ void AnalogSensor::reload(bool get_nvs) { sensor.type == AnalogType::COUNTER ? FL_(counter) : sensor.type == AnalogType::DIGITAL_OUT ? FL_(digital_out) : sensor.type == AnalogType::RGB ? FL_(RGB) + : sensor.type == AnalogType::PULSE ? FL_(pulse) : FL_(pwm), CommandFlag::ADMIN_ONLY); } @@ -204,6 +230,18 @@ void AnalogSensor::reload(bool get_nvs) { sensor.set_offset(0); sensor.set_value(0); publish_sensor(sensor); +#ifndef EMSESP_STANDALONE + } else if (sensor.type() >= AnalogType::FREQ_0 && sensor.type() <= AnalogType::FREQ_2) { + LOG_DEBUG("Frequency on GPIO %02d", sensor.gpio()); + pinMode(sensor.gpio(), INPUT_PULLUP); + sensor.set_offset(0); + sensor.set_value(0); + publish_sensor(sensor); + auto index = sensor.type() - AnalogType::FREQ_0; + attachInterrupt(sensor.gpio(), index == 0 ? freqIrq0 : index == 1 ? freqIrq1 : freqIrq2, FALLING); + lastedge[index] = edge[index] = micros(); + edgecnt[index] = 0; +#endif } else if (sensor.type() == AnalogType::DIGITAL_IN) { LOG_DEBUG("Digital Read on GPIO %02d", sensor.gpio()); pinMode(sensor.gpio(), INPUT_PULLUP); @@ -260,6 +298,11 @@ void AnalogSensor::reload(bool get_nvs) { sensor.set_value(sensor.offset()); } publish_sensor(sensor); + } else if (sensor.type() == AnalogType::PULSE) { + LOG_DEBUG("Pulse on GPIO %02d", sensor.gpio()); + pinMode(sensor.gpio(), OUTPUT); + digitalWrite(sensor.gpio(), (sensor.offset() == 1) ^ (sensor.value() == 1)); + sensor.polltime_ = sensor.value() != 0 ? uuid::get_uptime() + (sensor.factor() * 1000) : 0; } else if (sensor.type() >= AnalogType::PWM_0 && sensor.type() <= AnalogType::PWM_2) { LOG_DEBUG("PWM output on GPIO %02d", sensor.gpio()); #if ESP_IDF_VERSION_MAJOR >= 5 @@ -330,6 +373,25 @@ void AnalogSensor::measure() { changed_ = true; publish_sensor(sensor); } +#ifndef EMSESP_STANDALONE + } else if (sensor.type() >= AnalogType::FREQ_0 && sensor.type() <= AnalogType::FREQ_2) { + auto index = sensor.type() - AnalogType::FREQ_0; + auto oldval = sensor.value(); + if (edge[index] != lastedge[index] && edgecnt[index] > 0) { + portENTER_CRITICAL_ISR(&mux); + auto t = (edge[index] - lastedge[index]) / edgecnt[index]; + lastedge[index] = edge[index]; + edgecnt[index] = 0; + portEXIT_CRITICAL_ISR(&mux); + sensor.set_value(sensor.factor() * 1000000.0 / t); + } else if (micros() - edge[index] > 10000000ul && sensor.value() > 0) { + sensor.set_value(0); + } + if (sensor.value() != oldval) { + changed_ = true; + publish_sensor(sensor); + } +#endif } } } @@ -337,9 +399,9 @@ void AnalogSensor::measure() { // poll digital io every time with debounce // go through the list of digital sensors for (auto & sensor : sensors_) { + auto old_value = sensor.value(); // remember current value before reading if (sensor.type() == AnalogType::DIGITAL_IN || sensor.type() == AnalogType::COUNTER || sensor.type() == AnalogType::TIMER || sensor.type() == AnalogType::RATE) { - auto old_value = sensor.value(); // remember current value before reading auto current_reading = digitalRead(sensor.gpio()); if (sensor.poll_ != current_reading) { // check for pinchange sensor.polltime_ = uuid::get_uptime(); // remember time of pinchange @@ -362,13 +424,17 @@ void AnalogSensor::measure() { sensor.last_polltime_ = sensor.polltime_; } } - - // see if there is a change and increment # reads - if (old_value != sensor.value()) { - sensorreads_++; - changed_ = true; - publish_sensor(sensor); - } + } + if (sensor.type() == AnalogType::PULSE && sensor.value() && sensor.polltime_ && sensor.polltime_ < uuid::get_uptime()) { + sensor.set_value(0); + digitalWrite(sensor.gpio(), sensor.offset()); + sensor.polltime_ = 0; + } + // see if there is a change and increment # reads + if (old_value != sensor.value()) { + sensorreads_++; + changed_ = true; + publish_sensor(sensor); } } @@ -562,6 +628,9 @@ void AnalogSensor::publish_values(const bool force) { case AnalogType::PWM_0: case AnalogType::PWM_1: case AnalogType::PWM_2: + case AnalogType::FREQ_0: + case AnalogType::FREQ_1: + case AnalogType::FREQ_2: case AnalogType::RGB: case AnalogType::NTC: dataSensor["value"] = serialized(Helpers::render_value(s, sensor.value(), 2)); // double @@ -762,7 +831,7 @@ void AnalogSensor::get_value_json(JsonObject output, const Sensor & sensor) { output["analog"] = FL_(list_sensortype)[sensor.type()]; output["value"] = sensor.value(); output["readable"] = true; - output["writeable"] = sensor.type() == AnalogType::COUNTER || sensor.type() >= AnalogType::RGB + output["writeable"] = sensor.type() == AnalogType::COUNTER || sensor.type() == AnalogType::RGB || sensor.type() == AnalogType::PULSE || (sensor.type() >= AnalogType::DIGITAL_OUT && sensor.type() <= AnalogType::PWM_2); output["visible"] = true; if (sensor.type() == AnalogType::COUNTER) { @@ -842,6 +911,12 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) { rgbLedWrite(sensor.gpio(), 2 * r, 2 * g, 2 * b); #endif LOG_DEBUG("RGB set to %d, %d, %d", r, g, b); + } else if (sensor.type() == AnalogType::PULSE) { + uint8_t v = val; + sensor.set_value(v); + pinMode(sensor.gpio(), OUTPUT); + digitalWrite(sensor.gpio(), (sensor.offset() != 0) ^ (sensor.value() != 0)); + sensor.polltime_ = sensor.value() != 0 ? uuid::get_uptime() + (sensor.factor() * 1000) : 0; } else if (sensor.type() == AnalogType::DIGITAL_OUT) { uint8_t v = val; #if CONFIG_IDF_TARGET_ESP32 diff --git a/src/core/analogsensor.h b/src/core/analogsensor.h index 32b31a0e6..7125a2b3b 100644 --- a/src/core/analogsensor.h +++ b/src/core/analogsensor.h @@ -124,7 +124,11 @@ class AnalogSensor { PWM_1 = 8, PWM_2 = 9, NTC = 10, - RGB = 11 + RGB = 11, + PULSE = 12, + FREQ_0 = 13, + FREQ_1 = 14, + FREQ_2 = 15 }; void start(const bool factory_settings = false); @@ -190,6 +194,15 @@ class AnalogSensor { bool changed_ = true; // this will force a publish of all sensors when initialising uint32_t sensorfails_ = 0; uint32_t sensorreads_ = 0; + +#ifndef EMSESP_STANDALONE + static void IRAM_ATTR freqIrq0(); + static void IRAM_ATTR freqIrq1(); + static void IRAM_ATTR freqIrq2(); + static unsigned long edge[3]; + static unsigned long edgecnt[3]; + unsigned long lastedge[3] = {0, 0, 0}; +#endif }; } // namespace emsesp diff --git a/src/core/console.cpp b/src/core/console.cpp index 08b400dd6..a7dd705b6 100644 --- a/src/core/console.cpp +++ b/src/core/console.cpp @@ -364,6 +364,10 @@ static void setup_commands(std::shared_ptr const & commands) { // EMS device commands // + commands->add_command(ShellContext::MAIN, CommandFlags::ADMIN, {F_(scan)}, [](Shell & shell, const std::vector & arguments) { + EMSESP::scan_devices(); + }); + /* removed scan deep commands->add_command(ShellContext::MAIN, CommandFlags::ADMIN, {F_(scan)}, {F_(deep_optional)}, [](Shell & shell, const std::vector & arguments) { if (arguments.empty()) { EMSESP::scan_devices(); @@ -379,7 +383,7 @@ static void setup_commands(std::shared_ptr const & commands) { } } }); - + */ // read [offset] [length] commands->add_command(ShellContext::MAIN, CommandFlags::USER, diff --git a/src/core/device_library.h b/src/core/device_library.h index 1d5fc2e77..917b9f6ae 100644 --- a/src/core/device_library.h +++ b/src/core/device_library.h @@ -25,6 +25,7 @@ // Boilers - 0x08 { 8, DeviceType::BOILER, "CS5800i, CS6800i, WLW176i, WLW186i", DeviceFlags::EMS_DEVICE_FLAG_CS6800}, +{ 11, DeviceType::BOILER, "Greenstar 2000", DeviceFlags::EMS_DEVICE_FLAG_NONE}, { 12, DeviceType::BOILER, "C1200W", DeviceFlags::EMS_DEVICE_FLAG_NONE}, { 16, DeviceType::BOILER, "CS5800iG", DeviceFlags::EMS_DEVICE_FLAG_NONE}, { 64, DeviceType::BOILER, "BK13/BK15, Smartline, GB1*2", DeviceFlags::EMS_DEVICE_FLAG_NONE}, diff --git a/src/core/emsdevice.cpp b/src/core/emsdevice.cpp index 800bb95b5..605edc8dc 100644 --- a/src/core/emsdevice.cpp +++ b/src/core/emsdevice.cpp @@ -258,6 +258,9 @@ uint8_t EMSdevice::device_name_2_device_type(const char * topic) { if (!strcmp(lowtopic, F_(pool))) { return DeviceType::POOL; } + if (!strcmp(lowtopic, F_(connect))) { + return DeviceType::CONNECT; + } // non EMS if (!strcmp(lowtopic, F_(custom))) { @@ -1896,6 +1899,11 @@ void EMSdevice::mqtt_ha_entity_config_create() { create_device_config = false; // only create the main config once count++; } + // SRC thermostats mapped to connect/hs1/... + if (dv.tag >= DeviceValueTAG::TAG_HS1 && !strcmp(dv.short_name, FL_(roomtemp)[0])) { + Mqtt::publish_ha_climate_config(dv.tag, true, false, dv.min, dv.max); + } + #ifndef EMSESP_STANDALONE // always create minimum one config if (count && (heap_caps_get_free_size(MALLOC_CAP_8BIT) < 65 * 1024)) { // checks free Heap+PSRAM diff --git a/src/core/emsdevicevalue.cpp b/src/core/emsdevicevalue.cpp index 82bb45732..d62ca4628 100644 --- a/src/core/emsdevicevalue.cpp +++ b/src/core/emsdevicevalue.cpp @@ -108,10 +108,10 @@ DeviceValue::DeviceValue(uint8_t device_type, const char * DeviceValue::DeviceValueUOM_s[] = { F_(uom_blank), // 0 - F_(uom_degrees), F_(uom_degrees), F_(uom_percent), F_(uom_lmin), F_(uom_kwh), F_(uom_wh), FL_(hours)[0], - FL_(minutes)[0], F_(uom_ua), F_(uom_bar), F_(uom_kw), F_(uom_w), F_(uom_kb), FL_(seconds)[0], - F_(uom_dbm), F_(uom_fahrenheit), F_(uom_mv), F_(uom_sqm), F_(uom_m3), F_(uom_l), F_(uom_kmin), - F_(uom_k), F_(uom_volts), F_(uom_mbar), F_(uom_lh), F_(uom_ctkwh), F_(uom_blank) + F_(uom_degrees), F_(uom_degrees), F_(uom_percent), F_(uom_lmin), F_(uom_kwh), F_(uom_wh), FL_(hours)[0], FL_(minutes)[0], + F_(uom_ua), F_(uom_bar), F_(uom_kw), F_(uom_w), F_(uom_kb), FL_(seconds)[0], F_(uom_dbm), F_(uom_fahrenheit), + F_(uom_mv), F_(uom_sqm), F_(uom_m3), F_(uom_l), F_(uom_kmin), F_(uom_k), F_(uom_volts), F_(uom_mbar), + F_(uom_lh), F_(uom_ctkwh), F_(uom_hz), F_(uom_blank) }; diff --git a/src/core/emsdevicevalue.h b/src/core/emsdevicevalue.h index ec4bf4e76..49d509e8b 100644 --- a/src/core/emsdevicevalue.h +++ b/src/core/emsdevicevalue.h @@ -75,7 +75,8 @@ class DeviceValue { MBAR, // 24 - mbar LH, // 25 - l/h CTKWH, // 26 - ct/kWh - CONNECTIVITY // 27 - used in HA + HZ, // 27 - Hz + CONNECTIVITY // 28 - used in HA }; // TAG mapping - maps to DeviceValueTAG_s in emsdevicevalue.cpp diff --git a/src/core/emsesp.cpp b/src/core/emsesp.cpp index e605d560b..3accadf47 100644 --- a/src/core/emsesp.cpp +++ b/src/core/emsesp.cpp @@ -1307,7 +1307,11 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const default_name = "Wireless sensor base"; } } - + // map MX400 also to RF Base + if (device_id == EMSdevice::EMS_DEVICE_ID_RFBASE) { + device_type = DeviceType::CONNECT; + default_name = "Wireless base"; + } if ((device_id >= EMSdevice::EMS_DEVICE_ID_DHW1 && device_id <= EMSdevice::EMS_DEVICE_ID_DHW8) || device_id == EMSdevice::EMS_DEVICE_ID_IPM_DHW) { device_type = DeviceType::WATER; } diff --git a/src/core/locale_common.h b/src/core/locale_common.h index 36a072937..f3c5cdbd0 100644 --- a/src/core/locale_common.h +++ b/src/core/locale_common.h @@ -264,6 +264,7 @@ MAKE_WORD_CUSTOM(uom_volts, "V") MAKE_WORD_CUSTOM(uom_mbar, "mbar") MAKE_WORD_CUSTOM(uom_lh, "l/h") MAKE_WORD_CUSTOM(uom_ctkwh, "ct/kWh") +MAKE_WORD_CUSTOM(uom_hz, "Hz") // MQTT topics and prefixes MAKE_WORD_CUSTOM(heating_active, "heating_active") @@ -277,7 +278,8 @@ MAKE_ENUM_FIXED(list_syslog_level, "off", "emerg", "alert", "crit", "error", "wa MAKE_ENUM_FIXED(counter, "counter") MAKE_ENUM_FIXED(digital_out, "digital_out") MAKE_ENUM_FIXED(RGB, "RGB") -MAKE_ENUM_FIXED(list_sensortype, "disabled", "digital in", "counter", "adc", "timer", "rate", "digital out", "pwm 0", "pwm 1", "pwm 2", "NTC Temp", "RGB Led") +MAKE_ENUM_FIXED(pulse, "pulse") +MAKE_ENUM_FIXED(list_sensortype, "disabled", "digital in", "counter", "adc", "timer", "rate", "digital out", "pwm 0", "pwm 1", "pwm 2", "NTC Temp", "RGB Led", "pulse", "freq 0", "freq 1", "freq 2") // watch MAKE_ENUM_FIXED(list_watch, "off", "on", "raw", "unknown") @@ -337,6 +339,7 @@ MAKE_ENUM(enum_mode4, FL_(nofrost), FL_(eco), FL_(heat), FL_(auto)) // JUNKERS MAKE_ENUM(enum_mode5, FL_(auto), FL_(off)) // CRF MAKE_ENUM(enum_mode6, FL_(nofrost), FL_(night), FL_(day)) // RC10 MAKE_ENUM(enum_mode7, FL_(off), FL_(manual)) // CR11 +MAKE_ENUM(enum_mode8, FL_(auto), FL_(heat), FL_(cool), FL_(off)) // SRC room thermostats MAKE_ENUM(enum_mode_ha, FL_(off), FL_(heat), FL_(auto)) // HA climate MAKE_ENUM(enum_modetype, FL_(eco), FL_(comfort)) diff --git a/src/core/locale_translations.h b/src/core/locale_translations.h index 80e5af84b..133356a92 100644 --- a/src/core/locale_translations.h +++ b/src/core/locale_translations.h @@ -915,6 +915,9 @@ MAKE_TRANSLATION(status, "status", "status", "Status", "Status", "Status", "stat // RF sensor, id 0x40, telegram 0x435 MAKE_TRANSLATION(RFTemp, "rftemp", "RF room temperature sensor", "RF Raumtemperatursensor", "RF ruimtetemperatuur sensor", "RF Rumsgivare Temperatur", "bezprzewodowy czujnik temperatury pomieszczenia", "RF romsgiver temp", "capteur de température de pièce RF", "RF oda sıcaklık sensörü", "Sensore di temperatura ambiente RF", "RF snímač izbovej teploty", "RF senzor teploty místnosti") +// gateway thermostat +MAKE_TRANSLATION(name, "name", "name", "Name") + // ventilation MAKE_TRANSLATION(outFresh, "outfresh", "outdoor fresh air", "Außenlufttemp.", "temperatuur buitenlucht", "Utelufttemperatur", "świeże powietrze z zewnątrz", "", "", "dış ortam taze hava", "aria fresca esterna", "čerstvý vzduch vonku", "venkovní čerstvý vzduch") // TODO translate MAKE_TRANSLATION(inFresh, "infresh", "indoor fresh air", "Zulufttemp.", "temperatuur aanvoer", "Tillufttemperatur", "nawiew", "", "", "iç ortam taze hava", "aria fresca interna", "čerstvý vzduch v interiéri", "vnitřní čerstvý vzduch") // TODO translate diff --git a/src/core/mqtt.cpp b/src/core/mqtt.cpp index b87216f9c..272426cc4 100644 --- a/src/core/mqtt.cpp +++ b/src/core/mqtt.cpp @@ -528,8 +528,8 @@ void Mqtt::ha_status() { strcpy(uniq, "system_status"); } - doc["uniq_id"] = uniq; - doc["obj_id"] = uniq; + doc["uniq_id"] = uniq; + doc["default_entity_id"] = uniq; doc["stat_t"] = Mqtt::base() + "/status"; doc["name"] = "System status"; @@ -827,7 +827,7 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev snprintf(entity_with_tag, sizeof(entity_with_tag), "%s", entity); } - // build unique identifier also used as object_id which also becomes the Entity ID in HA + // build unique identifier also used as default_entity_id which also becomes the Entity ID in HA char uniq_id[80]; // list of boiler entities that need conversion for 3.6 compatibility, add ww suffix @@ -980,8 +980,8 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev // build the full payload JsonDocument doc; - doc["uniq_id"] = uniq_id; - doc["obj_id"] = uniq_id; // same as unique_id + doc["uniq_id"] = uniq_id; + doc["default_entity_id"] = uniq_id; // same as unique_id char sample_val[30] = "0"; // sample, correct(!) entity value, used only to prevent warning/error in HA if real value is not published yet @@ -1235,7 +1235,9 @@ void Mqtt::add_ha_classes(JsonObject doc, const uint8_t device_type, const uint8 } bool Mqtt::publish_ha_climate_config(const int8_t tag, const bool has_roomtemp, const bool remove, const int16_t min, const uint32_t max) { - uint8_t hc_num = tag; + uint8_t hc_num = tag < DeviceValueTAG::TAG_HS1 ? tag : tag - DeviceValueTAG::TAG_HS1 + 1; + const char * devicename = tag < DeviceValueTAG::TAG_HS1 ? "thermostat" : "connect"; + const char * tagname = tag < DeviceValueTAG::TAG_HS1 ? "hc" : "hs"; char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; char topic_t[Mqtt::MQTT_TOPIC_MAX_SIZE]; @@ -1253,21 +1255,21 @@ bool Mqtt::publish_ha_climate_config(const int8_t tag, const bool has_roomtemp, char min_s[10]; char max_s[10]; - snprintf(topic, sizeof(topic), "climate/%s/thermostat_hc%d/config", Mqtt::basename().c_str(), hc_num); + snprintf(topic, sizeof(topic), "climate/%s/%s_%s%d/config", Mqtt::basename().c_str(), devicename, tagname, hc_num); if (remove) { return queue_remove_topic(topic); // publish empty payload with retain flag } if (Mqtt::is_nested()) { // nested format - snprintf(hc_mode_s, sizeof(hc_mode_s), "value_json.hc%d.mode", hc_num); - snprintf(hc_mode_cond, sizeof(hc_mode_cond), "value_json.hc%d is undefined or %s is undefined", hc_num, hc_mode_s); - snprintf(seltemp_s, sizeof(seltemp_s), "value_json.hc%d.seltemp", hc_num); - snprintf(seltemp_cond, sizeof(seltemp_cond), "value_json.hc%d is defined and %s is defined", hc_num, seltemp_s); + snprintf(hc_mode_s, sizeof(hc_mode_s), "value_json.%s%d.mode", tagname, hc_num); + snprintf(hc_mode_cond, sizeof(hc_mode_cond), "value_json.%s%d is undefined or %s is undefined", tagname, hc_num, hc_mode_s); + snprintf(seltemp_s, sizeof(seltemp_s), "value_json.%s%d.seltemp", tagname, hc_num); + snprintf(seltemp_cond, sizeof(seltemp_cond), "value_json.%s%d is defined and %s is defined", tagname, hc_num, seltemp_s); if (has_roomtemp) { - snprintf(currtemp_s, sizeof(currtemp_s), "value_json.hc%d.currtemp", hc_num); - snprintf(currtemp_cond, sizeof(currtemp_cond), "value_json.hc%d is defined and %s is defined", hc_num, currtemp_s); + snprintf(currtemp_s, sizeof(currtemp_s), "value_json.%s%d.currtemp", tagname, hc_num); + snprintf(currtemp_cond, sizeof(currtemp_cond), "value_json.%s%d is defined and %s is defined", tagname, hc_num, currtemp_s); } snprintf(topic_t, sizeof(topic_t), "~/%s", Mqtt::tag_to_topic(EMSdevice::DeviceType::THERMOSTAT, DeviceValueTAG::TAG_NONE).c_str()); } else { @@ -1281,7 +1283,10 @@ bool Mqtt::publish_ha_climate_config(const int8_t tag, const bool has_roomtemp, snprintf(currtemp_s, sizeof(currtemp_s), "value_json.currtemp"); snprintf(currtemp_cond, sizeof(currtemp_cond), "%s is defined", currtemp_s); } - snprintf(topic_t, sizeof(topic_t), "~/%s", Mqtt::tag_to_topic(EMSdevice::DeviceType::THERMOSTAT, DeviceValueTAG::TAG_HC1 + hc_num - 1).c_str()); + snprintf(topic_t, + sizeof(topic_t), + "~/%s", + Mqtt::tag_to_topic(tag < DeviceValueTAG::TAG_HS1 ? EMSdevice::DeviceType::THERMOSTAT : EMSdevice::DeviceType::CONNECT, tag).c_str()); } snprintf(mode_str_tpl, @@ -1297,28 +1302,28 @@ bool Mqtt::publish_ha_climate_config(const int8_t tag, const bool has_roomtemp, hc_mode_s, Helpers::translated_word(FL_(off))); - snprintf(name_s, sizeof(name_s), "Hc%d", hc_num); + snprintf(name_s, sizeof(name_s), "%s%d", tag < DeviceValueTAG::TAG_HS1 ? "Hc" : "Hs", hc_num); if (Mqtt::entity_format() == entityFormat::MULTI_SHORT) { - snprintf(uniq_id_s, sizeof(uniq_id_s), "%s_thermostat_hc%d", Mqtt::basename().c_str(), hc_num); // add basename + snprintf(uniq_id_s, sizeof(uniq_id_s), "%s_%s%s%d", Mqtt::basename().c_str(), devicename, tagname, hc_num); // add basename } else { - snprintf(uniq_id_s, sizeof(uniq_id_s), "thermostat_hc%d", hc_num); // backward compatible with v3.4 + snprintf(uniq_id_s, sizeof(uniq_id_s), "%s%d", devicename, hc_num); // backward compatible with v3.4 } - snprintf(temp_cmd_s, sizeof(temp_cmd_s), "~/thermostat/hc%d/seltemp", hc_num); - snprintf(mode_cmd_s, sizeof(mode_cmd_s), "~/thermostat/hc%d/mode", hc_num); + snprintf(temp_cmd_s, sizeof(temp_cmd_s), "~/%s/%s%d/seltemp", devicename, tagname, hc_num); + snprintf(mode_cmd_s, sizeof(mode_cmd_s), "~/%s/%s%d/mode", devicename, tagname, hc_num); JsonDocument doc; - doc["~"] = Mqtt::base(); - doc["uniq_id"] = uniq_id_s; - doc["obj_id"] = uniq_id_s; // same as uniq_id - doc["name"] = name_s; - doc["mode_stat_t"] = topic_t; - doc["mode_stat_tpl"] = mode_str_tpl; - doc["temp_cmd_t"] = temp_cmd_s; - doc["temp_stat_t"] = topic_t; - doc["temp_stat_tpl"] = (std::string) "{{" + seltemp_s + " if " + seltemp_cond + " else 0}}"; + doc["~"] = Mqtt::base(); + doc["uniq_id"] = uniq_id_s; + doc["default_entity_id"] = uniq_id_s; // same as uniq_id + doc["name"] = name_s; + doc["mode_stat_t"] = topic_t; + doc["mode_stat_tpl"] = mode_str_tpl; + doc["temp_cmd_t"] = temp_cmd_s; + doc["temp_stat_t"] = topic_t; + doc["temp_stat_tpl"] = (std::string) "{{" + seltemp_s + " if " + seltemp_cond + " else 0}}"; if (has_roomtemp) { doc["curr_temp_t"] = topic_t; @@ -1341,7 +1346,7 @@ bool Mqtt::publish_ha_climate_config(const int8_t tag, const bool has_roomtemp, modes.add("heat"); modes.add("off"); - add_ha_dev_section(doc.as(), "thermostat", nullptr, nullptr, nullptr, false); // add dev section + add_ha_dev_section(doc.as(), devicename, nullptr, nullptr, nullptr, false); // add dev section add_ha_avail_section(doc.as(), topic_t, false, seltemp_cond, has_roomtemp ? currtemp_cond : nullptr, hc_mode_cond); // add availability section return queue_ha(topic, doc.as()); // publish the config payload with retain flag diff --git a/src/core/shuntingYard.cpp b/src/core/shuntingYard.cpp index 4ee8c30bf..5cb262f50 100644 --- a/src/core/shuntingYard.cpp +++ b/src/core/shuntingYard.cpp @@ -518,7 +518,7 @@ std::string calculate(const std::string & expr) { #ifndef EMSESP_STANDALONE stack.push_back(to_string(rhd * esp_random() / UINT32_MAX)); #else - stack.push_back(to_string(rhd * random())); + stack.push_back(to_string(rhd * rand() / RAND_MAX)); #endif break; } diff --git a/src/devices/boiler.cpp b/src/devices/boiler.cpp index 130133672..acef3e50e 100644 --- a/src/devices/boiler.cpp +++ b/src/devices/boiler.cpp @@ -66,6 +66,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const register_telegram_type(0xE9, "UBAMonitorWWPlus", false, MAKE_PF_CB(process_UBAMonitorWWPlus)); register_telegram_type(0xEA, "UBAParameterWWPlus", true, MAKE_PF_CB(process_UBAParameterWWPlus)); register_telegram_type(0x28, "WeatherComp", true, MAKE_PF_CB(process_WeatherComp)); + register_telegram_type(0x2E0, "UBASetPoints", false, MAKE_PF_CB(process_UBASetPoints2)); } if (isHeatPump()) { @@ -1208,8 +1209,13 @@ void Boiler::check_active() { static uint32_t lastSendHeatingOff = 0; if (forceHeatingOff_ == EMS_VALUE_BOOL_ON && (uuid::get_uptime_sec() - lastSendHeatingOff) >= 60) { lastSendHeatingOff = uuid::get_uptime_sec(); - uint8_t data[] = {0, 0, 0, 0}; - write_command(EMS_TYPE_UBASetPoints, 0, data, sizeof(data), 0); + if (has_telegram_id(0xE4)) { + uint8_t data[] = {1, 0, 0, 1, 1}; + write_command(EMS_TYPE_UBASetPoints2, 0, data, sizeof(data), 0); + } else { + uint8_t data[] = {0, 0, 0, 0}; + write_command(EMS_TYPE_UBASetPoints, 0, data, sizeof(data), 0); + } } // calculate energy for boiler 0x08 from stored modulation an time in units of 0.01 Wh @@ -1832,20 +1838,34 @@ void Boiler::process_UBAOutdoorTemp(std::shared_ptr telegram) { // UBASetPoint 0x1A void Boiler::process_UBASetPoints(std::shared_ptr telegram) { - uint8_t setFlowTemp_ = 0; - uint8_t setBurnPow_ = 0; - uint8_t wwSetBurnPow_ = 0; + uint8_t setFlowTemp_ = 0; + uint8_t setBurnPow_ = 0; + uint8_t setPumpMod_ = 0; telegram->read_value(setFlowTemp_, 0); telegram->read_value(setBurnPow_, 1); - telegram->read_value(wwSetBurnPow_, 2); + telegram->read_value(setPumpMod_, 2); - // overwrite other settings on receive? - if (forceHeatingOff_ == EMS_VALUE_BOOL_ON && telegram->dest == 0x08 && (setFlowTemp_ + setBurnPow_ + wwSetBurnPow_) != 0) { + // forceHeatingOff overwrite to zero + if (forceHeatingOff_ == EMS_VALUE_BOOL_ON && telegram->dest == 0x08 && (setFlowTemp_ + setBurnPow_ + setPumpMod_) != 0) { uint8_t data[] = {0, 0, 0, 0}; write_command(EMS_TYPE_UBASetPoints, 0, data, sizeof(data), 0); } } +// UBASetPoints ems+ 0x2E0 +void Boiler::process_UBASetPoints2(std::shared_ptr telegram) { + uint8_t setFlowTemp_ = 0; + uint8_t setBurnPow_ = 0; + telegram->read_value(setFlowTemp_, 0); + telegram->read_value(setBurnPow_, 1); + + // forceHeatingOff overwrite to zero + if (forceHeatingOff_ == EMS_VALUE_BOOL_ON && telegram->dest == 0x08 && (setFlowTemp_ + setBurnPow_) != 0) { + uint8_t data[] = {1, 0, 0, 1, 1}; + write_command(EMS_TYPE_UBASetPoints2, 0, data, sizeof(data), 0); + } +} + // 0x35 - not yet implemented, not readable, only for settings void Boiler::process_UBAFlags(std::shared_ptr telegram) { } @@ -2419,12 +2439,17 @@ bool Boiler::set_flow_temp(const char * value, const int8_t id) { // no verify if value is unchanged, put it to end of tx-queue, no priority // see https://github.com/emsesp/EMS-ESP32/issues/654, https://github.com/emsesp/EMS-ESP32/issues/954 if (v == selFlowTemp_) { - EMSESP::txservice_.add(Telegram::Operation::TX_WRITE, device_id(), EMS_TYPE_UBASetPoints, 0, (uint8_t *)&v, 1, 0, false); + uint8_t v1 = v; + if (has_telegram_id(0xE4)) { + EMSESP::txservice_.add(Telegram::Operation::TX_WRITE, device_id(), EMS_TYPE_UBASetPoints2, 1, &v1, 1, 0, false); + } else { + EMSESP::txservice_.add(Telegram::Operation::TX_WRITE, device_id(), EMS_TYPE_UBASetPoints, 0, &v1, 1, 0, false); + } return true; } if (has_telegram_id(0xE4)) { - write_command(EMS_TYPE_UBASetPoints, 0, v, 0xE4); + write_command(EMS_TYPE_UBASetPoints2, 1, v, 0xE4); } else { write_command(EMS_TYPE_UBASetPoints, 0, v, 0x18); } @@ -2438,8 +2463,11 @@ bool Boiler::set_burn_power(const char * value, const int8_t id) { return false; } - write_command(EMS_TYPE_UBASetPoints, 1, v, EMS_TYPE_UBASetPoints); - + if (has_telegram_id(0xE4)) { + write_command(EMS_TYPE_UBASetPoints2, 2, v); + } else { + write_command(EMS_TYPE_UBASetPoints, 1, v); + } return true; } @@ -3415,14 +3443,20 @@ bool Boiler::set_wwAltOpPrio(const char * value, const int8_t id) { bool Boiler::set_forceHeatingOff(const char * value, const int8_t id) { bool v; if (Helpers::value2bool(value, v)) { - has_update(forceHeatingOff_, v); - if (!v && Helpers::hasValue(heatingTemp_)) { - uint8_t data[] = {heatingTemp_, - (Helpers::hasValue(burnMaxPower_) ? burnMaxPower_ : (uint8_t)100), - (Helpers::hasValue(pumpModMax_) ? pumpModMax_ : (uint8_t)0), - 0}; - write_command(EMS_TYPE_UBASetPoints, 0, data, sizeof(data), 0); + // set only on change on->off + if (!v && forceHeatingOff_ && Helpers::hasValue(heatingTemp_)) { + if (has_telegram_id(0xE4)) { + uint8_t data[] = {1, heatingTemp_, (Helpers::hasValue(burnMaxPower_) ? burnMaxPower_ : (uint8_t)100), 1, 1}; + write_command(EMS_TYPE_UBASetPoints2, 0, data, sizeof(data), 0); + } else { + uint8_t data[] = {heatingTemp_, + (Helpers::hasValue(burnMaxPower_) ? burnMaxPower_ : (uint8_t)100), + (Helpers::hasValue(pumpModMax_) ? pumpModMax_ : (uint8_t)100), + 0}; + write_command(EMS_TYPE_UBASetPoints, 0, data, sizeof(data), 0); + } } + has_update(forceHeatingOff_, v); return true; } return false; diff --git a/src/devices/boiler.h b/src/devices/boiler.h index 3ff03cf76..aeee7b4e4 100644 --- a/src/devices/boiler.h +++ b/src/devices/boiler.h @@ -354,6 +354,7 @@ class Boiler : public EMSdevice { void process_UBAParameterWWPlus(std::shared_ptr telegram); void process_UBAOutdoorTemp(std::shared_ptr telegram); void process_UBASetPoints(std::shared_ptr telegram); + void process_UBASetPoints2(std::shared_ptr telegram); void process_UBAFlags(std::shared_ptr telegram); void process_MC110Status(std::shared_ptr telegram); void process_UBAMaintenanceStatus(std::shared_ptr telegram); diff --git a/src/devices/connect.cpp b/src/devices/connect.cpp index 03303b257..0579d0f70 100644 --- a/src/devices/connect.cpp +++ b/src/devices/connect.cpp @@ -32,6 +32,18 @@ Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, con DeviceValueNumOp::DV_NUMOP_DIV10, FL_(outdoorTemp), DeviceValueUOM::DEGREES); + // Roomthermostats + for (uint8_t i = 0; i < 16; i++) { + register_telegram_type(0x0BDD + i, "Room", false, MAKE_PF_CB(process_roomThermostat)); // broadcasted + register_telegram_type(0x0B3D + i, "Roomname", false, MAKE_PF_CB(process_roomThermostatName)); // fetch for active circuits + register_telegram_type(0x0BB5 + i, "Roomsettings", false, MAKE_PF_CB(process_roomThermostatMode)); // fetch for active circuits + register_telegram_type(0x1230 + i, "Roomparams", false, MAKE_PF_CB(process_roomThermostatParam)); // fetch for active circuits + register_telegram_type(0x1244 + i, "Roomdata", false, MAKE_PF_CB(process_roomThermostatData)); // broadcasted + } + // register_telegram_type(0xDB65, "Roomschedule", true, MAKE_PF_CB(process_roomSchedule)); + // 0x2040, broadcast 36 bytes: + // data: 0E 60 00 DF 0D AF 0A 46 0A 46 02 9A 1C 53 1C 53 12 AD 12 AD 00 00 13 C2 + // data: 1F 37 1F 37 00 00 00 00 18 97 11 27 (offset 24) } } /* @@ -46,6 +58,151 @@ void Connect::process_OutdoorTemp(std::shared_ptr telegram) { (0x0880), data: 01 04 (0x0889), data: 00 80 80 01 */ +void Connect::register_device_values_room(std::shared_ptr room) { + auto tag = DeviceValueTAG::TAG_HS1 + room->room(); + register_device_value(tag, &room->temp_, DeviceValueType::INT16, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(roomTemp), DeviceValueUOM::DEGREES); + register_device_value(tag, &room->humidity_, DeviceValueType::INT8, FL_(airHumidity), DeviceValueUOM::PERCENT); + register_device_value(tag, &room->dewtemp_, DeviceValueType::INT16, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(dewTemperature), DeviceValueUOM::DEGREES); + register_device_value( + tag, &room->seltemp_, DeviceValueType::UINT8, DeviceValueNumOp::DV_NUMOP_DIV2, FL_(seltemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_seltemp)); + register_device_value(tag, &room->mode_, DeviceValueType::ENUM, FL_(enum_mode8), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode)); + register_device_value(tag, &room->name_, DeviceValueType::STRING, FL_(name), DeviceValueUOM::NONE, MAKE_CF_CB(set_name)); +} + +std::shared_ptr Connect::room_circuit(const uint8_t num, const bool create) { + // check for existing circuit + for (const auto & room_circuit : room_circuits_) { + if (room_circuit->room() == num) { + return room_circuit; + } + } + if (!create) { + return nullptr; + } + // create a new circuit object and add to the list + auto new_room = std::make_shared(num); + room_circuits_.push_back(new_room); + // register the device values + register_device_values_room(new_room); + toggle_fetch(0x0B3D + num, true); // name + toggle_fetch(0x0BB5 + num, true); // mode + toggle_fetch(0x1230 + num, true); // unknown + + return new_room; // return back point to new HC object +} + +// gateway(0x50) B all(0x00), ?(0x0BDD), data: 00 E6 36 2A +void Connect::process_roomThermostat(std::shared_ptr telegram) { + bool create = telegram->offset == 0 && telegram->message_data[0] < 0x80; + auto rc = room_circuit(telegram->type_id - 0xBDD, create); + if (rc == nullptr) { + return; + } + has_update(telegram, rc->temp_, 0); + has_update(telegram, rc->humidity_, 2); // could show -3 if not set + has_update(telegram, rc->seltemp_, 3); + + // calculate dew temperature + const float k2 = 17.62; + const float k3 = 243.12; + const float t = (float)rc->temp_ / 10; + const float h = (float)rc->humidity_ / 100; + int16_t dt = (10 * k3 * (((k2 * t) / (k3 + t)) + log(h)) / (((k2 * k3) / (k3 + t)) - log(h))); + has_update(rc->dewtemp_, dt); +} + +// gateway(0x48) W gateway(0x50), ?(0x0B42), data: 01 +// gateway(0x48) W gateway(0x50), ?(0x0B42), data: 00 4B 00 FC 00 63 00 68 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 (offset 1) +void Connect::process_roomThermostatName(std::shared_ptr telegram) { + auto rc = room_circuit(telegram->type_id - 0xB3D); + if (rc == nullptr) { + return; + } + + for (uint8_t i = telegram->offset; i < telegram->message_length + telegram->offset && i < 100; i++) { + if ((i > 1) && (i % 2) == 0) { + rc->name_[(i - 2) / 2] = telegram->message_data[i]; + } + } + rc->name_[50] = '\0'; // make sure name is terminated +} + +// settings 0-mode, 1-tempautotemp, 3 - manualtemp, 6,7 - ? +void Connect::process_roomThermostatMode(std::shared_ptr telegram) { + auto rc = room_circuit(telegram->type_id - 0xBB5); + if (rc == nullptr) { + return; + } + // has_enumupdate(telegram, rc->mode_, 0, {3, 1, 0}); + has_update(telegram, rc->mode_, 0); +} + +// unknown telegrams, needs fetch +void Connect::process_roomThermostatParam(std::shared_ptr telegram) { + auto rc = room_circuit(telegram->type_id - 0x1230); + if (rc == nullptr) { + return; + } +} + +// unknown broadcasted telegrams +void Connect::process_roomThermostatData(std::shared_ptr telegram) { + auto rc = room_circuit(telegram->type_id - 0x1244); + if (rc == nullptr) { + return; + } +} + +// Settings: + +bool Connect::set_mode(const char * value, const int8_t id) { + auto rc = room_circuit(id - DeviceValueTAG::TAG_HS1); + if (rc == nullptr) { + return false; + } + uint8_t v; + // if (Helpers::value2enum(value, v, FL_(enum_mode2), {3, 1, 0})) { + if (Helpers::value2enum(value, v, FL_(enum_mode8))) { + write_command(0xBB5 + rc->room(), 0, v); // no validate, mode change is broadcasted + return true; + } + return false; +} + +bool Connect::set_seltemp(const char * value, const int8_t id) { + auto rc = room_circuit(id - DeviceValueTAG::TAG_HS1); + if (rc == nullptr) { + return false; + } + float v; + if (Helpers::value2float(value, v)) { + // write_command(0xBB5 + rc->room(), rc->mode_ == 2 ? 1 : 3, v == -1 ? 0xFF : uint8_t(v * 2)); + write_command(0xBB5 + rc->room(), rc->mode_ == 0 ? 1 : 3, v == -1 ? 0xFF : uint8_t(v * 2)); + return true; + } + return false; +} + +bool Connect::set_name(const char * value, const int8_t id) { + auto rc = room_circuit(id - DeviceValueTAG::TAG_HS1); + if (rc == nullptr || value == nullptr || strlen(value) > 50) { + return false; + } + uint8_t len = strlen(value) * 2 + 2; + uint8_t data[len]; + for (uint8_t i = 0; i < strlen(value) + 1; i++) { // include terminating '\0' + data[2 * i] = 0; + data[2 * i + 1] = value[i]; + } + uint8_t ofs = 0; + while (len > 0) { + uint8_t part = len > 25 ? 25 : len; + write_command(0x0B3D + rc->room(), ofs + 1, &data[ofs], part, 0); + ofs += part; + len -= part; + } + return true; +} } // namespace emsesp diff --git a/src/devices/connect.h b/src/devices/connect.h index 5db2e11d3..3c1b48713 100644 --- a/src/devices/connect.h +++ b/src/devices/connect.h @@ -27,7 +27,42 @@ class Connect : public EMSdevice { public: Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand); + class RoomCircuit { + public: + RoomCircuit(const uint8_t num) + : room_(num) { + } + ~RoomCircuit() = default; + int16_t temp_; + int8_t humidity_; + uint8_t seltemp_; + uint8_t mode_; + char name_[51]; + int16_t dewtemp_; + + uint8_t room() { + return room_; + } + + private: + uint8_t room_; // dhw circuit number 0..10 + }; + private: + std::shared_ptr room_circuit(const uint8_t num, const bool create = false); + + void register_device_values_room(std::shared_ptr room); + void process_roomThermostat(std::shared_ptr telegram); + void process_roomThermostatName(std::shared_ptr telegram); + void process_roomThermostatMode(std::shared_ptr telegram); + void process_roomThermostatParam(std::shared_ptr telegram); + void process_roomThermostatData(std::shared_ptr telegram); + bool set_mode(const char * value, const int8_t id); + bool set_seltemp(const char * value, const int8_t id); + bool set_name(const char * value, const int8_t id); + + std::vector> room_circuits_; + void process_OutdoorTemp(std::shared_ptr telegram); int16_t outdoorTemp_; }; diff --git a/src/devices/thermostat.cpp b/src/devices/thermostat.cpp index 00eb431d1..c4e5ba166 100644 --- a/src/devices/thermostat.cpp +++ b/src/devices/thermostat.cpp @@ -2006,12 +2006,12 @@ bool Thermostat::set_calinttemp(const char * value, const int8_t id) { auto t = (int8_t)(ct * 10); LOG_DEBUG("Calibrating internal temperature to %d.%d C", t / 10, t < 0 ? -t % 10 : t % 10); - if (model() == EMSdevice::EMS_DEVICE_FLAG_RC10) { + if (device_id() >= 0x38 && device_id() <= 0x3F) { // remote thermostats RC100H, CR10, ... + write_command(0x273 + device_id() - 0x38, 0, t, 0x273 + device_id() - 0x38); + } else if (model() == EMSdevice::EMS_DEVICE_FLAG_RC10) { write_command(0xB0, 0, t, 0xB0); } else if (model() == EMSdevice::EMS_DEVICE_FLAG_RC30) { write_command(EMS_TYPE_RC30Settings, 1, t, EMS_TYPE_RC30Settings); - } else if (model() == EMSdevice::EMS_DEVICE_FLAG_RC100H) { - write_command(0x273, 0, t, 0x273); } else if (model() == EMSdevice::EMS_DEVICE_FLAG_RC100) { write_command(0x241, 7, t, 0x241); } else if (isRC300()) { diff --git a/src/devices/ventilation.cpp b/src/devices/ventilation.cpp index 8dcdf6605..6b41fb8d8 100644 --- a/src/devices/ventilation.cpp +++ b/src/devices/ventilation.cpp @@ -30,7 +30,7 @@ Ventilation::Ventilation(uint8_t device_type, uint8_t device_id, uint8_t product register_telegram_type(0x583, "VentilationMonitor", false, MAKE_PF_CB(process_MonitorMessage)); register_telegram_type(0x5D9, "Airquality", false, MAKE_PF_CB(process_VOCMessage)); register_telegram_type(0x587, "Bypass", false, MAKE_PF_CB(process_BypassMessage)); - // register_telegram_type(0x5, "VentilationSet", true, MAKE_PF_CB(process_SetMessage)); + register_telegram_type(0x55C, "VentilationSet", true, MAKE_PF_CB(process_SetMessage)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &outFresh_, @@ -50,8 +50,9 @@ Ventilation::Ventilation(uint8_t device_type, uint8_t device_id, uint8_t product register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &bypass_, DeviceValueType::BOOL, FL_(airbypass), DeviceValueUOM::NONE, MAKE_CF_CB(set_bypass)); } -// message +// message 0x055C, data: 08 01 11 17 void Ventilation::process_SetMessage(std::shared_ptr telegram) { + has_update(telegram, bypass_, 1); } // message 583 @@ -85,9 +86,10 @@ void Ventilation::process_ModeMessage(std::shared_ptr telegram) has_enumupdate(telegram, mode_, 0, -1); } -// message 0x0587, data: 01 00 +// message 0x0587, data: 00 00 64 00 64 0A 00 01 54 01 00 01 00 00 00 46 00 00 00 02 00 A3 00 A3 void Ventilation::process_BypassMessage(std::shared_ptr telegram) { - has_update(telegram, bypass_, 1); + // has_update(telegram, bypass_closing, 0); + // has_update(telegram, bypass_opening, 1); } bool Ventilation::set_ventMode(const char * value, const int8_t id) { diff --git a/src/devices/ventilation.h b/src/devices/ventilation.h index f76fc7185..d01dcd6a0 100644 --- a/src/devices/ventilation.h +++ b/src/devices/ventilation.h @@ -41,7 +41,7 @@ class Ventilation : public EMSdevice { uint8_t ventOutSpeed_; // handlers: 0x056B 0x0575 0x0583 0x0585 0x0586 0x0587 0x0588 0x058D 0x058E 0x058F 0x0590 0x05CF 0x05D9 0x05E3 - void process_SetMessage(std::shared_ptr telegram); + void process_SetMessage(std::shared_ptr telegram); // 0x55C void process_MonitorMessage(std::shared_ptr telegram); void process_ModeMessage(std::shared_ptr telegram); // 0x56B void process_BlowerMessage(std::shared_ptr telegram); // 0x56B diff --git a/src/emsesp_version.h b/src/emsesp_version.h index 453758804..096657251 100644 --- a/src/emsesp_version.h +++ b/src/emsesp_version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.7.3-dev.18" +#define EMSESP_APP_VERSION "3.7.3-dev.19" diff --git a/src/web/WebDataService.cpp b/src/web/WebDataService.cpp index 13489e7c8..735a35182 100644 --- a/src/web/WebDataService.cpp +++ b/src/web/WebDataService.cpp @@ -436,7 +436,8 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) { obj["v"] = Helpers::transformNumFloat(sensor.value()); } else #endif - if (sensor.type() == AnalogSensor::AnalogType::DIGITAL_OUT || sensor.type() == AnalogSensor::AnalogType::DIGITAL_IN) { + if (sensor.type() == AnalogSensor::AnalogType::DIGITAL_OUT || sensor.type() == AnalogSensor::AnalogType::DIGITAL_IN + || sensor.type() == AnalogSensor::AnalogType::PULSE) { char s[12]; dv["v"] = Helpers::render_boolean(s, sensor.value() != 0, true); JsonArray l = dv["l"].to(); @@ -448,7 +449,7 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) { } if (sensor.type() == AnalogSensor::AnalogType::COUNTER || (sensor.type() >= AnalogSensor::AnalogType::DIGITAL_OUT && sensor.type() <= AnalogSensor::AnalogType::PWM_2) - || sensor.type() == AnalogSensor::AnalogType::RGB) { + || sensor.type() == AnalogSensor::AnalogType::RGB || sensor.type() == AnalogSensor::AnalogType::PULSE) { dv["c"] = sensor.name(); } }