From 7f21bea8a62ff6a1be59d9d9d9de5c4be88b8569 Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Wed, 16 Feb 2022 15:41:12 +0100 Subject: [PATCH 01/12] Thermostat: RC20 temperatures, RC300 roominflfactor, fetch monitor --- src/devices/thermostat.cpp | 127 +++++++++++++++++++++++++++++++++++-- src/devices/thermostat.h | 10 +++ src/locale_EN.h | 5 ++ 3 files changed, 136 insertions(+), 6 deletions(-) diff --git a/src/devices/thermostat.cpp b/src/devices/thermostat.cpp index 0c6e14ea1..279ead77c 100644 --- a/src/devices/thermostat.cpp +++ b/src/devices/thermostat.cpp @@ -80,10 +80,14 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i } else if (model == EMSdevice::EMS_DEVICE_FLAG_RC20) { monitor_typeids = {0x91}; set_typeids = {0xA8}; + curve_typeids = {0x90}; + timer_typeids = {0x8F}; if (actual_master_thermostat == device_id) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) { register_telegram_type(monitor_typeids[i], F("RC20Monitor"), false, MAKE_PF_CB(process_RC20Monitor)); register_telegram_type(set_typeids[i], F("RC20Set"), false, MAKE_PF_CB(process_RC20Set)); + register_telegram_type(curve_typeids[i], F("RC20Temp"), false, MAKE_PF_CB(process_RC20Temp)); + register_telegram_type(timer_typeids[i], F("RC20Timer"), false, MAKE_PF_CB(process_RC20Timer)); } } // remote thermostat uses only 0xAF, register it also for master (in case of early detect) @@ -345,6 +349,9 @@ std::shared_ptr Thermostat::heating_circuit(std::sha } // set the flag saying we want its data during the next auto fetch + // monitor is broadcasted, but not frequently in some thermostats (IVT, #356) + toggle_fetch(monitor_typeids[hc_num - 1], toggle_); + if (set_typeids.size()) { toggle_fetch(set_typeids[hc_num - 1], toggle_); } @@ -384,7 +391,7 @@ void Thermostat::publish_ha_config_hc(std::shared_ptrroomTemp); - if (Mqtt::nested_format() == 1) { + if (Mqtt::is_nested()) { // nested format snprintf(hc_mode_s, sizeof(hc_mode_s), "value_json.hc%d.mode", hc_num); snprintf(seltemp_s, sizeof(seltemp_s), "{{value_json.hc%d.seltemp}}", hc_num); @@ -727,6 +734,48 @@ void Thermostat::process_RC20Set(std::shared_ptr telegram) { has_update(telegram, hc->manualtemp, 29); } +// 0x90 - for reading curve temperature from the RC20 thermostat (0x17) +// +void Thermostat::process_RC20Temp(std::shared_ptr telegram) { + std::shared_ptr hc = heating_circuit(telegram); + if (hc == nullptr) { + return; + } + has_update(telegram, hc->nighttemp, 3); // 0:off, 1:manual, 2:auto + has_update(telegram, hc->daylowtemp, 4); + has_update(telegram, hc->daymidtemp, 5); + has_update(telegram, hc->daytemp, 6); +} + +// 0x8F - for reading timer from the RC20 thermostat (0x17) +// data: 04 5D 01 78 24 5D 21 6E 43 5D 41 78 64 5D 61 78 84 5D 81 78 E7 90 E7 90 E7 90 E7 +// data: 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 (offset 27) +// data: E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 (offset 54) +// data: 90 E7 90 01 00 00 01 01 00 01 01 00 01 01 00 01 01 00 00 (offset 81) +void Thermostat::process_RC20Timer(std::shared_ptr telegram) { + std::shared_ptr hc = heating_circuit(telegram); + if (hc == nullptr) { + return; + } + if ((telegram->message_length == 2 && telegram->offset < 83 && !(telegram->offset & 1)) + || (!telegram->offset && telegram->message_length > 1 && !strlen(hc->switchtime1))) { + char data[sizeof(hc->switchtime1)]; + uint8_t no = telegram->offset / 2; + uint8_t day = telegram->message_data[0] >> 5; + uint8_t temp = telegram->message_data[0] & 1; + uint8_t time = telegram->message_data[1]; + + std::string sday = read_flash_string(FL_(enum_dayOfWeek)[day]); + if (day == 7) { + snprintf(data, sizeof(data), "%02d not_set", no); + } else { + snprintf(data, sizeof(data), "%02d %s %02d:%02d T%d", no, sday.c_str(), time / 6, 10 * (time % 6), temp); + } + strlcpy(hc->switchtime1, data, sizeof(hc->switchtime1)); + has_update(hc->switchtime1); // always publish + } +} + // type 0xAE - data from the RC20 thermostat (0x17) - not for RC20's // 17 00 AE 00 80 12 2E 00 D0 00 00 64 (#data=8) // https://github.com/emsesp/EMS-ESP/issues/361 @@ -1022,6 +1071,7 @@ void Thermostat::process_RC300Summer(std::shared_ptr telegram) { } has_update(telegram, hc->roominfluence, 0); + has_update(telegram, hc->roominfl_factor, 1); // is * 10 has_update(telegram, hc->offsettemp, 2); if (!is_fetch(summer2_typeids[hc->hc()])) { has_update(telegram, hc->summertemp, 6); @@ -1911,6 +1961,23 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) { return true; } +// set RC300 roominfluence factor +bool Thermostat::set_roominfl_factor(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) { + return false; + } + float val = 0; + if (!Helpers::value2float(value, val)) { + return false; + } + + write_command(summer_typeids[hc->hc()], 1, (uint8_t)(val * 10)); + + return true; +} + // sets the thermostat working mode, where mode is a string // converts string mode to HeatingCircuit::Mode bool Thermostat::set_mode(const char * value, const int8_t id) { @@ -2302,14 +2369,25 @@ bool Thermostat::set_switchtime(const char * value, const uint16_t type_id, char data[1] = time; } - if (no > 41 || time > 0x90 || (on > 1 && on != 7)) { - LOG_WARNING(F("Setting switchtime: Invalid data: %s"), value); + uint8_t max_on = 3; + if ((model() == EMS_DEVICE_FLAG_RC35) || (model() == EMS_DEVICE_FLAG_RC30_N)) { + max_on = 1; + } + if (no > 41 || time > 0x90 || (on > max_on && on != 7)) { + // LOG_WARNING(F("Setting switchtime: Invalid data: %s"), value); // LOG_WARNING(F("Setting switchtime: Invalid data: %02d.%1d.0x%02X.%1d"), no, day, time, on); return false; } if (data[0] != 0xE7) { std::string sday = read_flash_string(FL_(enum_dayOfWeek)[day]); - snprintf(out, len, "%02d %s %02d:%02d %s", no, sday.c_str(), time / 6, 10 * (time % 6), on ? "on" : "off"); + if ((model() == EMS_DEVICE_FLAG_RC35) || (model() == EMS_DEVICE_FLAG_RC30_N)) { + snprintf(out, len, "%02d %s %02d:%02d %s", no, sday.c_str(), time / 6, 10 * (time % 6), on ? "on" : "off"); + } else if (model() == EMS_DEVICE_FLAG_RC20) { + snprintf(out, len, "%02d %s %02d:%02d T%d", no, sday.c_str(), time / 6, 10 * (time % 6), on); + } else { + std::string son = read_flash_string(FL_(enum_switchmode)[on]); + snprintf(out, len, "%02d %s %02d:%02d %s", no, sday.c_str(), time / 6, 10 * (time % 6), son.c_str()); + } } else { snprintf(out, len, "%02d not_set", no); } @@ -2449,6 +2527,26 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co } } else if (model == EMS_DEVICE_FLAG_RC20) { switch (mode) { + case HeatingCircuit::Mode::NIGHT: + offset = 3; + set_typeid = curve_typeids[hc->hc()]; + validate_typeid = set_typeid; + break; + case HeatingCircuit::Mode::DAYLOW: + offset = 4; + set_typeid = curve_typeids[hc->hc()]; + validate_typeid = set_typeid; + break; + case HeatingCircuit::Mode::DAYMID: + offset = 5; + set_typeid = curve_typeids[hc->hc()]; + validate_typeid = set_typeid; + break; + case HeatingCircuit::Mode::DAY: + offset = 6; + set_typeid = curve_typeids[hc->hc()]; + validate_typeid = set_typeid; + break; case HeatingCircuit::Mode::MANUAL: offset = EMS_OFFSET_RC20Set_temp_manual; break; @@ -2482,7 +2580,7 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co offset = 0x0A; // manual offset break; case HeatingCircuit::Mode::TEMPAUTO: - offset = 0x08; // manual offset + offset = 0x08; // auto offset if (temperature == -1) { factor = 1; // to write 0xFF } @@ -2758,6 +2856,14 @@ bool Thermostat::set_daytemp(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::DAY); } +bool Thermostat::set_daylowtemp(const char * value, const int8_t id) { + return set_temperature_value(value, id, HeatingCircuit::Mode::DAYLOW); +} + +bool Thermostat::set_daymidtemp(const char * value, const int8_t id) { + return set_temperature_value(value, id, HeatingCircuit::Mode::DAYMID); +} + bool Thermostat::set_comforttemp(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::COMFORT); } @@ -3101,7 +3207,9 @@ void Thermostat::register_device_values() { FL_(div10), FL_(ibaCalIntTemperature), DeviceValueUOM::DEGREES_R, - MAKE_CF_CB(set_calinttemp)); + MAKE_CF_CB(set_calinttemp), + -5, + 5); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &ibaMinExtTemperature_, DeviceValueType::INT, @@ -3269,6 +3377,8 @@ void Thermostat::register_device_values_hc(std::shared_ptrminflowtemp, DeviceValueType::UINT, nullptr, FL_(minflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_minflowtemp)); register_device_value(tag, &hc->maxflowtemp, DeviceValueType::UINT, nullptr, FL_(maxflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_maxflowtemp)); register_device_value(tag, &hc->roominfluence, DeviceValueType::UINT, nullptr, FL_(roominfluence), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_roominfluence)); + register_device_value( + tag, &hc->roominfl_factor, DeviceValueType::UINT, FL_(div10), FL_(roominfl_factor), DeviceValueUOM::NONE, MAKE_CF_CB(set_roominfl_factor)); register_device_value(tag, &hc->curroominfl, DeviceValueType::SHORT, FL_(div10), FL_(curroominfl), DeviceValueUOM::DEGREES_R); register_device_value(tag, &hc->nofrosttemp, DeviceValueType::INT, nullptr, FL_(nofrosttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nofrosttemp)); register_device_value(tag, &hc->targetflowtemp, DeviceValueType::UINT, nullptr, FL_(targetflowtemp), DeviceValueUOM::DEGREES); @@ -3292,6 +3402,11 @@ void Thermostat::register_device_values_hc(std::shared_ptrmode, DeviceValueType::ENUM, FL_(enum_mode2), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode)); register_device_value(tag, &hc->manualtemp, DeviceValueType::UINT, FL_(div2), FL_(manualtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_manualtemp)); + register_device_value(tag, &hc->daylowtemp, DeviceValueType::UINT, FL_(div2), FL_(daylowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daylowtemp)); + register_device_value(tag, &hc->daymidtemp, DeviceValueType::UINT, FL_(div2), FL_(daymidtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daymidtemp)); + register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), FL_(dayhightemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daytemp)); + register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), FL_(nighttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nighttemp)); + register_device_value(tag, &hc->switchtime1, DeviceValueType::STRING, FL_(tpl_switchtime), FL_(switchtime), DeviceValueUOM::NONE, MAKE_CF_CB(set_switchtime1)); break; case EMS_DEVICE_FLAG_RC20_N: register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode2), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode)); diff --git a/src/devices/thermostat.h b/src/devices/thermostat.h index 9b38069b9..dc2d20771 100644 --- a/src/devices/thermostat.h +++ b/src/devices/thermostat.h @@ -43,6 +43,8 @@ class Thermostat : public EMSdevice { uint8_t summermode; uint8_t holidaymode; uint8_t daytemp; + uint8_t daylowtemp; + uint8_t daymidtemp; uint8_t nighttemp; uint8_t holidaytemp; uint8_t heatingtype; // type of heating: 1 radiator, 2 convectors, 3 floors, 4 room supply @@ -54,6 +56,7 @@ class Thermostat : public EMSdevice { uint8_t manualtemp; uint8_t summer_setmode; uint8_t roominfluence; + uint8_t roominfl_factor; int16_t curroominfl; uint8_t flowtempoffset; uint8_t minflowtemp; @@ -125,6 +128,8 @@ class Thermostat : public EMSdevice { TEMPAUTO, NOREDUCE, ON, + DAYLOW, + DAYMID, UNKNOWN }; @@ -313,6 +318,8 @@ class Thermostat : public EMSdevice { void process_RC30Set(std::shared_ptr telegram); void process_RC20Monitor(std::shared_ptr telegram); void process_RC20Set(std::shared_ptr telegram); + void process_RC20Temp(std::shared_ptr telegram); + void process_RC20Timer(std::shared_ptr telegram); void process_RC20Remote(std::shared_ptr telegram); void process_RC20Monitor_2(std::shared_ptr telegram); void process_RC20Set_2(std::shared_ptr telegram); @@ -358,6 +365,8 @@ class Thermostat : public EMSdevice { bool set_temp(const char * value, const int8_t id); bool set_nighttemp(const char * value, const int8_t id); bool set_daytemp(const char * value, const int8_t id); + bool set_daylowtemp(const char * value, const int8_t id); + bool set_daymidtemp(const char * value, const int8_t id); bool set_comforttemp(const char * value, const int8_t id); bool set_nofrosttemp(const char * value, const int8_t id); bool set_ecotemp(const char * value, const int8_t id); @@ -371,6 +380,7 @@ class Thermostat : public EMSdevice { bool set_noreducetemp(const char * value, const int8_t id); bool set_remotetemp(const char * value, const int8_t id); bool set_roominfluence(const char * value, const int8_t id); + bool set_roominfl_factor(const char * value, const int8_t id); 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); diff --git a/src/locale_EN.h b/src/locale_EN.h index e1356b759..0afdb0b1b 100644 --- a/src/locale_EN.h +++ b/src/locale_EN.h @@ -545,6 +545,7 @@ MAKE_PSTR_LIST(wwMaxTemp, F("wwmaxtemp"), F("maximum temperature")) MAKE_PSTR_LIST(wwOneTimeKey, F("wwonetimekey"), F("one time key function")) // mqtt values / commands +MAKE_PSTR_LIST(switchtime, F("switchtime"), F("program switchtime")) MAKE_PSTR_LIST(switchtime1, F("switchtime1"), F("own1 program switchtime")) MAKE_PSTR_LIST(switchtime2, F("switchtime2"), F("own2 program switchtime")) MAKE_PSTR_LIST(wwswitchtime, F("wwswitchtime"), F("program switchtime")) @@ -585,6 +586,9 @@ MAKE_PSTR_LIST(mode, F("mode"), F("mode")) MAKE_PSTR_LIST(modetype, F("modetype"), F("mode type")) MAKE_PSTR_LIST(fastheatup, F("fastheatup"), F("fast heatup")) MAKE_PSTR_LIST(daytemp, F("daytemp"), F("day temperature")) +MAKE_PSTR_LIST(daylowtemp, F("daytemp2"), F("day temperature T2")) +MAKE_PSTR_LIST(daymidtemp, F("daytemp3"), F("day temperature T3")) +MAKE_PSTR_LIST(dayhightemp, F("daytemp4"), F("day temperature T4")) MAKE_PSTR_LIST(heattemp, F("heattemp"), F("heat temperature")) MAKE_PSTR_LIST(nighttemp, F("nighttemp"), F("night temperature")) MAKE_PSTR_LIST(ecotemp, F("ecotemp"), F("eco temperature")) @@ -597,6 +601,7 @@ MAKE_PSTR_LIST(offsettemp, F("offsettemp"), F("offset temperature")) MAKE_PSTR_LIST(minflowtemp, F("minflowtemp"), F("min flow temperature")) MAKE_PSTR_LIST(maxflowtemp, F("maxflowtemp"), F("max flow temperature")) MAKE_PSTR_LIST(roominfluence, F("roominfluence"), F("room influence")) +MAKE_PSTR_LIST(roominfl_factor, F("roominflfactor"), F("room influence factor")) MAKE_PSTR_LIST(curroominfl, F("curroominfl"), F("current room influence")) MAKE_PSTR_LIST(nofrosttemp, F("nofrosttemp"), F("nofrost temperature")) MAKE_PSTR_LIST(targetflowtemp, F("targetflowtemp"), F("target flow temperature")) From 7bb6f551538696b413f92daadaed287ece0c6600 Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Wed, 16 Feb 2022 17:02:54 +0100 Subject: [PATCH 02/12] solar fix SM10 energy, remove unknowns --- src/devices/solar.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/devices/solar.cpp b/src/devices/solar.cpp index 0eeeb2a22..68650deaa 100644 --- a/src/devices/solar.cpp +++ b/src/devices/solar.cpp @@ -155,10 +155,6 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c FL_(solarPumpTurnoffDiff), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_TurnoffDiff)); - register_device_value( - DeviceValueTAG::TAG_NONE, &setting3_, DeviceValueType::UINT, nullptr, FL_(setting3), DeviceValueUOM::NONE, MAKE_CF_CB(set_CollectorMaxTemp)); - register_device_value( - DeviceValueTAG::TAG_NONE, &setting4_, DeviceValueType::UINT, nullptr, FL_(setting4), DeviceValueUOM::NONE, MAKE_CF_CB(set_CollectorMinTemp)); register_device_value(DeviceValueTAG::TAG_NONE, &solarPower_, DeviceValueType::SHORT, nullptr, FL_(solarPower), DeviceValueUOM::W); register_device_value(DeviceValueTAG::TAG_NONE, &energyLastHour_, DeviceValueType::ULONG, FL_(div10), FL_(energyLastHour), DeviceValueUOM::WH); register_device_value(DeviceValueTAG::TAG_NONE, &maxFlow_, DeviceValueType::UINT, FL_(div10), FL_(maxFlow), DeviceValueUOM::LMIN, MAKE_CF_CB(set_SM10MaxFlow)); @@ -177,10 +173,16 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c DeviceValueUOM::NONE, MAKE_CF_CB(set_solarEnabled)); + /* unknown values for testing and logging. Used by MichaelDvP + register_device_value( + DeviceValueTAG::TAG_NONE, &setting3_, DeviceValueType::UINT, nullptr, FL_(setting3), DeviceValueUOM::NONE, MAKE_CF_CB(set_CollectorMaxTemp)); + register_device_value( + DeviceValueTAG::TAG_NONE, &setting4_, DeviceValueType::UINT, nullptr, FL_(setting4), DeviceValueUOM::NONE, MAKE_CF_CB(set_CollectorMinTemp)); register_device_value(DeviceValueTAG::TAG_NONE, &data11_, DeviceValueType::UINT, nullptr, FL_(data11), DeviceValueUOM::NONE); register_device_value(DeviceValueTAG::TAG_NONE, &data12_, DeviceValueType::UINT, nullptr, FL_(data12), DeviceValueUOM::NONE); register_device_value(DeviceValueTAG::TAG_NONE, &data1_, DeviceValueType::UINT, nullptr, FL_(data1), DeviceValueUOM::NONE); register_device_value(DeviceValueTAG::TAG_NONE, &data0_, DeviceValueType::UINT, nullptr, FL_(data0), DeviceValueUOM::NONE); + */ } if (flags == EMSdevice::EMS_DEVICE_FLAG_ISM) { register_device_value(DeviceValueTAG::TAG_NONE, &energyLastHour_, DeviceValueType::ULONG, FL_(div10), FL_(energyLastHour), DeviceValueUOM::WH); @@ -400,6 +402,7 @@ void Solar::process_SM10Config(std::shared_ptr telegram) { } // SM10Monitor - type 0x97 +// Solar(0x30) -> All(0x00), SM10Monitor(0x97), data: 00 00 00 22 00 00 D2 01 00 F6 2A 00 00 void Solar::process_SM10Monitor(std::shared_ptr telegram) { uint8_t solarpumpmod = solarPumpMod_; @@ -436,11 +439,11 @@ void Solar::process_SM10Monitor(std::shared_ptr telegram) { energy.pop_front(); } energy.push_back(solarPower_); - uint32_t sum = 0; + int32_t sum = 0; for (auto e : energy) { sum += e; } - energyLastHour_ = sum / 6; // counts in 0.1 Wh + energyLastHour_ = sum > 0 ? sum / 6 : 0; // counts in 0.1 Wh has_update(&solarPower_); has_update(&energyLastHour_); } From 7f5e0f7244c3bc76726aa0fe2b946d35094bc7a4 Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Wed, 16 Feb 2022 17:59:53 +0100 Subject: [PATCH 03/12] Mqtt: remove all HA if not active, timeout QoS, option single2cmd --- .../src/framework/mqtt/MqttSettingsForm.tsx | 56 +++++--- interface/src/types/mqtt.ts | 1 + lib/framework/MqttSettingsService.cpp | 16 ++- lib/framework/MqttSettingsService.h | 1 + src/dallassensor.cpp | 6 +- src/default_settings.h | 8 +- src/emsdevice.cpp | 18 ++- src/mqtt.cpp | 122 ++++++++++++------ src/mqtt.h | 39 +++--- src/web/WebDataService.cpp | 2 +- 10 files changed, 169 insertions(+), 100 deletions(-) diff --git a/interface/src/framework/mqtt/MqttSettingsForm.tsx b/interface/src/framework/mqtt/MqttSettingsForm.tsx index 1d609ff33..e251bbcf9 100644 --- a/interface/src/framework/mqtt/MqttSettingsForm.tsx +++ b/interface/src/framework/mqtt/MqttSettingsForm.tsx @@ -183,29 +183,43 @@ const MqttSettingsForm: FC = () => { control={} label="Publish command output to a 'response' topic" /> - } - label="Publish single value topics on change" - /> - - } - label="Enable MQTT Discovery (for Home Assistant, Domoticz)" - /> - - {data.ha_enabled && ( - - + + } + label="Publish single value topics on change" /> - )} + {data.publish_single && ( + + } + label="publish to command topics (ioBroker)" + /> + + )} + + + + } + label="Enable MQTT Discovery (for Home Assistant, Domoticz)" + /> + + {data.ha_enabled && ( + + + + )} + Publish Intervals (in seconds, 0=automatic) diff --git a/interface/src/types/mqtt.ts b/interface/src/types/mqtt.ts index a372f217f..1e2b50a00 100644 --- a/interface/src/types/mqtt.ts +++ b/interface/src/types/mqtt.ts @@ -40,5 +40,6 @@ export interface MqttSettings { nested_format: number; send_response: boolean; publish_single: boolean; + publish_single2cmd: boolean; discovery_prefix: string; } diff --git a/lib/framework/MqttSettingsService.cpp b/lib/framework/MqttSettingsService.cpp index df9866958..561d7926d 100644 --- a/lib/framework/MqttSettingsService.cpp +++ b/lib/framework/MqttSettingsService.cpp @@ -176,6 +176,7 @@ void MqttSettings::read(MqttSettings & settings, JsonObject & root) { root["nested_format"] = settings.nested_format; root["discovery_prefix"] = settings.discovery_prefix; root["publish_single"] = settings.publish_single; + root["publish_single2cmd"] = settings.publish_single2cmd; root["send_response"] = settings.send_response; } @@ -203,11 +204,12 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting 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.ha_enabled = root["ha_enabled"] | EMSESP_DEFAULT_HA_ENABLED; - newSettings.nested_format = root["nested_format"] | EMSESP_DEFAULT_NESTED_FORMAT; - newSettings.discovery_prefix = root["discovery_prefix"] | EMSESP_DEFAULT_DISCOVERY_PREFIX; - newSettings.publish_single = root["publish_single"] | EMSESP_DEFAULT_PUBLISH_SINGLE; - newSettings.send_response = root["send_response"] | EMSESP_DEFAULT_SEND_RESPONSE; + newSettings.ha_enabled = root["ha_enabled"] | EMSESP_DEFAULT_HA_ENABLED; + newSettings.nested_format = root["nested_format"] | EMSESP_DEFAULT_NESTED_FORMAT; + newSettings.discovery_prefix = root["discovery_prefix"] | EMSESP_DEFAULT_DISCOVERY_PREFIX; + newSettings.publish_single = root["publish_single"] | EMSESP_DEFAULT_PUBLISH_SINGLE; + newSettings.publish_single2cmd = root["publish_single2cmd"] | EMSESP_DEFAULT_PUBLISH_SINGLE2CMD; + newSettings.send_response = root["send_response"] | EMSESP_DEFAULT_SEND_RESPONSE; if (newSettings.enabled != settings.enabled) { changed = true; @@ -230,6 +232,10 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting changed = true; } + if (newSettings.publish_single2cmd != settings.publish_single2cmd) { + changed = true; + } + if (newSettings.send_response != settings.send_response) { changed = true; } diff --git a/lib/framework/MqttSettingsService.h b/lib/framework/MqttSettingsService.h index f51efb6a8..560e29c96 100644 --- a/lib/framework/MqttSettingsService.h +++ b/lib/framework/MqttSettingsService.h @@ -90,6 +90,7 @@ class MqttSettings { uint8_t nested_format; String discovery_prefix; bool publish_single; + bool publish_single2cmd; bool send_response; static void read(MqttSettings & settings, JsonObject & root); diff --git a/src/dallassensor.cpp b/src/dallassensor.cpp index d1eb9215f..fe7846a5b 100644 --- a/src/dallassensor.cpp +++ b/src/dallassensor.cpp @@ -395,7 +395,11 @@ bool DallasSensor::get_value_info(JsonObject & output, const char * cmd, const i void DallasSensor::publish_sensor(const Sensor & sensor) { if (Mqtt::publish_single()) { char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; - snprintf(topic, sizeof(topic), "%s/%s", read_flash_string(F_(dallassensor)).c_str(), sensor.name().c_str()); + if (Mqtt::publish_single2cmd()) { + snprintf(topic, sizeof(topic), "%s/%s", read_flash_string(F_(dallassensor)).c_str(), sensor.name().c_str()); + } else { + snprintf(topic, sizeof(topic), "%s%s/%s", read_flash_string(F_(dallassensor)).c_str(), "_data", sensor.name().c_str()); + } char payload[10]; Mqtt::publish(topic, Helpers::render_value(payload, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0)); } diff --git a/src/default_settings.h b/src/default_settings.h index d399f9ace..b70fef920 100644 --- a/src/default_settings.h +++ b/src/default_settings.h @@ -128,10 +128,6 @@ #define EMSESP_DEFAULT_BOOL_FORMAT 1 // on/off #endif -#ifndef EMSESP_DEFAULT_HA_CLIMATE_FORMAT -#define EMSESP_DEFAULT_HA_CLIMATE_FORMAT 1 // current temp -#endif - #ifndef EMSESP_DEFAULT_MQTT_QOS #define EMSESP_DEFAULT_MQTT_QOS 0 #endif @@ -160,6 +156,10 @@ #define EMSESP_DEFAULT_PUBLISH_SINGLE false #endif +#ifndef EMSESP_DEFAULT_PUBLISH_SINGLE2CMD +#define EMSESP_DEFAULT_PUBLISH_SINGLE2CMD false +#endif + #ifndef EMSESP_DEFAULT_SEND_RESPONSE #define EMSESP_DEFAULT_SEND_RESPONSE false #endif diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index 52ed4be07..0306e4b10 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -533,16 +533,26 @@ void EMSdevice::publish_value(void * value_p) { for (auto & dv : devicevalues_) { if (dv.value_p == value_p && dv.has_state(DeviceValueState::DV_VISIBLE)) { char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; - if ((dv.tag >= DeviceValueTAG::TAG_HC1 && dv.tag <= DeviceValueTAG::TAG_HC8) - || (dv.tag >= DeviceValueTAG::TAG_WWC1 && dv.tag <= DeviceValueTAG::TAG_WWC4)) { + if (Mqtt::publish_single2cmd()) { + if ((dv.tag >= DeviceValueTAG::TAG_HC1 && dv.tag <= DeviceValueTAG::TAG_WWC4)) { + snprintf(topic, + sizeof(topic), + "%s/%s/%s", + device_type_2_device_name(device_type_).c_str(), + tag_to_mqtt(dv.tag).c_str(), + read_flash_string(dv.short_name).c_str()); + } else { + snprintf(topic, sizeof(topic), "%s/%s", device_type_2_device_name(device_type_).c_str(), read_flash_string(dv.short_name).c_str()); + } + } else if (Mqtt::is_nested() && dv.tag >= DeviceValueTAG::TAG_HC1) { snprintf(topic, sizeof(topic), "%s/%s/%s", - device_type_2_device_name(device_type_).c_str(), + Mqtt::tag_to_topic(device_type_, dv.tag).c_str(), tag_to_mqtt(dv.tag).c_str(), read_flash_string(dv.short_name).c_str()); } else { - snprintf(topic, sizeof(topic), "%s/%s", device_type_2_device_name(device_type_).c_str(), read_flash_string(dv.short_name).c_str()); + snprintf(topic, sizeof(topic), "%s/%s", Mqtt::tag_to_topic(device_type_, dv.tag).c_str(), read_flash_string(dv.short_name).c_str()); } int8_t divider = (dv.options_size == 1) ? Helpers::atoint(read_flash_string(dv.options[0]).c_str()) : 0; diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 5158a3643..ddd6d1b69 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -41,6 +41,7 @@ uint8_t Mqtt::nested_format_; std::string Mqtt::discovery_prefix_; bool Mqtt::send_response_; bool Mqtt::publish_single_; +bool Mqtt::publish_single2cmd_; std::deque Mqtt::mqtt_messages_; std::vector Mqtt::mqtt_subfunctions_; @@ -264,6 +265,14 @@ void Mqtt::on_message(const char * topic, const char * payload, size_t len) { LOG_DEBUG(F("Received topic `%s`"), topic); } #endif + // remove HA topics if we don't use discovery + if (strncmp(topic, discovery_prefix().c_str(), discovery_prefix().size()) == 0) { + if (!ha_enabled_ && len) { // don't ping pong the empty message + queue_publish_message(topic, "", true); + LOG_DEBUG(F("Remove topic %s"), topic); + } + return; + } // check first againts any of our subscribed topics for (const auto & mf : mqtt_subfunctions_) { @@ -290,7 +299,7 @@ void Mqtt::on_message(const char * topic, const char * payload, size_t len) { // convert payload into a json doc // if the payload doesn't not contain the key 'value' or 'data', treat the whole payload as the 'value' if (len != 0) { - DeserializationError error = deserializeJson(input_doc, message); + DeserializationError error = deserializeJson(input_doc, (const char *)message); if ((!input_doc.containsKey("value") && !input_doc.containsKey("data")) || error) { input_doc.clear(); input_doc["value"] = (const char *)message; // always a string @@ -387,15 +396,16 @@ void Mqtt::reset_mqtt() { void Mqtt::load_settings() { EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & mqttSettings) { - mqtt_base_ = mqttSettings.base.c_str(); // Convert String to std::string - mqtt_qos_ = mqttSettings.mqtt_qos; - mqtt_retain_ = mqttSettings.mqtt_retain; - mqtt_enabled_ = mqttSettings.enabled; - ha_enabled_ = mqttSettings.ha_enabled; - nested_format_ = mqttSettings.nested_format; - publish_single_ = mqttSettings.publish_single; - send_response_ = mqttSettings.send_response; - discovery_prefix_ = mqttSettings.discovery_prefix.c_str(); + mqtt_base_ = mqttSettings.base.c_str(); // Convert String to std::string + mqtt_qos_ = mqttSettings.mqtt_qos; + mqtt_retain_ = mqttSettings.mqtt_retain; + mqtt_enabled_ = mqttSettings.enabled; + ha_enabled_ = mqttSettings.ha_enabled; + nested_format_ = mqttSettings.nested_format; + publish_single_ = mqttSettings.publish_single; + publish_single2cmd_ = mqttSettings.publish_single2cmd; + send_response_ = mqttSettings.send_response; + discovery_prefix_ = mqttSettings.discovery_prefix.c_str(); // convert to milliseconds publish_time_boiler_ = mqttSettings.publish_time_boiler * 1000; @@ -444,14 +454,6 @@ void Mqtt::start() { if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED) { LOG_INFO(F("MQTT disconnected: Not authorized")); } - // remove message with pending ack - if (!mqtt_messages_.empty()) { - auto mqtt_message = mqtt_messages_.front(); - if (mqtt_message.packet_id_ != 0) { - mqtt_messages_.pop_front(); - } - } - // mqtt_messages_.clear(); }); // create will_topic with the base prefixed. It has to be static because asyncmqttclient destroys the reference @@ -570,9 +572,23 @@ void Mqtt::on_connect() { #endif publish(F_(info), doc.as()); // topic called "info" - // create the EMS-ESP device in HA, which is MQTT retained - if (ha_enabled()) { - ha_status(); + if (ha_enabled_) { + queue_unsubscribe_message(discovery_prefix_ + "/climate/" + mqtt_base_ + "/#"); + queue_unsubscribe_message(discovery_prefix_ + "/sensor/" + mqtt_base_ + "/#"); + queue_unsubscribe_message(discovery_prefix_ + "/binary_sensor/" + mqtt_base_ + "/#"); + queue_unsubscribe_message(discovery_prefix_ + "/number/" + mqtt_base_ + "/#"); + queue_unsubscribe_message(discovery_prefix_ + "/select/" + mqtt_base_ + "/#"); + queue_unsubscribe_message(discovery_prefix_ + "/switch/" + mqtt_base_ + "/#"); + EMSESP::reset_mqtt_ha(); // re-create all HA devices if there are any + ha_status(); // create the EMS-ESP device in HA, which is MQTT retained + } else { + queue_subscribe_message(discovery_prefix_ + "/climate/" + mqtt_base_ + "/#"); + queue_subscribe_message(discovery_prefix_ + "/sensor/" + mqtt_base_ + "/#"); + queue_subscribe_message(discovery_prefix_ + "/binary_sensor/" + mqtt_base_ + "/#"); + queue_subscribe_message(discovery_prefix_ + "/number/" + mqtt_base_ + "/#"); + queue_subscribe_message(discovery_prefix_ + "/select/" + mqtt_base_ + "/#"); + queue_subscribe_message(discovery_prefix_ + "/switch/" + mqtt_base_ + "/#"); + LOG_INFO(F("start removing topics %s/+/%s/#"), discovery_prefix_.c_str(), mqtt_base_.c_str()); } // send initial MQTT messages for some of our services @@ -582,8 +598,6 @@ void Mqtt::on_connect() { // re-subscribe to all custom registered MQTT topics resubscribe(); - EMSESP::reset_mqtt_ha(); // re-create all HA devices if there are any - publish_retain(F("status"), "online", true); // say we're alive to the Last Will topic, with retain on mqtt_publish_fails_ = 0; // reset fail count to 0 @@ -669,6 +683,7 @@ std::shared_ptr Mqtt::queue_message(const uint8_t operation, if (mqtt_messages_.size() >= MAX_MQTT_MESSAGES) { mqtt_messages_.pop_front(); LOG_WARNING(F("Queue overflow, removing one message")); + mqtt_publish_fails_++; } mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message)); @@ -688,6 +703,11 @@ std::shared_ptr Mqtt::queue_subscribe_message(const std::stri return queue_message(Operation::SUBSCRIBE, topic, "", false); // no payload } +// add MQTT unsubscribe message to queue +std::shared_ptr Mqtt::queue_unsubscribe_message(const std::string & topic) { + return queue_message(Operation::UNSUBSCRIBE, topic, "", false); // no payload +} + // MQTT Publish, using a user's retain flag void Mqtt::publish(const std::string & topic, const std::string & payload) { queue_publish_message(topic, payload, mqtt_retain_); @@ -712,11 +732,6 @@ void Mqtt::publish(const std::string & topic, const JsonObject & payload) { publish_retain(topic, payload, mqtt_retain_); } -// no payload -void Mqtt::publish(const std::string & topic) { - queue_publish_message(topic, "", false); -} - // MQTT Publish, using a specific retain flag, topic is a flash string, forcing retain flag void Mqtt::publish_retain(const __FlashStringHelper * topic, const std::string & payload, bool retain) { queue_publish_message(read_flash_string(topic), payload, retain); @@ -750,7 +765,7 @@ void Mqtt::publish_ha(const std::string & topic) { LOG_DEBUG(F("[DEBUG] Publishing empty HA topic=%s"), fulltopic.c_str()); #endif - publish(fulltopic); + queue_publish_message(fulltopic, "", true); // publish with retain to remove from broker } // publish a Home Assistant config topic and payload, with retain flag off. @@ -792,12 +807,29 @@ void Mqtt::process_queue() { snprintf(topic, MQTT_TOPIC_MAX_SIZE, "%s/%s", mqtt_base_.c_str(), message->topic.c_str()); } + // if this has already been published and we're waiting for an ACK, don't publish again + // it will have a real packet ID + if (mqtt_message.packet_id_ > 0) { +#if defined(EMSESP_DEBUG) + LOG_DEBUG(F("[DEBUG] Waiting for QOS-ACK")); +#endif + // if we don't get the ack within 10 minutes, republish with new packet_id + if (uuid::get_uptime_sec() - last_publish_queue_ < 600) { + return; + } + } + last_publish_queue_ = uuid::get_uptime_sec(); + // if we're subscribing... if (message->operation == Operation::SUBSCRIBE) { LOG_DEBUG(F("Subscribing to topic '%s'"), topic); uint16_t packet_id = mqttClient_->subscribe(topic, mqtt_qos_); if (!packet_id) { + if (++mqtt_messages_.front().retry_count_ < MQTT_PUBLISH_MAX_RETRY) { + return; + } LOG_ERROR(F("Error subscribing to topic '%s'"), topic); + mqtt_publish_fails_++; // increment failure counter } mqtt_messages_.pop_front(); // remove the message from the queue @@ -805,12 +837,20 @@ void Mqtt::process_queue() { return; } - // if this has already been published and we're waiting for an ACK, don't publish again - // it will have a real packet ID - if (mqtt_message.packet_id_ > 0) { -#if defined(EMSESP_DEBUG) - LOG_DEBUG(F("[DEBUG] Waiting for QOS-ACK")); -#endif + // if we're unsubscribing... + if (message->operation == Operation::UNSUBSCRIBE) { + LOG_DEBUG(F("Subscribing to topic '%s'"), topic); + uint16_t packet_id = mqttClient_->unsubscribe(topic); + if (!packet_id) { + if (++mqtt_messages_.front().retry_count_ < MQTT_PUBLISH_MAX_RETRY) { + return; + } + LOG_ERROR(F("Error unsubscribing to topic '%s'"), topic); + mqtt_publish_fails_++; // increment failure counter + } + + mqtt_messages_.pop_front(); // remove the message from the queue + return; } @@ -992,8 +1032,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, return; } - bool have_tag = !EMSdevice::tag_to_string(tag).empty(); - bool is_nested = (nested_format_ == 1); // nested_format is 1 if nested, otherwise 2 for single topics + bool have_tag = !EMSdevice::tag_to_string(tag).empty(); // build the payload DynamicJsonDocument doc(EMSESP_JSON_SIZE_HA_CONFIG); @@ -1069,7 +1108,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // value template // if its nested mqtt format then use the appended entity name, otherwise take the original char val_tpl[75]; - if (is_nested) { + if (is_nested()) { snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", new_entity); } else { snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", read_flash_string(entity).c_str()); @@ -1193,10 +1232,11 @@ const std::string Mqtt::tag_to_topic(uint8_t device_type, uint8_t tag) { } // if there is a tag add it - if ((EMSdevice::tag_to_mqtt(tag).empty()) || ((nested_format_ == 1) && (device_type != EMSdevice::DeviceType::BOILER))) { - return EMSdevice::device_type_2_device_name(device_type) + "_data"; - } else { + if (!EMSdevice::tag_to_mqtt(tag).empty() + && ((device_type == EMSdevice::DeviceType::BOILER && tag == DeviceValueTAG::TAG_DEVICE_DATA_WW) || (!is_nested() && tag >= DeviceValueTAG::TAG_HC1))) { return EMSdevice::device_type_2_device_name(device_type) + "_data_" + EMSdevice::tag_to_mqtt(tag); + } else { + return EMSdevice::device_type_2_device_name(device_type) + "_data"; } } diff --git a/src/mqtt.h b/src/mqtt.h index 95b461de4..8fed88ec0 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -29,9 +29,6 @@ using uuid::console::Shell; -// time between HA publishes -#define MQTT_HA_PUBLISH_DELAY 50 - // size of queue #define MAX_MQTT_MESSAGES 300 @@ -70,16 +67,10 @@ class Mqtt { void set_publish_time_sensor(uint16_t publish_time); bool get_publish_onchange(uint8_t device_type); - enum Operation { PUBLISH, SUBSCRIBE }; + enum Operation : uint8_t { PUBLISH, SUBSCRIBE, UNSUBSCRIBE }; + enum NestedFormat : uint8_t { NESTED = 1, SINGLE }; - enum HA_Climate_Format : uint8_t { - CURRENT = 1, // 1 - SETPOINT, // 2 - ZERO // 3 - - }; - - static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 128; // note this should really match the user setting in mqttSettings.maxTopicLength + static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = FACTORY_MQTT_MAX_TOPIC_LENGTH; // fixed, not a user setting anymore static void on_connect(); @@ -92,7 +83,6 @@ class Mqtt { static void publish(const std::string & topic, const JsonObject & payload); static void publish(const __FlashStringHelper * topic, const JsonObject & payload); static void publish(const __FlashStringHelper * topic, const std::string & payload); - static void publish(const std::string & topic); static void publish_retain(const std::string & topic, const JsonObject & payload, bool retain); static void publish_retain(const __FlashStringHelper * topic, const std::string & payload, bool retain); static void publish_retain(const __FlashStringHelper * topic, const JsonObject & payload, bool retain); @@ -125,10 +115,6 @@ class Mqtt { static void ha_status(); - void disconnect() { - mqttClient_->disconnect(); - } - #if defined(EMSESP_DEBUG) void incoming(const char * topic, const char * payload = ""); // for testing only #endif @@ -179,13 +165,10 @@ class Mqtt { static void reset_mqtt(); - // nested_format is 1 if nested, otherwise 2 for single topics - static uint8_t nested_format() { - return nested_format_; - } static bool is_nested() { - return nested_format_ == 1; + return nested_format_ == NestedFormat::NESTED; } + static void nested_format(uint8_t nested_format) { nested_format_ = nested_format; } @@ -193,6 +176,11 @@ class Mqtt { static bool publish_single() { return publish_single_; } + + static bool publish_single2cmd() { + return publish_single2cmd_; + } + static void publish_single(bool publish_single) { publish_single_ = publish_single; } @@ -200,6 +188,7 @@ class Mqtt { static bool ha_enabled() { return ha_enabled_; } + static void ha_enabled(bool ha_enabled) { ha_enabled_ = ha_enabled; } @@ -207,6 +196,7 @@ class Mqtt { static bool send_response() { return send_response_; } + static void send_response(bool send_response) { send_response_ = send_response; } @@ -232,7 +222,7 @@ class Mqtt { uint16_t packet_id_; ~QueuedMqttMessage() = default; - QueuedMqttMessage(uint16_t id, std::shared_ptr && content) + QueuedMqttMessage(uint32_t id, std::shared_ptr && content) : id_(id) , content_(std::move(content)) { retry_count_ = 0; @@ -254,6 +244,7 @@ class Mqtt { static std::shared_ptr queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, bool retain); static std::shared_ptr queue_publish_message(const std::string & topic, const std::string & payload, bool retain); static std::shared_ptr queue_subscribe_message(const std::string & topic); + static std::shared_ptr queue_unsubscribe_message(const std::string & topic); void on_publish(uint16_t packetId); void on_message(const char * topic, const char * payload, size_t len); @@ -281,6 +272,7 @@ class Mqtt { uint32_t last_publish_mixer_ = 0; uint32_t last_publish_other_ = 0; uint32_t last_publish_sensor_ = 0; + uint32_t last_publish_queue_ = 0; static bool connecting_; static bool initialized_; @@ -303,6 +295,7 @@ class Mqtt { static uint8_t nested_format_; static std::string discovery_prefix_; static bool publish_single_; + static bool publish_single2cmd_; static bool send_response_; }; diff --git a/src/web/WebDataService.cpp b/src/web/WebDataService.cpp index c2efaffef..a012c2d35 100644 --- a/src/web/WebDataService.cpp +++ b/src/web/WebDataService.cpp @@ -144,7 +144,7 @@ void WebDataService::sensor_data(AsyncWebServerRequest * request) { obj["t"] = sensor.type(); if (sensor.type() != AnalogSensor::AnalogType::NOTUSED) { - obj["v"] = Helpers::round2(sensor.value(), 1); // is optional and is a float + obj["v"] = Helpers::round2(sensor.value(), 0); // is optional and is a float } } } From 073493cba23001dcaf53359d6a60f78a4f8ec537 Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Wed, 16 Feb 2022 18:35:25 +0100 Subject: [PATCH 04/12] analogsensors, outputs PWM, DAC, digital --- interface/src/project/DashboardData.tsx | 119 ++++++++++-- interface/src/project/types.ts | 34 +++- src/analogsensor.cpp | 233 +++++++++++++++++++----- src/analogsensor.h | 37 ++-- src/emsdevicevalue.h | 4 +- src/helpers.cpp | 4 + src/locale_DE.h | 12 +- src/locale_EN.h | 6 +- src/system.cpp | 103 +++++++---- src/web/WebCustomizationService.h | 2 +- src/web/WebDataService.cpp | 2 +- 11 files changed, 433 insertions(+), 123 deletions(-) diff --git a/interface/src/project/DashboardData.tsx b/interface/src/project/DashboardData.tsx index 36614e0f0..19047073c 100644 --- a/interface/src/project/DashboardData.tsx +++ b/interface/src/project/DashboardData.tsx @@ -59,7 +59,8 @@ import { DeviceValue, DeviceValueUOM, DeviceValueUOM_s, - AnalogTypes, + AnalogType, + AnalogTypeNames, Sensor, Analog } from './types'; @@ -715,7 +716,7 @@ const DashboardData: FC = () => { ))} - {analog.t === 3 && ( + {analog.t >= AnalogType.COUNTER && analog.t <= AnalogType.RATE && ( <> @@ -726,22 +727,37 @@ const DashboardData: FC = () => { ))} - - - mV - }} - /> - + {analog.t === AnalogType.ADC && ( + + mV + }} + /> + + )} + {analog.t === AnalogType.COUNTER && ( + + + + )} { )} + {analog.t === AnalogType.DIGITAL_OUT && (analog.i === 25 || analog.i === 26) && ( + <> + + + + + )} + {analog.t === AnalogType.DIGITAL_OUT && analog.i !== 25 && analog.i !== 26 && ( + <> + + + + + )} + {analog.t >= AnalogType.PWM_0 && ( + <> + + Hz + }} + /> + + + % + }} + + /> + + + )} Warning: be careful when assigning a GPIO! diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index 1c0871691..b5f3a9497 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -176,7 +176,9 @@ export enum DeviceValueUOM { DBM, FAHRENHEIT, MV, - SQM + SQM, + M3, + L } export const DeviceValueUOM_s = [ @@ -199,10 +201,36 @@ export const DeviceValueUOM_s = [ '°F', 'mV', 'sqm', - "o'clock" + "m3", + "l" +]; + +export enum AnalogType { + NOTUSED = 0, + DIGITAL_IN, + COUNTER, + ADC, + TIMER, + RATE, + DIGITAL_OUT, + PWM_0, + PWM_1, + PWM_2 +} + +export const AnalogTypeNames = [ + '(disabled)', + 'Digital in', + 'Counter', + 'ADC', + 'Timer', + 'Rate', + 'Digital out', + 'PWM 0', + 'PWM 1', + 'PWM 2' ]; -export const AnalogTypes = ['(disabled)', 'Digital in', 'Counter', 'ADC']; type BoardProfiles = { [name: string]: string; diff --git a/src/analogsensor.cpp b/src/analogsensor.cpp index 734b9e441..5ea7dffac 100644 --- a/src/analogsensor.cpp +++ b/src/analogsensor.cpp @@ -27,7 +27,7 @@ void AnalogSensor::start() { reload(); // fetch the list of sensors from our customization service if (analog_enabled_) { - analogSetAttenuation(ADC_2_5db); // for all channels + analogSetAttenuation(ADC_2_5db); // for all channels 1.5V } LOG_INFO(F("Starting Analog sensor service")); @@ -40,10 +40,15 @@ void AnalogSensor::start() { F_(info_cmd)); Command::add( EMSdevice::DeviceType::ANALOGSENSOR, - F_(counter), - [&](const char * value, const int8_t id) { return command_counter(value, id); }, - F("set counter value"), + F_(setvalue), + [&](const char * value, const int8_t id) { return command_setvalue(value, id); }, + F("set io value"), CommandFlag::ADMIN_ONLY); + Command::add( + EMSdevice::DeviceType::ANALOGSENSOR, + F_(commands), + [&](const char * value, const int8_t id, JsonObject & output) { return command_commands(value, id, output); }, + F_(commands_cmd)); } // load settings from the customization file, sorts them and initializes the GPIOs @@ -58,11 +63,47 @@ void AnalogSensor::reload() { // and store them locally and then activate them EMSESP::webCustomizationService.read([&](WebCustomization & settings) { auto sensors = settings.analogCustomizations; - sensors_.clear(); // start with an empty list - if (sensors.size() != 0) { - for (auto & sensor : sensors) { + auto it = sensors_.begin(); + for (auto & sensor_ : sensors_) { + // update existing sensors + bool found = false; + for (auto & sensor : sensors) { //search customlist + if (sensor_.id() == sensor.id) { + // for output sensors set value to new start-value + if ((sensor.type == AnalogType::COUNTER || sensor.type >= AnalogType::DIGITAL_OUT) + && (sensor_.type() != sensor.type || sensor_.offset() != sensor.offset || sensor_.factor() != sensor.factor)) { + sensor_.set_value(sensor.offset); + } + sensor_.set_name(sensor.name); + sensor_.set_type(sensor.type); + sensor_.set_offset(sensor.offset); + sensor_.set_factor(sensor.factor); + sensor_.set_uom(sensor.uom); + sensor_.ha_registered = false; + found = true; + } + } + if (!found) { + sensors_.erase(it); + } + it++; + } + // add new sensors from list + for (auto & sensor : sensors) { + bool found = false; + for (auto & sensor_ : sensors_) { + if (sensor_.id() == sensor.id) { + found = true; + } + } + if (!found) { sensors_.emplace_back(sensor.id, sensor.name, sensor.offset, sensor.factor, sensor.uom, sensor.type); sensors_.back().ha_registered = false; // this will trigger recrate of the HA config + if (sensor.type == AnalogType::COUNTER || sensor.type >= AnalogType::DIGITAL_OUT) { + sensors_.back().set_value(sensor.offset); + } else { + sensors_.back().set_value(0); // reset value only for new sensors + } } } return true; @@ -82,11 +123,21 @@ void AnalogSensor::reload() { } else if (sensor.type() == AnalogType::COUNTER) { LOG_DEBUG(F("Adding analog I/O Counter sensor on GPIO%d"), sensor.id()); pinMode(sensor.id(), INPUT_PULLUP); - sensor.set_value(0); // reset count - sensor.set_uom(0); // no uom, just for safe measures + if (sensor.id() == 25 || sensor.id() == 26) { + dacWrite(sensor.id(), 255); + } sensor.polltime_ = 0; sensor.poll_ = digitalRead(sensor.id()); publish_sensor(sensor); + } else if (sensor.type() == AnalogType::TIMER || sensor.type() == AnalogType::RATE) { + LOG_DEBUG(F("Adding analog Timer/Rate sensor on GPIO%d"), sensor.id()); + pinMode(sensor.id(), INPUT_PULLUP); + sensor.polltime_ = uuid::get_uptime(); + sensor.last_polltime_ = uuid::get_uptime(); + sensor.poll_ = digitalRead(sensor.id()); + sensor.set_offset(0); + sensor.set_value(0); + publish_sensor(sensor); } else if (sensor.type() == AnalogType::DIGITAL_IN) { LOG_DEBUG(F("Adding analog Read sensor on GPIO%d"), sensor.id()); pinMode(sensor.id(), INPUT_PULLUP); @@ -95,18 +146,49 @@ void AnalogSensor::reload() { sensor.polltime_ = 0; sensor.poll_ = digitalRead(sensor.id()); publish_sensor(sensor); + } else if (sensor.type() == AnalogType::DIGITAL_OUT) { + LOG_DEBUG(F("Adding analog Write sensor on GPIO%d"), sensor.id()); + pinMode(sensor.id(), OUTPUT); + if (sensor.id() == 25 || sensor.id() == 26) { + if (sensor.offset() > 255) { + sensor.set_offset(255); + } else if (sensor.offset() < 0) { + sensor.set_offset(0); + } + dacWrite(sensor.id(), sensor.offset()); + sensor.set_value(sensor.offset()); + } else { + digitalWrite(sensor.id(), sensor.offset() > 0 ? 1 : 0); + sensor.set_value(digitalRead(sensor.id())); + } + sensor.set_uom(0); // no uom, just for safe measures + publish_sensor(sensor); + } else if (sensor.type() >= AnalogType::PWM_0) { + LOG_DEBUG(F("Adding PWM output sensor on GPIO%d"), sensor.id()); + uint channel = sensor.type() - AnalogType::PWM_0; + ledcSetup(channel, sensor.factor(), 13); + ledcAttachPin(sensor.id(), channel); + if (sensor.offset() > 100) { + sensor.set_offset(100); + } else if (sensor.offset() < 0) { + sensor.set_offset(0); + } + ledcWrite(channel, (uint32_t)(sensor.offset() * 8191 / 100)); + sensor.set_value(sensor.offset()); + sensor.set_uom(DeviceValueUOM::PERCENT); + publish_sensor(sensor); } } } -// measure and moving average adc +// measure input sensors and moving average adc void AnalogSensor::measure() { static uint32_t measure_last_ = 0; - // measure interval 500ms for analog sensors + // measure interval 500ms for adc sensors if (!measure_last_ || (uuid::get_uptime() - measure_last_) >= MEASURE_ANALOG_INTERVAL) { measure_last_ = uuid::get_uptime(); - // go through the list of ADC sensors + // go through the list of adc sensors for (auto & sensor : sensors_) { if (sensor.type() == AnalogType::ADC) { uint16_t a = analogReadMilliVolts(sensor.id()); // e.g. ADC1_CHANNEL_0_GPIO_NUM @@ -128,34 +210,38 @@ void AnalogSensor::measure() { } } } - // poll digital io every time + // poll digital io every time with debounce // go through the list of digital sensors for (auto & sensor : sensors_) { - if (sensor.type() == AnalogType::DIGITAL_IN || sensor.type() == AnalogType::COUNTER) { + 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.id()); - if (sensor.poll_ != current_reading) { // check for pinchange - sensor.polltime_ = uuid::get_uptime(); + if (sensor.poll_ != current_reading) { // check for pinchange + sensor.polltime_ = uuid::get_uptime(); // remember time of pinchange sensor.poll_ = current_reading; } - if (uuid::get_uptime() - sensor.polltime_ >= 15) { // debounce + // debounce and check for real pinchange + if (uuid::get_uptime() - sensor.polltime_ >= 15 && sensor.poll_ != sensor.last_reading_) { + sensor.last_reading_ = sensor.poll_; if (sensor.type() == AnalogType::DIGITAL_IN) { sensor.set_value(sensor.poll_); - } else if (sensor.type() == AnalogType::COUNTER) { - // capture reading and compare with the last one to see if there is high/low change - if (sensor.poll_ != sensor.last_reading_) { - sensor.last_reading_ = sensor.poll_; - if (!sensor.poll_) { - sensor.set_value(old_value + 1); - } + } else if (!sensor.poll_) { // falling edge + if (sensor.type() == AnalogType::COUNTER) { + sensor.set_value(old_value + sensor.factor()); + } else if (sensor.type() == AnalogType::RATE) { // dafault uom: Hz (1/sec) with factor 1 + sensor.set_value(sensor.factor() * 1000 / (sensor.polltime_ - sensor.last_polltime_)); + } else if (sensor.type() == AnalogType::TIMER) { // default seconds with factor 1 + sensor.set_value(sensor.factor() * (sensor.polltime_ - sensor.last_polltime_) / 1000); } + 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); - } + } + // see if there is a change and increment # reads + if (old_value != sensor.value()) { + sensorreads_++; + changed_ = true; + publish_sensor(sensor); } } } @@ -170,7 +256,7 @@ void AnalogSensor::loop() { } // update analog information name and offset -bool AnalogSensor::update(uint8_t id, const std::string & name, uint16_t offset, float factor, uint8_t uom, int8_t type) { +bool AnalogSensor::update(uint8_t id, const std::string & name, float offset, float factor, uint8_t uom, int8_t type) { boolean found_sensor = false; // see if we can find the sensor in our customization list EMSESP::webCustomizationService.update( @@ -240,7 +326,11 @@ bool AnalogSensor::updated_values() { void AnalogSensor::publish_sensor(const Sensor & sensor) { if (Mqtt::publish_single()) { char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; - snprintf(topic, sizeof(topic), "%s/%s", read_flash_string(F_(analogsensor)).c_str(), sensor.name().c_str()); + if (Mqtt::publish_single2cmd()) { + snprintf(topic, sizeof(topic), "%s/%s", read_flash_string(F_(analogsensor)).c_str(), sensor.name().c_str()); + } else { + snprintf(topic, sizeof(topic), "%s%s/%s", read_flash_string(F_(analogsensor)).c_str(), "_data", sensor.name().c_str()); + } char payload[10]; Mqtt::publish(topic, Helpers::render_value(payload, sensor.value(), 2)); // always publish as floats } @@ -285,12 +375,16 @@ void AnalogSensor::publish_values(const bool force) { dataSensor["name"] = sensor.name(); switch (sensor.type()) { case AnalogType::COUNTER: - dataSensor["value"] = (uint16_t)sensor.value(); // convert to integer - break; + case AnalogType::TIMER: + case AnalogType::RATE: case AnalogType::ADC: + case AnalogType::PWM_0: + case AnalogType::PWM_1: + case AnalogType::PWM_2: dataSensor["value"] = (float)sensor.value(); // float break; case AnalogType::DIGITAL_IN: + case AnalogType::DIGITAL_OUT: default: dataSensor["value"] = (uint8_t)sensor.value(); // convert to char for 1 or 0 break; @@ -373,6 +467,15 @@ bool AnalogSensor::command_info(const char * value, const int8_t id, JsonObject dataSensor["uom"] = EMSdevice::uom_to_string(sensor.uom()); dataSensor["offset"] = sensor.offset(); dataSensor["factor"] = sensor.factor(); + } else if (sensor.type() == AnalogType::COUNTER) { + dataSensor["uom"] = EMSdevice::uom_to_string(sensor.uom()); + dataSensor["start_value"] = sensor.offset(); + dataSensor["factor"] = sensor.factor(); + } else if (sensor.type() == AnalogType::TIMER || sensor.type() == AnalogType::RATE) { + dataSensor["factor"] = sensor.factor(); + } else if (sensor.type() >= AnalogType::PWM_0) { + dataSensor["uom"] = EMSdevice::uom_to_string(sensor.uom()); + dataSensor["frequency"] = sensor.factor(); } dataSensor["value"] = sensor.value(); } else { @@ -384,7 +487,7 @@ bool AnalogSensor::command_info(const char * value, const int8_t id, JsonObject } // this creates the sensor, initializing everything -AnalogSensor::Sensor::Sensor(const uint8_t id, const std::string & name, const uint16_t offset, const float factor, const uint8_t uom, const int8_t type) +AnalogSensor::Sensor::Sensor(const uint8_t id, const std::string & name, const float offset, const float factor, const uint8_t uom, const int8_t type) : id_(id) , name_(name) , offset_(offset) @@ -405,28 +508,70 @@ std::string AnalogSensor::Sensor::name() const { } // set the counter value, id is gpio-no -bool AnalogSensor::command_counter(const char * value, const int8_t id) { - int val; - if (!Helpers::value2number(value, val)) { +bool AnalogSensor::command_setvalue(const char * value, const int8_t id) { + float val; + if (!Helpers::value2float(value, val)) { return false; } for (auto & sensor : sensors_) { - if (sensor.type() == AnalogType::COUNTER && sensor.id() == id) { - if (val < 0) { // negative values corrects - sensor.set_value(sensor.value() + val); - } else { // positive values are set + if (sensor.id() == id) { + if (sensor.type() == AnalogType::COUNTER) { + if (val < 0 || value[0] == '+') { // sign corrects values + sensor.set_offset(sensor.value() + val); + sensor.set_value(sensor.value() + val); + } else { // positive values are set + sensor.set_offset(val); + sensor.set_value(val); + } + publish_sensor(sensor); + return true; + } else if (sensor.type() == AnalogType::ADC) { + sensor.set_offset(val); + return true; + } else if (sensor.type() == AnalogType::DIGITAL_OUT) { + uint8_t v = val; + if ((sensor.id() == 25 || sensor.id() == 26) && v <= 255) { + sensor.set_offset(v); + sensor.set_value(v); + pinMode(sensor.id(), OUTPUT); + dacWrite(sensor.id(), sensor.offset()); + publish_sensor(sensor); + return true; + } else if (v == 0 || v == 1) { + sensor.set_offset(v); + sensor.set_value(v); + pinMode(sensor.id(), OUTPUT); + digitalWrite(sensor.id(), sensor.offset() > 0 ? 1 : 0); + publish_sensor(sensor); + return true; + } + } else if (sensor.type() >= AnalogType::PWM_0) { + uint8_t channel = sensor.type() - AnalogType::PWM_0; + if (val > 100) { + val = 100; + } else if (val < 0) { + val = 0; + } + sensor.set_offset(val); sensor.set_value(val); + ledcWrite(channel, (uint32_t)(val * 8191 / 100)); + publish_sensor(sensor); + return true; } - return true; } } return false; } +// list commands +bool AnalogSensor::command_commands(const char * value, const int8_t id, JsonObject & output) { + return Command::list(EMSdevice::DeviceType::ANALOGSENSOR, output); +} + // hard coded tests #ifdef EMSESP_DEBUG void AnalogSensor::test() { - // Sensor(const uint8_t id, const std::string & name, const uint16_t offset, const float factor, const uint8_t uom, const int8_t type); + // Sensor(const uint8_t id, const std::string & name, const float offset, const float factor, const uint8_t uom, const int8_t type); sensors_.emplace_back(36, "test12", 0, 0.1, 17, AnalogType::ADC); sensors_.back().set_value(12.4); diff --git a/src/analogsensor.h b/src/analogsensor.h index 70e94fd95..e48acd7e9 100644 --- a/src/analogsensor.h +++ b/src/analogsensor.h @@ -36,10 +36,10 @@ class AnalogSensor { public: class Sensor { public: - Sensor(const uint8_t id, const std::string & name, const uint16_t offset, const float factor, const uint8_t uom, const int8_t type); + Sensor(const uint8_t id, const std::string & name, const float offset, const float factor, const uint8_t uom, const int8_t type); ~Sensor() = default; - void set_offset(const uint16_t offset) { + void set_offset(const float offset) { offset_ = offset; } @@ -68,7 +68,7 @@ class AnalogSensor { factor_ = factor; } - uint16_t offset() const { + float offset() const { return offset_; } @@ -90,16 +90,18 @@ class AnalogSensor { bool ha_registered = false; - uint16_t analog_ = 0; // ADC - average value - uint32_t sum_ = 0; // ADC - rolling sum - uint16_t last_reading_ = 0; // IO COUNTER & ADC - last reading - uint32_t polltime_ = 0; // digital IO & COUNTER debounce time - int poll_ = 0; + uint16_t analog_ = 0; // ADC - average value + uint32_t sum_ = 0; // ADC - rolling sum + uint16_t last_reading_ = 0; // IO COUNTER & ADC - last reading + uint16_t count_ = 0; // counter raw counts + uint32_t polltime_ = 0; // digital IO & COUNTER debounce time + int poll_ = 0; + uint32_t last_polltime_ = 0; // for timer private: uint8_t id_; std::string name_; - uint16_t offset_; + float offset_; float factor_; uint8_t uom_; float value_; // float because of the factor is a float @@ -112,9 +114,15 @@ class AnalogSensor { enum AnalogType : int8_t { MARK_DELETED = -1, // mark for deletion NOTUSED, // 0 - disabled - DIGITAL_IN, // 1 - COUNTER, // 2 - ADC // 3 + DIGITAL_IN, + COUNTER, + ADC, + TIMER, + RATE, + DIGITAL_OUT, + PWM_0, + PWM_1, + PWM_2 }; void start(); @@ -149,7 +157,7 @@ class AnalogSensor { return sensors_.size(); } - bool update(uint8_t id, const std::string & name, uint16_t offset, float factor, uint8_t uom, int8_t type); + bool update(uint8_t id, const std::string & name, float offset, float factor, uint8_t uom, int8_t type); bool get_value_info(JsonObject & output, const char * cmd, const int8_t id); #ifdef EMSESP_DEBUG @@ -163,9 +171,10 @@ class AnalogSensor { static uuid::log::Logger logger_; void remove_ha_topic(const uint8_t id); - bool command_counter(const char * value, const int8_t id); + bool command_setvalue(const char * value, const int8_t id); void measure(); bool command_info(const char * value, const int8_t id, JsonObject & output); + bool command_commands(const char * value, const int8_t id, JsonObject & output); std::vector sensors_; // our list of sensors diff --git a/src/emsdevicevalue.h b/src/emsdevicevalue.h index a9b4b029c..c6bf629b6 100644 --- a/src/emsdevicevalue.h +++ b/src/emsdevicevalue.h @@ -66,7 +66,9 @@ class DeviceValue { DBM, // 15 FAHRENHEIT, // 16 MV, // 17 - SQM // 18 + SQM, // 18 squaremeter + M3, // 19 cubic meter + L // 20 }; // TAG mapping - maps to DeviceValueTAG_s in emsdevice.cpp diff --git a/src/helpers.cpp b/src/helpers.cpp index 343b5a01a..0f9b339f3 100644 --- a/src/helpers.cpp +++ b/src/helpers.cpp @@ -526,6 +526,10 @@ bool Helpers::value2float(const char * v, float & value) { value = atof((char *)v); return true; } + if (v[0] == '+' && (v[1] == '.' || (v[1] >= '0' && v[1] <= '9'))) { + value = atof((char *)(v + 1)); + return true; + } return false; } diff --git a/src/locale_DE.h b/src/locale_DE.h index 82345d198..c57701bfd 100644 --- a/src/locale_DE.h +++ b/src/locale_DE.h @@ -70,7 +70,7 @@ MAKE_PSTR_WORD(pin) MAKE_PSTR_WORD(publish) MAKE_PSTR_WORD(timeout) MAKE_PSTR_WORD(board_profile) -MAKE_PSTR_WORD(counter) +MAKE_PSTR_WORD(setvalue) // for commands MAKE_PSTR_WORD(call) @@ -121,7 +121,7 @@ MAKE_STR(productid_fmt, "%s EMS ProductID") MAKE_PSTR_LIST(enum_syslog_level, F_(off), F("emerg"), F("alert"), F("crit"), F_(error), F("warn"), F("notice"), F_(info), F_(debug), F("trace"), F("all")) MAKE_PSTR_LIST(enum_watch, F_(off), F_(on), F_(raw), F_(unknown)) -MAKE_PSTR_LIST(enum_sensortype, F("none"), F("digital in"), F("counter"), F("adc")) +MAKE_PSTR_LIST(enum_sensortype, F("none"), F("digital in"), F("counter"), F("adc"), F("timer"), F("rate"), F("digital out"), F("pwm 0"), F("pwm 1"), F("pwm 2")) // strings MAKE_PSTR(EMSESP, "EMS-ESP") @@ -196,6 +196,8 @@ MAKE_PSTR(dbm, "dBm") MAKE_PSTR(fahrenheit, "°F") MAKE_PSTR(mv, "mV") MAKE_PSTR(sqm, "sqm") +MAKE_PSTR(m3, "m3") +MAKE_PSTR(l, "l") // MAKE_PSTR(times, "mal") // MAKE_PSTR(oclock, "Uhr") @@ -585,6 +587,9 @@ MAKE_PSTR_LIST(mode, F("mode"), F("modus")) MAKE_PSTR_LIST(modetype, F("modetype"), F("modus Typ")) MAKE_PSTR_LIST(fastheatup, F("fastheatup"), F("fast heatup")) MAKE_PSTR_LIST(daytemp, F("daytemp"), F("Tagestemperatur")) +MAKE_PSTR_LIST(daylowtemp, F("daytemp2"), F("Tagestemperatur T2")) +MAKE_PSTR_LIST(daymidtemp, F("daytemp3"), F("Tagestemperatur T3")) +MAKE_PSTR_LIST(dayhightemp, F("daytemp4"), F("Tagestemperatur T4")) MAKE_PSTR_LIST(heattemp, F("heattemp"), F("Heizen Temperatur")) MAKE_PSTR_LIST(nighttemp, F("nighttemp"), F("Nachttemperatur")) MAKE_PSTR_LIST(ecotemp, F("ecotemp"), F("eco Temperatur")) @@ -597,7 +602,8 @@ MAKE_PSTR_LIST(offsettemp, F("offsettemp"), F("Temperaturanhebung")) MAKE_PSTR_LIST(minflowtemp, F("minflowtemp"), F("min Flusstemperatur")) MAKE_PSTR_LIST(maxflowtemp, F("maxflowtemp"), F("max Flusstemperatur")) MAKE_PSTR_LIST(roominfluence, F("roominfluence"), F("Raumeinfluss")) -MAKE_PSTR_LIST(curroominfl, F("curroominfl"), F("current room influence")) +MAKE_PSTR_LIST(roominfl_factor, F("roominflfactor"), F("Raumeinfluss Factor")) +MAKE_PSTR_LIST(curroominfl, F("curroominfl"), F("aktueller Raumeinfluss")) MAKE_PSTR_LIST(nofrosttemp, F("nofrosttemp"), F("Frostschutztemperatur")) MAKE_PSTR_LIST(targetflowtemp, F("targetflowtemp"), F("berechnete Flusstemperatur")) MAKE_PSTR_LIST(heatingtype, F("heatingtype"), F("Heizungstyp")) diff --git a/src/locale_EN.h b/src/locale_EN.h index 0afdb0b1b..e6908a12c 100644 --- a/src/locale_EN.h +++ b/src/locale_EN.h @@ -70,7 +70,7 @@ MAKE_PSTR_WORD(pin) MAKE_PSTR_WORD(publish) MAKE_PSTR_WORD(timeout) MAKE_PSTR_WORD(board_profile) -MAKE_PSTR_WORD(counter) +MAKE_PSTR_WORD(setvalue) // for commands MAKE_PSTR_WORD(call) @@ -121,7 +121,7 @@ MAKE_STR(productid_fmt, "%s EMS ProductID") MAKE_PSTR_LIST(enum_syslog_level, F_(off), F("emerg"), F("alert"), F("crit"), F_(error), F("warn"), F("notice"), F_(info), F_(debug), F("trace"), F("all")) MAKE_PSTR_LIST(enum_watch, F_(off), F_(on), F_(raw), F_(unknown)) -MAKE_PSTR_LIST(enum_sensortype, F("none"), F("digital in"), F("counter"), F("adc")) +MAKE_PSTR_LIST(enum_sensortype, F("none"), F("digital in"), F("counter"), F("adc"), F("timer"), F("rate"), F("digital out"), F("pwm 0"), F("pwm 1"), F("pwm 2")) // strings MAKE_PSTR(EMSESP, "EMS-ESP") @@ -196,6 +196,8 @@ MAKE_PSTR(dbm, "dBm") MAKE_PSTR(fahrenheit, "°F") MAKE_PSTR(mv, "mV") MAKE_PSTR(sqm, "sqm") +MAKE_PSTR(m3, "m3") +MAKE_PSTR(l, "l") // MAKE_PSTR(times, "times") // MAKE_PSTR(oclock, "o'clock") diff --git a/src/system.cpp b/src/system.cpp index 6b56724a9..d05dffb29 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -73,18 +73,18 @@ bool System::command_pin(const char * value, const int8_t id) { } else if (Helpers::value2bool(value, v)) { pinMode(id, OUTPUT); digitalWrite(id, v); - LOG_INFO(F("GPIO %d set to %s"), id, v ? "HIGH" : "LOW"); + // LOG_INFO(F("GPIO %d set to %s"), id, v ? "HIGH" : "LOW"); return true; } else if (Helpers::value2string(value, v1)) { if (v1 == "input" || v1 == "in" || v1 == "-1") { pinMode(id, INPUT); v = digitalRead(id); - LOG_INFO(F("GPIO %d set input, state %s"), id, v ? "HIGH" : "LOW"); + // LOG_INFO(F("GPIO %d set input, state %s"), id, v ? "HIGH" : "LOW"); return true; } } - LOG_INFO(F("GPIO %d: invalid value"), id); + // LOG_INFO(F("GPIO %d: invalid value"), id); #endif return false; @@ -162,13 +162,19 @@ bool System::command_publish(const char * value, const int8_t id) { bool System::command_syslog_level(const char * value, const int8_t id) { uint8_t s = 0xff; if (Helpers::value2enum(value, s, FL_(enum_syslog_level))) { + bool changed = false; EMSESP::webSettingsService.update( [&](WebSettings & settings) { - settings.syslog_level = (int8_t)s - 1; + if (settings.syslog_level != (int8_t)s - 1) { + settings.syslog_level = (int8_t)s - 1; + changed = true; + } return StateUpdateResult::CHANGED; }, "local"); - EMSESP::system_.syslog_init(); + if (changed) { + EMSESP::system_.syslog_init(); + } return true; } return false; @@ -176,29 +182,35 @@ bool System::command_syslog_level(const char * value, const int8_t id) { // watch bool System::command_watch(const char * value, const int8_t id) { - uint8_t w = 0xff; + uint8_t w = 0xff; + uint16_t i = Helpers::hextoint(value); if (Helpers::value2enum(value, w, FL_(enum_watch))) { if (w == 0 || EMSESP::watch() == EMSESP::Watch::WATCH_OFF) { EMSESP::watch_id(0); } - EMSESP::watch(w); - if (Mqtt::publish_single()) { - Mqtt::publish(F("system/watch"), read_flash_string(FL_(enum_watch)[w]).c_str()); + if (Mqtt::publish_single() && w != EMSESP::watch()) { + if (Mqtt::publish_single2cmd()) { + Mqtt::publish(F("system/watch"), + EMSESP::system_.enum_format() == ENUM_FORMAT_INDEX ? Helpers::itoa(w) : read_flash_string(FL_(enum_watch)[w]).c_str()); + } else { + Mqtt::publish(F("system_data/watch"), + EMSESP::system_.enum_format() == ENUM_FORMAT_INDEX ? Helpers::itoa(w) : read_flash_string(FL_(enum_watch)[w]).c_str()); + } } + EMSESP::watch(w); return true; - } - uint16_t i = Helpers::hextoint(value); - if (i) { + } else if (i) { + if (Mqtt::publish_single() && i != EMSESP::watch_id()) { + if (Mqtt::publish_single2cmd()) { + Mqtt::publish(F("system/watch"), Helpers::hextoa(i)); + } else { + Mqtt::publish(F("system_data/watch"), Helpers::hextoa(i)); + } + } EMSESP::watch_id(i); if (EMSESP::watch() == EMSESP::Watch::WATCH_OFF) { EMSESP::watch(EMSESP::Watch::WATCH_ON); } - if (Mqtt::publish_single()) { - char s[10]; - snprintf(s, sizeof(s), "0x%04X", i); - Mqtt::publish(F("system/watch"), s); - // Mqtt::publish(F("system/watch"), read_flash_string(FL_(enum_watch)[EMSESP::watch()]).c_str()); - } return true; } return false; @@ -273,13 +285,25 @@ void System::syslog_init() { } if (Mqtt::publish_single()) { - Mqtt::publish(F("system/syslog"), syslog_enabled_ ? read_flash_string(FL_(enum_syslog_level)[syslog_level_ + 1]).c_str() : "off"); - if (EMSESP::watch_id() == 0 || EMSESP::watch() == 0) { - Mqtt::publish(F("system/watch"), read_flash_string(FL_(enum_watch)[EMSESP::watch()]).c_str()); + if (Mqtt::publish_single2cmd()) { + Mqtt::publish(F("system/syslog"), syslog_enabled_ ? read_flash_string(FL_(enum_syslog_level)[syslog_level_ + 1]).c_str() : "off"); + if (EMSESP::watch_id() == 0 || EMSESP::watch() == 0) { + Mqtt::publish(F("system/watch"), + EMSESP::system_.enum_format() == ENUM_FORMAT_INDEX ? Helpers::itoa(EMSESP::watch()) + : read_flash_string(FL_(enum_watch)[EMSESP::watch()]).c_str()); + } else { + Mqtt::publish(F("system/watch"), Helpers::hextoa(EMSESP::watch_id())); + } + } else { - char s[10]; - snprintf(s, sizeof(s), "0x%04X", EMSESP::watch_id()); - Mqtt::publish(F("system/watch"), s); + Mqtt::publish(F("system_data/syslog"), syslog_enabled_ ? read_flash_string(FL_(enum_syslog_level)[syslog_level_ + 1]).c_str() : "off"); + if (EMSESP::watch_id() == 0 || EMSESP::watch() == 0) { + Mqtt::publish(F("system_data/watch"), + EMSESP::system_.enum_format() == ENUM_FORMAT_INDEX ? Helpers::itoa(EMSESP::watch()) + : read_flash_string(FL_(enum_watch)[EMSESP::watch()]).c_str()); + } else { + Mqtt::publish(F("system_data/watch"), Helpers::hextoa(EMSESP::watch_id())); + } } } #endif @@ -524,7 +548,7 @@ bool System::heartbeat_json(JsonObject & output) { output["txfails"] = EMSESP::txservice_.telegram_read_fail_count() + EMSESP::txservice_.telegram_write_fail_count(); if (Mqtt::enabled()) { - output["mqttfails"] = Mqtt::publish_fails(); + output["mqttcount"] = Mqtt::publish_count(); output["mqttfails"] = Mqtt::publish_fails(); } output["apicalls"] = WebAPIService::api_count(); // + WebAPIService::api_fails(); @@ -636,16 +660,12 @@ void System::system_check() { // commands - takes static function pointers void System::commands_init() { - Command::add(EMSdevice::DeviceType::SYSTEM, - F_(pin), - System::command_pin, - F("set a GPIO on/off"), - CommandFlag::MQTT_SUB_FLAG_NOSUB | CommandFlag::ADMIN_ONLY); // dont create a MQTT topic for this - + // Command::add(EMSdevice::DeviceType::SYSTEM, F_(pin), System::command_pin, F("set a GPIO on/off"), CommandFlag::ADMIN_ONLY); Command::add(EMSdevice::DeviceType::SYSTEM, F_(send), System::command_send, F("send a telegram"), CommandFlag::ADMIN_ONLY); Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), System::command_fetch, F("refresh all EMS values"), CommandFlag::ADMIN_ONLY); Command::add(EMSdevice::DeviceType::SYSTEM, F_(restart), System::command_restart, F("restart EMS-ESP"), CommandFlag::ADMIN_ONLY); Command::add(EMSdevice::DeviceType::SYSTEM, F_(watch), System::command_watch, F("watch incoming telegrams")); + // register syslog command in syslog init // Command::add(EMSdevice::DeviceType::SYSTEM, F_(syslog), System::command_syslog_level, F("set syslog level"), CommandFlag::ADMIN_ONLY); if (Mqtt::enabled()) { @@ -822,7 +842,7 @@ void System::show_system(uuid::console::Shell & shell) { // show Ethernet if connected if (ethernet_connected_) { shell.println(); - shell.printfln(F(" Wired Network: connected")); + shell.printfln(F(" Ethernet Network: connected")); shell.printfln(F(" MAC address: %s"), ETH.macAddress().c_str()); shell.printfln(F(" Hostname: %s"), ETH.getHostname()); shell.printfln(F(" IPv4 address: %s/%s"), uuid::printable_to_string(ETH.localIP()).c_str(), uuid::printable_to_string(ETH.subnetMask()).c_str()); @@ -1021,6 +1041,13 @@ bool System::command_customizations(const char * value, const int8_t id, JsonObj sensorJson["offset"] = sensor.offset; sensorJson["factor"] = sensor.factor; sensorJson["uom"] = EMSdevice::uom_to_string(sensor.uom); + } else if (sensor.type == AnalogSensor::AnalogType::COUNTER || sensor.type == AnalogSensor::AnalogType::TIMER + || sensor.type == AnalogSensor::AnalogType::RATE) { + sensorJson["factor"] = sensor.factor; + sensorJson["uom"] = EMSdevice::uom_to_string(sensor.uom); + } else if (sensor.type >= AnalogSensor::AnalogType::PWM_0) { + sensorJson["frequency"] = sensor.factor; + sensorJson["factor"] = sensor.factor; } } @@ -1154,10 +1181,14 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp for (const auto & device_class : EMSFactory::device_handlers()) { for (const auto & emsdevice : EMSESP::emsdevices) { if ((emsdevice) && (emsdevice->device_type() == device_class.first)) { - JsonObject obj = devices.createNestedObject(); - obj["type"] = emsdevice->device_type_name(); - obj["name"] = emsdevice->to_string(); - obj["entities"] = emsdevice->count_entities(); + JsonObject obj = devices.createNestedObject(); + obj["type"] = emsdevice->device_type_name(); + // obj["name"] = emsdevice->to_string(); + obj["name"] = emsdevice->name(); + obj["device id"] = Helpers::hextoa(emsdevice->device_id()); + obj["product id"] = emsdevice->product_id(); + obj["version"] = emsdevice->version(); + obj["entities"] = emsdevice->count_entities(); char result[200]; (void)emsdevice->show_telegram_handlers(result, EMSdevice::Handlers::RECEIVED); if (result[0] != '\0') { diff --git a/src/web/WebCustomizationService.h b/src/web/WebCustomizationService.h index 5371e74bb..b2bd869da 100644 --- a/src/web/WebCustomizationService.h +++ b/src/web/WebCustomizationService.h @@ -44,7 +44,7 @@ class AnalogCustomization { public: uint8_t id; std::string name; - uint16_t offset; + float offset; float factor; uint8_t uom; // 0 is none int8_t type; // -1 is for deletion diff --git a/src/web/WebDataService.cpp b/src/web/WebDataService.cpp index a012c2d35..85989b9c7 100644 --- a/src/web/WebDataService.cpp +++ b/src/web/WebDataService.cpp @@ -275,7 +275,7 @@ void WebDataService::write_analog(AsyncWebServerRequest * request, JsonVariant & uint8_t id = analog["id"]; // this is the unique key std::string name = analog["name"]; float factor = analog["factor"]; - int16_t offset = analog["offset"]; + float offset = analog["offset"]; uint8_t uom = analog["uom"]; int8_t type = analog["type"]; ok = EMSESP::analogsensor_.update(id, name, offset, factor, uom, type); From 6b164b5487a2d8e5fcb0a55f148c617eef9a5c08 Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Wed, 16 Feb 2022 18:57:43 +0100 Subject: [PATCH 05/12] burner up to 130% --- src/devices/boiler.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/devices/boiler.cpp b/src/devices/boiler.cpp index 6ddebf5d7..b562e64ad 100644 --- a/src/devices/boiler.cpp +++ b/src/devices/boiler.cpp @@ -150,8 +150,15 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const MAKE_CF_CB(set_burn_period)); register_device_value( DeviceValueTAG::TAG_BOILER_DATA, &burnMinPower_, DeviceValueType::UINT, nullptr, FL_(burnMinPower), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_min_power)); - register_device_value( - DeviceValueTAG::TAG_BOILER_DATA, &burnMaxPower_, DeviceValueType::UINT, nullptr, FL_(burnMaxPower), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_max_power)); + register_device_value(DeviceValueTAG::TAG_BOILER_DATA, + &burnMaxPower_, + DeviceValueType::UINT, + nullptr, + FL_(burnMaxPower), + DeviceValueUOM::PERCENT, + MAKE_CF_CB(set_max_power), + 0, + 130); register_device_value( DeviceValueTAG::TAG_BOILER_DATA, &boilHystOn_, DeviceValueType::INT, nullptr, FL_(boilHystOn), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_hyst_on)); register_device_value( @@ -318,7 +325,9 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const nullptr, FL_(wwMaxPower), DeviceValueUOM::PERCENT, - MAKE_CF_CB(set_ww_maxpower)); + MAKE_CF_CB(set_ww_maxpower), + 0, + 130); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwCircPump_, DeviceValueType::BOOL, From 9ccb04489b740613a89f0363031c97f8c43f9354 Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Wed, 16 Feb 2022 18:58:15 +0100 Subject: [PATCH 06/12] log detection of devcies without values --- src/emsesp.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/emsesp.cpp b/src/emsesp.cpp index b6ec5f953..fe60f9825 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -1136,6 +1136,9 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const fetch_device_values(device_id); // go and fetch its data + // Print to LOG showing we've added a new device + LOG_INFO(F("Recognized new %s with deviceID 0x%02X"), EMSdevice::device_type_2_device_name(device_type).c_str(), device_id); + // add command commands for all devices, except for connect, controller and gateway if ((device_type == DeviceType::CONNECT) || (device_type == DeviceType::CONTROLLER) || (device_type == DeviceType::GATEWAY)) { return true; @@ -1170,9 +1173,6 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const // MQTT subscribe to the device e.g. "ems-esp/boiler/#" Mqtt::subscribe(device_type, EMSdevice::device_type_2_device_name(device_type) + "/#", nullptr); - // Print to LOG showing we've added a new device - LOG_INFO(F("Recognized new %s with deviceID 0x%02X"), EMSdevice::device_type_2_device_name(device_type).c_str(), device_id); - return true; } From b2eaca27ded38e03c851cc157435c850cbfd3289 Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Wed, 16 Feb 2022 18:58:45 +0100 Subject: [PATCH 07/12] remove unused flag --- src/command.cpp | 3 +-- src/command.h | 7 +++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/command.cpp b/src/command.cpp index 6164134ed..0efece5cd 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -317,14 +317,13 @@ void Command::add(const uint8_t device_type, const __FlashStringHelper * cmd, co } // add a command to the list, which does return a json object as output -// flag is fixed to MqttSubFlag::MQTT_SUB_FLAG_NOSUB so there will be no topic subscribed to this void Command::add(const uint8_t device_type, const __FlashStringHelper * cmd, const cmd_json_function_p cb, const __FlashStringHelper * description, uint8_t flags) { // if the command already exists for that device type don't add it if (find_command(device_type, read_flash_string(cmd).c_str()) != nullptr) { return; } - cmdfunctions_.emplace_back(device_type, (CommandFlag::MQTT_SUB_FLAG_NOSUB | flags), cmd, nullptr, cb, description); // callback for json is included + cmdfunctions_.emplace_back(device_type, flags, cmd, nullptr, cb, description); // callback for json is included } // see if a command exists for that device type diff --git a/src/command.h b/src/command.h index 6889be6c5..bc1bf7e7c 100644 --- a/src/command.h +++ b/src/command.h @@ -32,10 +32,9 @@ enum CommandFlag : uint8_t { MQTT_SUB_FLAG_DEFAULT = 0, // 0 no flags set, always subscribe to MQTT MQTT_SUB_FLAG_HC = (1 << 0), // 1 TAG_HC1 - TAG_HC8 MQTT_SUB_FLAG_WWC = (1 << 1), // 2 TAG_WWC1 - TAG_WWC4 - MQTT_SUB_FLAG_NOSUB = (1 << 2), // 4 - MQTT_SUB_FLAG_WW = (1 << 3), // 8 TAG_DEVICE_DATA_WW - HIDDEN = (1 << 4), // 16 do not show in API or Web - ADMIN_ONLY = (1 << 5) // 32 requires authentication + MQTT_SUB_FLAG_WW = (1 << 2), // 4 TAG_DEVICE_DATA_WW + HIDDEN = (1 << 3), // 8 do not show in API or Web + ADMIN_ONLY = (1 << 4) // 16 requires authentication }; From 3b41d6fff6d69ff187eed1d1ad9925e89910c76a Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Wed, 16 Feb 2022 18:59:22 +0100 Subject: [PATCH 08/12] fahrenheit uom --- src/analogsensor.cpp | 4 ++-- src/dallassensor.cpp | 20 ++++++++------------ src/emsdevice.cpp | 11 +++++------ 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/analogsensor.cpp b/src/analogsensor.cpp index 5ea7dffac..147440e82 100644 --- a/src/analogsensor.cpp +++ b/src/analogsensor.cpp @@ -89,7 +89,7 @@ void AnalogSensor::reload() { it++; } // add new sensors from list - for (auto & sensor : sensors) { + for (auto & sensor : sensors) { bool found = false; for (auto & sensor_ : sensors_) { if (sensor_.id() == sensor.id) { @@ -229,7 +229,7 @@ void AnalogSensor::measure() { } else if (!sensor.poll_) { // falling edge if (sensor.type() == AnalogType::COUNTER) { sensor.set_value(old_value + sensor.factor()); - } else if (sensor.type() == AnalogType::RATE) { // dafault uom: Hz (1/sec) with factor 1 + } else if (sensor.type() == AnalogType::RATE) { // dafault uom: Hz (1/sec) with factor 1 sensor.set_value(sensor.factor() * 1000 / (sensor.polltime_ - sensor.last_polltime_)); } else if (sensor.type() == AnalogType::TIMER) { // default seconds with factor 1 sensor.set_value(sensor.factor() * (sensor.polltime_ - sensor.last_polltime_) / 1000); diff --git a/src/dallassensor.cpp b/src/dallassensor.cpp index fe7846a5b..d8e9722ba 100644 --- a/src/dallassensor.cpp +++ b/src/dallassensor.cpp @@ -361,10 +361,10 @@ bool DallasSensor::command_info(const char * value, const int8_t id, JsonObject JsonObject dataSensor = output.createNestedObject(sensor.name()); dataSensor["id_str"] = sensor.id_str(); if (Helpers::hasValue(sensor.temperature_c)) { - dataSensor["temp"] = (float)(sensor.temperature_c) / 10; + dataSensor["temp"] = Helpers::round2((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0); } } else if (Helpers::hasValue(sensor.temperature_c)) { - output[sensor.name()] = (float)(sensor.temperature_c) / 10; + output[sensor.name()] = Helpers::round2((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0); } } @@ -378,11 +378,11 @@ bool DallasSensor::get_value_info(JsonObject & output, const char * cmd, const i output["id_str"] = sensor.id_str(); output["name"] = sensor.name(); if (Helpers::hasValue(sensor.temperature_c)) { - output["value"] = (float)(sensor.temperature_c) / 10; + output["value"] = Helpers::round2((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0); } output["type"] = F_(number); - output["min"] = -55; - output["max"] = 125; + output["min"] = Helpers::round2(-55, 0, EMSESP::system_.fahrenheit() ? 2 : 0); + output["max"] = Helpers::round2(125, 0, EMSESP::system_.fahrenheit() ? 2 : 0); output["unit"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES); output["writeable"] = false; return true; @@ -444,10 +444,10 @@ void DallasSensor::publish_values(const bool force) { JsonObject dataSensor = doc.createNestedObject(sensor.id_str()); dataSensor["name"] = sensor.name(); if (has_value) { - dataSensor["temp"] = (float)(sensor.temperature_c) / 10; + dataSensor["temp"] = Helpers::round2((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0); } } else if (has_value) { - doc[sensor.name()] = (float)(sensor.temperature_c) / 10; + doc[sensor.name()] = Helpers::round2((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0); } // create the HA MQTT config @@ -463,11 +463,7 @@ void DallasSensor::publish_values(const bool force) { snprintf(stat_t, sizeof(stat_t), "%s/dallassensor_data", Mqtt::base().c_str()); config["stat_t"] = stat_t; - if (EMSESP::system_.fahrenheit()) { - config["unit_of_meas"] = FJSON("°F"); - } else { - config["unit_of_meas"] = FJSON("°C"); - } + config["unit_of_meas"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES); char str[50]; snprintf(str, sizeof(str), "{{value_json['%s'].temp}}", sensor.id_str().c_str()); diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index 0306e4b10..17a0f4434 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -52,6 +52,9 @@ const std::string EMSdevice::tag_to_mqtt(uint8_t tag) { } const std::string EMSdevice::uom_to_string(uint8_t uom) { + if (EMSESP::system_.fahrenheit() && (uom == DeviceValueUOM::DEGREES || uom == DeviceValueUOM::DEGREES_R)) { + return read_flash_string(DeviceValue::DeviceValueUOM_s[DeviceValueUOM::FAHRENHEIT]); + } return read_flash_string(DeviceValue::DeviceValueUOM_s[uom]); } @@ -306,11 +309,7 @@ void EMSdevice::list_device_entries(JsonObject & output) { // add uom if (!uom_to_string(dv.uom).empty() && uom_to_string(dv.uom) != " ") { - if (EMSESP::system_.fahrenheit() && (dv.uom == DeviceValueUOM::DEGREES || dv.uom == DeviceValueUOM::DEGREES_R)) { - details.add(EMSdevice::uom_to_string(DeviceValueUOM::FAHRENHEIT)); - } else { - details.add(EMSdevice::uom_to_string(dv.uom)); - } + details.add(EMSdevice::uom_to_string(dv.uom)); } } } @@ -1024,7 +1023,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8 // add uom if it's not a " " (single space) if (!uom_to_string(dv.uom).empty() && uom_to_string(dv.uom) != " ") { - json["uom"] = fahrenheit ? "°F" : uom_to_string(dv.uom); + json["uom"] = uom_to_string(dv.uom); } json["writeable"] = dv.has_cmd; From 4219842088bf04ae5ffc45ed29c3627c78052bda Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Wed, 16 Feb 2022 19:36:17 +0100 Subject: [PATCH 09/12] fetch devices one by one --- src/emsesp.cpp | 27 +++++++++++++++++++++++---- src/emsesp.h | 1 + src/telegram.h | 4 ++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/emsesp.cpp b/src/emsesp.cpp index fe60f9825..9a9c53ddd 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -1413,6 +1413,28 @@ void EMSESP::start() { webServer.begin(); // start the web server } +// fetch devices one by one +void EMSESP::scheduled_fetch_values() { + static uint8_t no = 0; + if (no || (uuid::get_uptime() - last_fetch_ > EMS_FETCH_FREQUENCY)) { + if (!no) { + last_fetch_ = uuid::get_uptime(); + no = 1; + } + if (txservice_.tx_queue_empty()) { + uint8_t i = 0; + for (const auto & emsdevice : emsdevices) { + if (emsdevice && ++i >= no) { + emsdevice->fetch_values(); + no++; + return; + } + } + no = 0; + } + } +} + // main loop calling all services void EMSESP::loop() { esp8266React.loop(); // web services @@ -1429,10 +1451,7 @@ void EMSESP::loop() { mqtt_.loop(); // sends out anything in the MQTT queue // force a query on the EMS devices to fetch latest data at a set interval (1 min) - if ((uuid::get_uptime() - last_fetch_ > EMS_FETCH_FREQUENCY)) { - last_fetch_ = uuid::get_uptime(); - fetch_device_values(); - } + scheduled_fetch_values(); } console_.loop(); // telnet/serial console diff --git a/src/emsesp.h b/src/emsesp.h index f9fc2fc31..3395824d5 100644 --- a/src/emsesp.h +++ b/src/emsesp.h @@ -206,6 +206,7 @@ class EMSESP { static void fetch_device_values(const uint8_t device_id = 0); static void fetch_device_values_type(const uint8_t device_type); static bool valid_device(const uint8_t device_id); + static void scheduled_fetch_values(); static bool add_device(const uint8_t device_id, const uint8_t product_id, const char * version, const uint8_t brand); static void scan_devices(); diff --git a/src/telegram.h b/src/telegram.h index 5ce46c5ad..e75f53008 100644 --- a/src/telegram.h +++ b/src/telegram.h @@ -399,6 +399,10 @@ class TxService : public EMSbus { return tx_telegrams_; } + bool tx_queue_empty() { + return tx_telegrams_.size() == 0; + } + #if defined(EMSESP_DEBUG) static constexpr uint8_t MAXIMUM_TX_RETRIES = 0; // when compiled with EMSESP_DEBUG don't retry #else From 9046a6578aed16cc857d431875b197a95179db9a Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Wed, 16 Feb 2022 19:37:14 +0100 Subject: [PATCH 10/12] telegram read length depends on ems/ems+ --- src/telegram.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/telegram.cpp b/src/telegram.cpp index 0fa1b531e..59c0921b8 100644 --- a/src/telegram.cpp +++ b/src/telegram.cpp @@ -536,13 +536,13 @@ void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t lengt void TxService::read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t length) { LOG_DEBUG(F("Tx read request to device 0x%02X for type ID 0x%02X"), dest, type_id); - uint8_t message_data[1] = {EMS_MAX_TELEGRAM_LENGTH}; // request all data, 32 bytes + uint8_t message_data = (type_id > 0xFF) ? (EMS_MAX_TELEGRAM_MESSAGE_LENGTH - 2) : EMS_MAX_TELEGRAM_MESSAGE_LENGTH; // if length set, publish result and set telegram to front if (length) { - message_data[0] = length; + message_data = length; EMSESP::set_read_id(type_id); } - add(Telegram::Operation::TX_READ, dest, type_id, offset, message_data, 1, 0, length != 0); + add(Telegram::Operation::TX_READ, dest, type_id, offset, &message_data, 1, 0, length != 0); } // Send a raw telegram to the bus, telegram is a text string of hex values @@ -663,9 +663,10 @@ uint16_t TxService::post_send_query() { if (post_typeid) { uint8_t dest = (this->telegram_last_->dest & 0x7F); // when set a value with large offset before and validate on same type, we have to add offset 0, 26, 52, ... - uint8_t offset = (this->telegram_last_->type_id == post_typeid) ? ((this->telegram_last_->offset / 26) * 26) : 0; - uint8_t message_data[1] = {EMS_MAX_TELEGRAM_LENGTH}; // request all data, 32 bytes - this->add(Telegram::Operation::TX_READ, dest, post_typeid, offset, message_data, 1, 0, true); // add to top/front of queue + uint8_t offset = (this->telegram_last_->type_id == post_typeid) ? ((this->telegram_last_->offset / 26) * 26) : 0; + uint8_t message_data = + (this->telegram_last_->type_id > 0xFF) ? (EMS_MAX_TELEGRAM_MESSAGE_LENGTH - 2) : EMS_MAX_TELEGRAM_MESSAGE_LENGTH; // request all data, 32 bytes + this->add(Telegram::Operation::TX_READ, dest, post_typeid, offset, &message_data, 1, 0, true); // add to top/front of queue // read_request(telegram_last_post_send_query_, dest, 0); // no offset LOG_DEBUG(F("Sending post validate read, type ID 0x%02X to dest 0x%02X"), post_typeid, dest); set_post_send_query(0); // reset From 18651bdaf40220003749d6cecc8e7d3d17d8a617 Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Wed, 16 Feb 2022 19:37:23 +0100 Subject: [PATCH 11/12] changelog, typo --- CHANGELOG_LATEST.md | 7 +++++++ interface/src/project/DashboardData.tsx | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index 89d33a09b..74ecb6350 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -22,6 +22,10 @@ - Help text for string commands in WebUI [#320](https://github.com/emsesp/EMS-ESP32/issues/320) - Germany translations (at compile time) - #entities added to system/info` endpoint [#322](https://github.com/emsesp/EMS-ESP32/issues/322) +- analog outputs digital/pwm/dac +- remove MQTT retained configs if discovery is disabled +- timeout 10 min for MQTT-QoS wait +- Moduline 300 auto-temperatures T1-T4, RC300 romminfluencefactor ### Fixed @@ -38,6 +42,7 @@ - Fix uploading firmware on OSX [#345](https://github.com/emsesp/EMS-ESP32/issues/345) - Non-nested MQTT would corrupt the json [#354](https://github.com/emsesp/EMS-ESP32/issues/354) - Burner selected max power can have a value higher than 100% [#314](https://github.com/emsesp/EMS-ESP32/issues/314) +- some missing fahrenheit calculations ### Changed @@ -51,6 +56,8 @@ - Show ems tx reads and writes separatly - Show ems device handlers separated for received, fetched and pending handlers. - Wired renamed to Ethernet +- removed system/pin command, new commands in analogsensors +- system/info device-info split to name/version/brand ## **BREAKING CHANGES:** diff --git a/interface/src/project/DashboardData.tsx b/interface/src/project/DashboardData.tsx index 19047073c..fab64a359 100644 --- a/interface/src/project/DashboardData.tsx +++ b/interface/src/project/DashboardData.tsx @@ -614,7 +614,7 @@ const DashboardData: FC = () => { {analog_data.i} {analog_data.n} - {AnalogTypes[analog_data.t]} + {AnalogTypeNames[analog_data.t]} {formatValue(analog_data.v, analog_data.u)} ))} @@ -709,7 +709,7 @@ const DashboardData: FC = () => { - {AnalogTypes.map((val, i) => ( + {AnalogTypeNames.map((val, i) => ( {val} From b7d8447f737fb8891974ec266a2313a9f22855dc Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Thu, 17 Feb 2022 09:17:17 +0100 Subject: [PATCH 12/12] version 3.4.0b6 --- src/version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h b/src/version.h index 79659168a..ead90ccd1 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.4.0b5" +#define EMSESP_APP_VERSION "3.4.0b6"