Merge remote-tracking branch 'origin/dev'

This commit is contained in:
proddy
2026-08-02 09:55:48 +02:00
54 changed files with 1458 additions and 1111 deletions
+15 -9
View File
@@ -86,16 +86,22 @@ void NTPSettingsService::ntp_received(struct timeval * tv) {
}
void NTPSettings::read(NTPSettings & settings, JsonObject root) {
root["enabled"] = settings.enabled;
root["server"] = settings.server;
root["tz_label"] = settings.tzLabel;
root["tz_format"] = settings.tzFormat;
root["enabled"] = settings.enabled;
root["server"] = settings.server;
root["tz_label"] = settings.tzLabel;
root["tz_format"] = settings.tzFormat;
root["thermostat_sync"] = settings.thermostat_sync;
root["tz_label_t"] = settings.tzLabelT;
root["tz_format_t"] = settings.tzFormatT;
}
StateUpdateResult NTPSettings::update(JsonObject root, NTPSettings & settings) {
settings.enabled = root["enabled"] | FACTORY_NTP_ENABLED;
settings.server = root["server"] | FACTORY_NTP_SERVER;
settings.tzLabel = root["tz_label"] | FACTORY_NTP_TIME_ZONE_LABEL;
settings.tzFormat = root["tz_format"] | FACTORY_NTP_TIME_ZONE_FORMAT;
settings.enabled = root["enabled"] | FACTORY_NTP_ENABLED;
settings.server = root["server"] | FACTORY_NTP_SERVER;
settings.tzLabel = root["tz_label"] | FACTORY_NTP_TIME_ZONE_LABEL;
settings.tzFormat = root["tz_format"] | FACTORY_NTP_TIME_ZONE_FORMAT;
settings.thermostat_sync = root["thermostat_sync"] | FACTORY_NTP_THERMOSTAT_SYNC;
settings.tzLabelT = root["tz_label_t"] | FACTORY_NTP_TIME_ZONE_LABEL;
settings.tzFormatT = root["tz_format_t"] | FACTORY_NTP_TIME_ZONE_FORMAT;
return StateUpdateResult::CHANGED;
}
}
+11 -4
View File
@@ -19,6 +19,10 @@
#define FACTORY_NTP_TIME_ZONE_FORMAT "GMT0BST,M3.5.0/1,M10.5.0"
#endif
#ifndef FACTORY_NTP_THERMOSTAT_SYNC
#define FACTORY_NTP_THERMOSTAT_SYNC 1
#endif
#ifndef FACTORY_NTP_SERVER
#define FACTORY_NTP_SERVER "time.google.com"
#endif
@@ -30,10 +34,13 @@
class NTPSettings {
public:
bool enabled = FACTORY_NTP_ENABLED;
String tzLabel = FACTORY_NTP_TIME_ZONE_LABEL;
String tzFormat = FACTORY_NTP_TIME_ZONE_FORMAT;
String server = FACTORY_NTP_SERVER;
bool enabled = FACTORY_NTP_ENABLED;
String tzLabel = FACTORY_NTP_TIME_ZONE_LABEL;
String tzFormat = FACTORY_NTP_TIME_ZONE_FORMAT;
String server = FACTORY_NTP_SERVER;
uint8_t thermostat_sync = FACTORY_NTP_THERMOSTAT_SYNC;
String tzLabelT = FACTORY_NTP_TIME_ZONE_LABEL;
String tzFormatT = FACTORY_NTP_TIME_ZONE_FORMAT;
static void read(NTPSettings & settings, JsonObject root);
static StateUpdateResult update(JsonObject root, NTPSettings & settings);
+11 -10
View File
@@ -4,7 +4,6 @@
#include <esp_app_format.h>
#include <esp_ota_ops.h>
// #include <esp_partition.h>
static String getFilenameExtension(const String & filename) {
const auto pos = filename.lastIndexOf('.');
@@ -49,7 +48,7 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
// LittleFS filesystem image
_is_filesystem = true;
_md5[0] = '\0'; // clear any stale md5 so Update.end() doesn't compare against it
} else if ((extension == "bin") && (filesize > 2000000)) {
} else if ((extension == "bin") && (filesize > 1000000)) {
_is_firmware = true;
} else if (extension == "json") {
_md5[0] = '\0'; // clear md5
@@ -133,9 +132,10 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
}
if (_is_firmware || _is_filesystem) {
if (!request->_tempObject) { // if we haven't delt with an error, continue with the OTA update
if (!request->_tempObject) {
//continue with the OTA update
if (Update.write(data, len) != len) {
emsesp::EMSESP::logger().err("Update.write failed at offset %u (chunk %u): %s",
emsesp::EMSESP::logger().err("OTA update failed at offset %u (chunk %u): %s",
static_cast<unsigned>(Update.progress()),
static_cast<unsigned>(len),
Update.errorString());
@@ -144,15 +144,16 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
}
if (final) {
if (!Update.end(true)) {
emsesp::EMSESP::logger().err("Update.end failed: %s", Update.errorString());
handleError(request, 500);
emsesp::EMSESP::logger().err("OTA update failed: %s", Update.errorString());
handleError(request, 500); // internal error, failed
return;
}
}
} else {
if (len && len != request->_tempFile.write(data, len)) { // stream the incoming chunk to the opened file
handleError(request, 507); // 507-Insufficient Storage
}
}
} else {
// stream the incoming chunk to the opened file
if (len && len != request->_tempFile.write(data, len)) {
handleError(request, 507); // 507-Insufficient Storage
}
}
}
+6 -2
View File
@@ -15,7 +15,7 @@ void WiFiScanner::scanNetworks(AsyncWebServerRequest * request) {
if (WiFi.scanComplete() != -1) {
WiFi.scanDelete();
WiFi.scanNetworks(true);
WiFi.scanNetworks(true, false, true, 250);
}
}
@@ -38,6 +38,10 @@ void WiFiScanner::listNetworks(AsyncWebServerRequest * request) {
} else if (numNetworks == -1) {
request->send(202); // special code to indicate scan in progress
} else {
scanNetworks(request);
auto * response = new AsyncJsonResponse(false);
JsonObject root = response->getRoot();
root["networks"].to<JsonArray>();
response->setLength();
request->send(response);
}
}
+4 -2
View File
@@ -546,11 +546,13 @@ bool AnalogSensor::update(uint8_t gpio, const char * org_name, double offset, do
if (deleted) {
LOG_DEBUG("Removing analog sensor GPIO %02d", gpio);
EMSESP::system_.remove_gpio(gpio); // remove from used list only
EMSESP::nvs_.remove(AnalogCustomization.name);
if (EMSESP::nvs_.isKey(AnalogCustomization.name)) {
EMSESP::nvs_.remove(AnalogCustomization.name);
}
settings.analogCustomizations.remove(AnalogCustomization);
} else {
// update existing record
if (!strcmp(name, AnalogCustomization.name)) {
if (!strcmp(name, AnalogCustomization.name) && EMSESP::nvs_.isKey(AnalogCustomization.name)) {
EMSESP::nvs_.remove(AnalogCustomization.name);
}
strlcpy(AnalogCustomization.name, name, sizeof(AnalogCustomization.name));
+4 -3
View File
@@ -105,7 +105,7 @@
{215, DeviceType::THERMOSTAT, "Comfort RF", DeviceFlags::EMS_DEVICE_FLAG_CRF | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18
{216, DeviceType::THERMOSTAT, "CRF200S", DeviceFlags::EMS_DEVICE_FLAG_CRF | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18
{246, DeviceType::THERMOSTAT, "Comfort+2RF", DeviceFlags::EMS_DEVICE_FLAG_CRF | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18
{253, DeviceType::THERMOSTAT, "Rego 3000, UI800, Logamatic BC400", DeviceFlags::EMS_DEVICE_FLAG_BC400}, // 0x10
{253, DeviceType::THERMOSTAT, "Rego 3000, UI800, Logamatic BC400", DeviceFlags::EMS_DEVICE_FLAG_UI800}, // 0x10
// Thermostat - Sieger - 0x10 / 0x17
{ 66, DeviceType::THERMOSTAT, "ES72, RC20", DeviceFlags::EMS_DEVICE_FLAG_RC20_N}, // 0x17 or remote
@@ -187,8 +187,9 @@
{ 74, DeviceType::ALERT, "EM10", DeviceFlags::EMS_DEVICE_FLAG_NONE},
// Gateways - 0x48
{17, DeviceType::GATEWAY, "MX400", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x48 and 0x4B
{189, DeviceType::GATEWAY, "KM200, MB LAN 2", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{17, DeviceType::GATEWAY, "MX400", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x48, 0x4B, or 0x50 as wireless base
{189, DeviceType::GATEWAY, "KM200, MB LAN 2", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x48
{222, DeviceType::GATEWAY, "KM300", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x4A
{252, DeviceType::GATEWAY, "K30RF, MX300", DeviceFlags::EMS_DEVICE_FLAG_NONE},
// Generic - 0x40 or other with no product-id and no version
+1
View File
@@ -513,6 +513,7 @@ class EMSdevice {
static constexpr uint8_t EMS_DEVICE_FLAG_CR120 = 16; // mostly like RC300, but some changes
static constexpr uint8_t EMS_DEVICE_FLAG_CR11 = 17; // CRF200 only monitor
static constexpr uint8_t EMS_DEVICE_FLAG_HMC310 = 18;
static constexpr uint8_t EMS_DEVICE_FLAG_UI800 = 19;
uint8_t count_entities();
uint8_t count_entities_fav();
+1
View File
@@ -158,6 +158,7 @@ class DeviceValue {
enum DeviceValueNumOp : int8_t {
DV_NUMOP_NONE = 0, // default
DV_NUMOP_DIV2 = 2,
DV_NUMOP_DIV4 = 4,
DV_NUMOP_DIV10 = 10,
DV_NUMOP_DIV60 = 60,
DV_NUMOP_DIV100 = 100,
+23 -11
View File
@@ -1800,8 +1800,8 @@ void EMSESP::start() {
LOG_INFO("Library loaded: %d EMS devices, %d device entities, %s", device_library_.size(), EMSESP_TRANSLATION_COUNT, system_.languages_string().c_str());
webCustomizationService.begin(); // load the customizations
webSchedulerService.begin(); // load the scheduler events
webCustomEntityService.begin(); // load the custom telegram reads
webSchedulerService.begin(); // load the scheduler events
// start telnet service if it's enabled
// default idle is 10 minutes, default write timeout is 0 (automatic)
@@ -1864,16 +1864,9 @@ void EMSESP::loop() {
webLogService.loop(); // log in Web UI
// run the loop, unless we're in the middle of an OTA upload
if (EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_NORMAL || EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_INVALID_GPIO) {
// check for GPIO Errors - this is called once when booting
if (EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_INVALID_GPIO) {
static bool only_once = false;
if (!only_once) {
LOG_ERROR("Invalid GPIOs used. Please check your settings and the system log");
only_once = true;
}
}
// if (EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_NORMAL || EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_INVALID_GPIO) {
if (EMSESP::system_.systemStatus() != SYSTEM_STATUS::SYSTEM_STATUS_PENDING_UPLOAD
&& EMSESP::system_.systemStatus() != SYSTEM_STATUS::SYSTEM_STATUS_UPLOADING) {
// loop through the services
rxservice_.loop(); // process any incoming Rx telegrams
shower_.loop(); // check for shower on/off
@@ -1887,6 +1880,14 @@ void EMSESP::loop() {
}
scheduled_fetch_values(); // force a query on the EMS devices to fetch latest data at a set interval (1 min)
}
// check for GPIO Errors - this is called once when booting
if (EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_INVALID_GPIO) {
static bool only_once = false;
if (!only_once) {
LOG_ERROR("Invalid GPIOs used. Please check your settings and the system log");
only_once = true;
}
}
if (EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_PENDING_UPLOAD) {
// start an upload from a URL, assuming the URL exists and set from a previous pass
@@ -1899,6 +1900,17 @@ void EMSESP::loop() {
}
}
// reset status after 5 Minutes
if (EMSESP::system_.systemStatus() != SYSTEM_STATUS::SYSTEM_STATUS_NORMAL) {
static uint32_t starttime = 0;
if (starttime == 0) {
starttime = uuid::get_uptime_ms();
} else if (uuid::get_uptime_ms() - starttime > 300000) {
starttime = 0;
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_NORMAL);
}
}
// telnet service
#ifndef EMSESP_STANDALONE
if (system_.telnet_enabled()) {
+14 -1
View File
@@ -817,7 +817,7 @@ std::string Helpers::toUpper(std::string const & s) {
// capitalizes one UTF-8 character in char array
// works with Latin1 (1 byte), Polish and other (2 bytes) characters
// supports special characters for all 11 supported languages: EN, DE, NL, SV, PL, NO, FR, TR, IT, SK, CZ
// supports special characters for all 11 supported languages: EN, DE, NL, SV, PL, NO, FR, TR, IT, SK, CS
#if defined(EMSESP_STANDALONE)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wtype-limits"
@@ -1033,4 +1033,17 @@ float Helpers::numericoperator2scalefactor(int8_t numeric_operator) {
return -numeric_operator;
}
int16_t Helpers::calc_dew(int16_t temp, uint8_t humi) {
if (humi == EMS_VALUE_UINT8_NOTSET || temp == EMS_VALUE_INT16_NOTSET) {
return EMS_VALUE_INT16_NOTSET;
}
const float k2 = 17.62;
const float k3 = 243.12;
const float t = (float)temp / 10;
const float h = (float)humi / 100;
int16_t dt = (10 * k3 * (((k2 * t) / (k3 + t)) + log(h)) / (((k2 * k3) / (k3 + t)) - log(h)));
return dt;
}
} // namespace emsesp
+1
View File
@@ -84,6 +84,7 @@ class Helpers {
static uint8_t count_items(const char * const * list);
static const char * translated_word(const char * const * strings, const bool force_en = false);
static int16_t calc_dew(int16_t temp, uint8_t hum);
#ifdef EMSESP_STANDALONE
static char * ultostr(char * ptr, uint32_t value, const uint8_t base);
+6 -1
View File
@@ -190,7 +190,11 @@ MAKE_NOTRANSLATION(rc100, "RC100")
MAKE_NOTRANSLATION(rc100h, "RC100H")
MAKE_NOTRANSLATION(tc100, "TC100")
MAKE_NOTRANSLATION(rc120rf, "RC120RF")
MAKE_NOTRANSLATION(rc220, "RC220/RT800")
MAKE_NOTRANSLATION(rc220, "RC220")
MAKE_NOTRANSLATION(rt800, "RT800")
MAKE_NOTRANSLATION(cr10, "CR10")
MAKE_NOTRANSLATION(cr10h, "CR10H")
MAKE_NOTRANSLATION(cr20rf, "CR20RF")
MAKE_NOTRANSLATION(single, "single")
MAKE_NOTRANSLATION(dash, "-")
MAKE_NOTRANSLATION(BLANK, "")
@@ -370,6 +374,7 @@ MAKE_ENUM(enum_roomsensor, FL_(extern), FL_(intern), FL_(auto))
MAKE_ENUM(enum_roominfluence, FL_(off), FL_(intern), FL_(extern), FL_(auto))
MAKE_ENUM(enum_control1, FL_(rc310), FL_(rc200), FL_(rc100), FL_(rc100h), FL_(tc100))
MAKE_ENUM(enum_control2, FL_(off), FL_(dash), FL_(rc100), FL_(rc100h), FL_(dash), FL_(rc120rf), FL_(rc220), FL_(single)) // BC400
MAKE_ENUM(enum_control3, FL_(off), FL_(dash), FL_(cr10), FL_(cr10h), FL_(dash), FL_(cr20rf), FL_(rt800), FL_(single)) // UI800
MAKE_ENUM(enum_switchmode, FL_(off), FL_(eco), FL_(comfort), FL_(heat))
MAKE_ENUM(enum_switchProgMode, FL_(level), FL_(absolute))
+6 -6
View File
@@ -30,9 +30,9 @@
#define EMSESP_LOCALE_TR "tr"
#define EMSESP_LOCALE_IT "it"
#define EMSESP_LOCALE_SK "sk"
#define EMSESP_LOCALE_CZ "cz"
#define EMSESP_LOCALE_CS "cs"
// IMPORTANT! translations are in the order: en, de, nl, sv, pl, no, fr, tr, it, sk, cz
// IMPORTANT! translations are in the order: en, de, nl, sv, pl, no, fr, tr, it, sk, cs
//
// if there is no translation, it will default to en
@@ -292,7 +292,7 @@ MAKE_WORD_TRANSLATION(curve, "heatingcurve", "Heizkurve", "stookkromme", "värme
MAKE_WORD_TRANSLATION(radiator, "radiator", "Heizkörper", "radiator", "Radiator", "grzejniki", "radiator", "radiateur", "radyatör", "radiatore", "radiátor", "radiátor")
MAKE_WORD_TRANSLATION(convector, "convector", "Konvektor", "convector", "Konvektor", "konwektory", "konvektor", "convecteur", "convector", "convettore", "konvektor", "konvektor")
MAKE_WORD_TRANSLATION(floor, "floor", "Fussboden", "vloer", "Golv", "podłoga", "gulv", "sol", "yer", "pavimento", "podlaha", "podlaha")
MAKE_WORD_TRANSLATION(roomflow, "roomflow", "Raum Fluß", "kamer doorstroming", "Rumsflöde", "przepływ w pomieszczeniu", "romstrøm", "flux de la pièce", "oda akışı", "flusso della stanza", "prúdenie miestnosti", "průtok mistnosti")
MAKE_WORD_TRANSLATION(roomflow, "roomflow", "Raum Fluss", "kamer doorstroming", "Rumsflöde", "przepływ w pomieszczeniu", "romstrøm", "flux de la pièce", "oda akışı", "flusso della stanza", "prúdenie miestnosti", "průtok mistnosti")
MAKE_WORD_TRANSLATION(roomload, "roomload", "Raum Bedarf", "kamer behoefte", "Rumsbehov", "zapotrzebowanie pomieszczenia", "rombelastning", "charge de la pièce", "oda yükü", "carico della stanza", "izbová zaťaž", "zatížení místnosti")
MAKE_WORD_TRANSLATION(summer, "summer", "Sommer", "zomer", "Sommar", "lato", "sommer", "été", "yaz", "estate", "leto", "léto")
MAKE_WORD_TRANSLATION(winter, "winter", "Winter", "winter", "Vinter", "zima", "vinter", "hiver", "kış", "inverno", "zima", "zima")
@@ -367,7 +367,7 @@ MAKE_WORD_TRANSLATION(bookshelf, "mdi:bookshelf", "mdi:bookshelf")
// MQTT Discovery - this is special device entity for 'climate'
MAKE_TRANSLATION(haclimate, "haclimate", "mqtt discovery current room temperature", "Discovery aktuelle Raumtemperatur", "Discovery huidige kamertemperatuur", "MQTT Discovery för aktuell rumstemperatur", "termostat w HA", "HA Avlest temp", "découverte mqtt température actuelle", "Güncel osa sıcaklığı", "verifica temperatura ambiente attuale", "mqtt discovery aktuálna teplota v miestnosti", "mqtt discovery aktuální pokojová teplota")
// Entity translations: tag, mqtt, en, de, nl, sv, pl, no, fr, tr, it, sk, cz
// Entity translations: tag, mqtt, en, de, nl, sv, pl, no, fr, tr, it, sk, cs
// Boiler
MAKE_TRANSLATION(chimneysweeper, "chimneysweeper", "chimney sweeper", "Schornsteinfeger", "schoorsteenveger", "Sotare", "tryb kominiarza", "feier", "ramoneur", "baca temizleyici", "spazzacamino", "kominár", "kominík")
MAKE_TRANSLATION(reset, "reset", "reset", "Reset", "Reset", "Återställ", "kasowanie komunikatu", "nullstill", "reset", "Sıfırla", "Reset", "reset", "reset")
@@ -826,8 +826,8 @@ MAKE_TRANSLATION(vacations7, "vacations7", "vacation dates 7", "Urlaubstage 7",
MAKE_TRANSLATION(vacations8, "vacations8", "vacation dates 8", "Urlaubstage 8", "Vakantiedagen 8", "Semesterdatum 8", "urlop 8", "feriedager 8", "dates vacances 8", "izin günleri 8", "date vacanze 8", "termíny dovolenky 8", "data prázdnin 8")
MAKE_TRANSLATION(absent, "absent", "absent", "Abwesend", "", "Frånvarande", "", "", "", "", "", "chýbajúci", "chybějící")
MAKE_TRANSLATION(redthreshold, "redthreshold", "reduction threshold", "Absenkschwelle", "", "Tröskel för sänkning", "", "", "", "", "", "zníženie tresholdu", "práh snížení")
MAKE_TRANSLATION(solarinfl, "solarinfl", "solar influence", "Solareinfluß", "", "", "", "", "", "", "", "slnečný vplyv", "sluneční vliv")
MAKE_TRANSLATION(currsolarinfl, "currsolarinfl", "current solar influence", "akt. Solareinfluß", "", "", "", "", "", "", "", "aktuálny slnečný vplyv", "aktuální sluneční vliv")
MAKE_TRANSLATION(solarinfl, "solarinfl", "solar influence", "Solareinfluss", "", "", "", "", "", "", "", "slnečný vplyv", "sluneční vliv")
MAKE_TRANSLATION(currsolarinfl, "currsolarinfl", "current solar influence", "akt. Solareinfluss", "", "", "", "", "", "", "", "aktuálny slnečný vplyv", "aktuální sluneční vliv")
MAKE_TRANSLATION(hpmode, "hpmode", "HP Mode", "WP-Modus", "Modus warmtepomp", "Värmepumpsläge", "tryb pracy pompy ciepła", "", "", "yüksek güç modu", "Modalità Termopompa", "Režim TČ", "režim tepelného čerpadla")
MAKE_TRANSLATION(dewoffset, "dewoffset", "dew point offset", "Taupunktdifferenz", "Offset dauwpunt", "Daggpunktsförskjutning", "przesunięcie punktu rosy", "", "", "çiğ noktası göreli", "differenza del punto di rugiada", "posun rosného bodu", "offset rosného bodu")
MAKE_TRANSLATION(roomtempdiff, "roomtempdiff", "room temp difference", "Raumtemperaturdifferenz", "Verschiltemperatuur kamertemp", "Rumstemperaturskillnad", "różnica temp. pomieszczenia", "", "", "oda sıcaklığı farkı", "differenza temperatura ambiente", "rozdiel izbovej teploty", "rozdíl teploty místnosti")
+13 -19
View File
@@ -300,6 +300,14 @@ const std::initializer_list<Modbus::EntityModbusInfo> Modbus::modbus_register_ma
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(heatingPID), 74, 1), // heatingpid
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(preheating), 75, 1), // preheating
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(vacations), 76, 13), // vacations
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(hpoperatingmode), 89, 1), // hpoperatingmode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(summertemp), 90, 1), // summertemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(instantstart), 91, 1), // instantstart
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(coolstart), 92, 1), // coolstart
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(heatondelay), 93, 1), // heatondelay
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(heatoffdelay), 94, 1), // heatoffdelay
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(coolondelay), 95, 1), // coolondelay
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DEVICE_DATA, FL_(cooloffdelay), 96, 1), // cooloffdelay
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(selRoomTemp), 0, 1), // seltemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(roomTemp), 1, 1), // currtemp
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(haclimate), 2, 1), // haclimate
@@ -349,12 +357,12 @@ const std::initializer_list<Modbus::EntityModbusInfo> Modbus::modbus_register_ma
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(remotehum), 46, 1), // remotehum
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(heatondelay), 47, 1), // heatondelay
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(heatoffdelay), 48, 1), // heatoffdelay
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(instantstart), 49, 1), // instantstart
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(boost), 50, 1), // boost
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(boosttime), 51, 1), // boosttime
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(coolondelay), 49, 1), // coolondelay
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(cooloffdelay), 50, 1), // cooloffdelay
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(instantstart), 51, 1), // instantstart
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(coolstart), 52, 1), // coolstart
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(coolondelay), 53, 1), // coolondelay
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(cooloffdelay), 54, 1), // cooloffdelay
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(boost), 53, 1), // boost
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(boosttime), 54, 1), // boosttime
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(switchProgMode), 55, 1), // switchprogmode
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(redthreshold), 56, 1), // redthreshold
REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(solarinfl), 57, 1), // solarinfl
@@ -629,20 +637,6 @@ const std::initializer_list<Modbus::EntityModbusInfo> Modbus::modbus_register_ma
REGISTER_MAPPING(dt::WATER, TAG_TYPE_DHW, FL_(wwDiffTemp), 13, 1), // difftemp
REGISTER_MAPPING(dt::WATER, TAG_TYPE_DHW, FL_(wwRedTemp), 14, 1), // redtemp
REGISTER_MAPPING(dt::WATER, TAG_TYPE_DHW, FL_(wwRequiredTemp), 15, 1), // requiredtemp
REGISTER_MAPPING(dt::WATER, TAG_TYPE_DHW, FL_(wwStorageTemp1), 16, 1), // storagetemp1
REGISTER_MAPPING(dt::WATER, TAG_TYPE_DHW, FL_(wwColdTemp), 17, 1), // coldtemp
REGISTER_MAPPING(dt::WATER, TAG_TYPE_DHW, FL_(wwTemp5), 18, 1), // temp5
REGISTER_MAPPING(dt::WATER, TAG_TYPE_DHW, FL_(retTemp), 19, 1), // rettemp
REGISTER_MAPPING(dt::WATER, TAG_TYPE_DHW, FL_(wwHotTemp), 20, 1), // hottemp
REGISTER_MAPPING(dt::WATER, TAG_TYPE_DHW, FL_(wwDailyTemp), 21, 1), // dailytemp
REGISTER_MAPPING(dt::WATER, TAG_TYPE_DHW, FL_(wwCircTc), 22, 1), // circtc
REGISTER_MAPPING(dt::WATER, TAG_TYPE_DHW, FL_(wwKeepWarm), 23, 1), // keepwarm
REGISTER_MAPPING(dt::WATER, TAG_TYPE_DHW, FL_(wwStatus2), 24, 1), // status2
REGISTER_MAPPING(dt::WATER, TAG_TYPE_DHW, FL_(wwPumpMod), 25, 1), // pumpmod
REGISTER_MAPPING(dt::WATER, TAG_TYPE_DHW, FL_(wwFlow), 26, 1), // flow
REGISTER_MAPPING(dt::WATER, TAG_TYPE_DHW, FL_(valveReturn), 27, 1), // valvereturn
REGISTER_MAPPING(dt::WATER, TAG_TYPE_DHW, FL_(deltaTRet), 28, 1), // deltatret
REGISTER_MAPPING(dt::WATER, TAG_TYPE_DHW, FL_(errorDisp), 29, 1), // errordisp
};
} // namespace emsesp
+16 -25
View File
@@ -377,19 +377,23 @@ void Roomctrl::temperature(uint8_t addr, uint8_t dst, uint8_t hc) {
// send telegram 0x047B only for RC100H
void Roomctrl::humidity(uint8_t addr, uint8_t dst, uint8_t hc) {
int16_t dew = Helpers::calc_dew(remotetemp_[hc], remotehum_[hc]);
int8_t dew8 = EMS_VALUE_INT8_NOTSET;
if (dew != EMS_VALUE_INT16_NOTSET) {
dew8 = static_cast<int8_t>((dew >= 0 ? dew + 5 : dew - 5) / 10);
}
uint8_t data[11];
data[0] = addr | EMSbus::ems_mask();
data[1] = dst & 0x7F;
uint16_t dew = calc_dew(remotetemp_[hc], remotehum_[hc]);
data[2] = 0xFF;
data[3] = 0;
data[4] = 3;
data[5] = 0x7B + hc;
data[6] = dew == EMS_VALUE_INT16_NOTSET ? EMS_VALUE_INT8_NOTSET : (uint8_t)((dew + 5) / 10);
data[7] = remotehum_[hc];
data[8] = (uint8_t)(dew << 8);
data[9] = (uint8_t)(dew & 0xFF);
data[10] = EMSbus::calculate_crc(data, 10); // append CRC
data[0] = addr | EMSbus::ems_mask();
data[1] = dst & 0x7F;
data[2] = 0xFF;
data[3] = 0;
data[4] = 3;
data[5] = 0x7B + hc;
data[6] = static_cast<uint8_t>(dew8);
data[7] = remotehum_[hc];
data[8] = static_cast<uint8_t>(dew >> 8);
data[9] = static_cast<uint8_t>(dew & 0x00FF);
data[10] = EMSbus::calculate_crc(data, 10); // append CRC
EMSuart::transmit(data, 11);
}
@@ -436,17 +440,4 @@ void Roomctrl::replyF7(uint8_t addr, uint8_t dst, uint8_t offset, uint8_t typehh
EMSuart::transmit(data, 10);
}
int16_t Roomctrl::calc_dew(int16_t temp, uint8_t humi) {
if (humi == EMS_VALUE_UINT8_NOTSET || temp == EMS_VALUE_INT16_NOTSET) {
return EMS_VALUE_INT16_NOTSET;
}
const float k2 = 17.62;
const float k3 = 243.12;
const float t = (float)temp / 10;
const float h = (float)humi / 100;
int16_t dt = (10 * k3 * (((k2 * t) / (k3 + t)) + log(h)) / (((k2 * k3) / (k3 + t)) - log(h)));
return dt;
}
} // namespace emsesp
-1
View File
@@ -50,7 +50,6 @@ class Roomctrl {
static void nack_write();
static void ack_write();
static void replyF7(uint8_t addr, uint8_t dst, uint8_t offset, uint8_t typehh, uint8_t typeh, uint8_t typel, uint8_t hc);
static int16_t calc_dew(int16_t temp, uint8_t hum);
static bool switch_off_[HCS];
static uint32_t send_time_[HCS];
+59 -55
View File
@@ -39,17 +39,8 @@ std::deque<Token> exprToTokens(const std::string & expr) {
i += (*p == '{') ? 1 : (*p == '}') ? -1 : 0;
++p;
}
if (*p) {
++p;
}
// Use string_view to avoid unnecessary string copies
std::string_view s(b, p - b);
auto n = s.find("\"\"");
while (n != std::string_view::npos) {
s.remove_prefix(n + 2);
n = s.find("\"\"");
}
tokens.emplace_back(Token::Type::String, std::string(s), -3);
const auto s = std::string(b, p);
tokens.emplace_back(Token::Type::String, s, -3);
if (*p == '\0') {
--p;
}
@@ -333,7 +324,10 @@ std::deque<Token> shuntingYard(const std::deque<Token> & tokens) {
// check if string is a number
bool isnum(const std::string & s) {
if (!s.empty() && (s.find_first_not_of("0123456789.") == std::string::npos || (s[0] == '-' && s.find_first_not_of("0123456789.", 1) == std::string::npos))) {
if (s.empty() || s.find_first_of("0123456789") == std::string::npos) {
return false;
}
if (s.find_first_not_of("0123456789.") == std::string::npos || (s[0] == '-' && s.find_first_not_of("0123456789.", 1) == std::string::npos)) {
return true;
}
return false;
@@ -636,6 +630,10 @@ std::string calculate(const std::string & expr) {
stack.push_back(lhs + rhs);
break;
}
if (!isnum(rhs) || !isnum(lhs)) {
stack.push_back(lhs + token.str + rhs);
break;
}
auto lhd = std::stod(lhs);
auto rhd = std::stod(rhs);
switch (token.str[0]) {
@@ -700,8 +698,7 @@ std::string compute(const std::string & expr) {
std::string cmd = expr_new.substr(f, e - f).c_str();
JsonDocument doc;
if (DeserializationError::Ok == deserializeJson(doc, cmd)) {
HTTPClient * http = new HTTPClient;
std::string url, header_s, value_s, method_s, key_s, keys_s;
std::string url, header_s, value_s, method_s, key_s, keys_s;
// search keys lower case
for (JsonPair p : doc.as<JsonObject>()) {
if (Helpers::toLower(p.key().c_str()) == "url") {
@@ -720,56 +717,63 @@ std::string compute(const std::string & expr) {
keys_s = p.key().c_str();
}
}
if (http->begin(url.c_str())) {
int httpResult = 0;
for (JsonPair p : doc[header_s].as<JsonObject>()) {
http->addHeader(p.key().c_str(), p.value().as<std::string>().c_str());
}
std::string value = doc[value_s] | "";
std::string method = doc[method_s] | "get";
// if there is data, force a POST
if (value.length() || Helpers::toLower(method) == "post") {
if (value.find_first_of('{') != std::string::npos) {
http->addHeader(asyncsrv::T_Content_Type, asyncsrv::T_application_json, false); // auto-set to JSON
if (url.substr(0, 4) == "http") { // match http and https
HTTPClient * http = new HTTPClient;
#ifndef EMSESP_STANDALONE
http->setConnectTimeout(10000);
http->setTimeout(10000);
#endif
if (http->begin(url.c_str())) {
int httpResult = 0;
for (JsonPair p : doc[header_s].as<JsonObject>()) {
http->addHeader(p.key().c_str(), p.value().as<std::string>().c_str());
}
httpResult = http->POST(value.c_str());
} else {
httpResult = http->GET(); // normal GET
}
std::string value = doc[value_s] | "";
std::string method = doc[method_s] | "get";
if (httpResult > 0) {
std::string result = http->getString().c_str();
std::string key = doc[key_s] | "";
JsonDocument keys_doc; // JsonDocument to hold "keys" after doc is parsed with HTTP body
if (doc[keys_s].is<JsonArray>()) {
keys_doc.set(doc[keys_s].as<JsonArray>());
// if there is data, force a POST
if (value.length() || Helpers::toLower(method) == "post") {
if (value.find_first_of('{') != std::string::npos) {
http->addHeader(asyncsrv::T_Content_Type, asyncsrv::T_application_json, false); // auto-set to JSON
}
httpResult = http->POST(value.c_str());
} else {
httpResult = http->GET(); // normal GET
}
JsonArray keys = keys_doc.as<JsonArray>();
if (key.length() || !keys.isNull()) {
doc.clear();
if (DeserializationError::Ok == deserializeJson(doc, result)) {
if (key.length()) {
result = doc[key.c_str()].as<std::string>();
} else {
JsonVariant json = doc.as<JsonVariant>();
for (JsonVariant keys_key : keys) {
if (keys_key.is<std::string>() && json.is<JsonObject>()) {
json = json[keys_key.as<std::string>()].as<JsonVariant>();
} else if (keys_key.is<int>() && json.is<JsonArray>()) {
json = json[keys_key.as<int>()].as<JsonVariant>();
} else {
break; // type mismatch
if (httpResult > 0) {
std::string result = http->getString().c_str();
std::string key = doc[key_s] | "";
JsonDocument keys_doc; // JsonDocument to hold "keys" after doc is parsed with HTTP body
if (doc[keys_s].is<JsonArray>()) {
keys_doc.set(doc[keys_s].as<JsonArray>());
}
JsonArray keys = keys_doc.as<JsonArray>();
if (key.length() || !keys.isNull()) {
doc.clear();
if (DeserializationError::Ok == deserializeJson(doc, result)) {
if (key.length()) {
result = doc[key.c_str()].as<std::string>();
} else {
JsonVariant json = doc.as<JsonVariant>();
for (JsonVariant keys_key : keys) {
if (keys_key.is<std::string>() && json.is<JsonObject>()) {
json = json[keys_key.as<std::string>()].as<JsonVariant>();
} else if (keys_key.is<int>() && json.is<JsonArray>()) {
json = json[keys_key.as<int>()].as<JsonVariant>();
} else {
break; // type mismatch
}
}
result = json.as<std::string>();
}
result = json.as<std::string>();
}
}
expr_new.replace(f, e - f, result.c_str());
}
expr_new.replace(f, e - f, result.c_str());
http->end();
}
http->end();
delete http;
}
}
@@ -821,4 +825,4 @@ std::string compute(const std::string & expr) {
return calculate(expr_new);
}
} // namespace emsesp
} // namespace emsesp
+9 -19
View File
@@ -30,7 +30,7 @@
#include <HTTPClient.h>
#include <map>
#include "shuntingYard.h" // for compute() used by the message
#include "EMSESP_Version.h"
#if defined(EMSESP_TEST)
@@ -61,7 +61,7 @@ const char * const languages[] = {EMSESP_LOCALE_EN,
EMSESP_LOCALE_TR,
EMSESP_LOCALE_IT,
EMSESP_LOCALE_SK,
EMSESP_LOCALE_CZ};
EMSESP_LOCALE_CS};
#endif
static constexpr uint8_t NUM_LANGUAGES = sizeof(languages) / sizeof(const char *);
@@ -222,22 +222,10 @@ bool System::command_message(const char * value, const int8_t id, JsonObject out
LOG_WARNING("Message is empty");
return false; // must have a string value
}
EMSESP::webSchedulerService.computed_value.clear();
EMSESP::webSchedulerService.raw_value = value;
for (uint16_t wait = 0; wait < 2000 && !EMSESP::webSchedulerService.raw_value.empty(); wait++) {
delay(1);
}
if (EMSESP::webSchedulerService.computed_value.empty()) {
LOG_WARNING("Message result is empty");
return false;
}
LOG_INFO("Message: %s", EMSESP::webSchedulerService.computed_value.c_str()); // send to log
Mqtt::queue_publish(F_(message), EMSESP::webSchedulerService.computed_value); // send to MQTT if enabled
output["api_data"] = EMSESP::webSchedulerService.computed_value; // send to API
std::string computed_value = compute(value);
LOG_INFO("Message: %s", computed_value.c_str()); // send to log
Mqtt::queue_publish(F_(message), computed_value); // send to MQTT if enabled
output["api_data"] = computed_value; // send to API
return true;
}
@@ -1964,7 +1952,9 @@ bool System::get_value_info(JsonObject output, const char * cmd) {
LOG_ERROR("empty system command");
return false;
}
if (!strcmp(cmd, "restart")) { // restart is a command, not an entity
return false;
}
// check for hardcoded "info"/"value"
if (!strcmp(cmd, F_(info)) || !strcmp(cmd, F_(values))) {
return command_info("", 0, output);
+7 -7
View File
@@ -161,9 +161,9 @@ void RxService::add(uint8_t * data, uint8_t length) {
}
if (data[0] != EMSuart::last_tx_src()) { // do not count echos as errors
telegram_error_count_++;
LOG_WARNING("Incomplete Rx: %s", Helpers::data_to_hex(data, length).c_str()); // include CRC
LOG_WARNING("Incomplete Rx: %s (crc: %02X)", Helpers::data_to_hex(data, length).c_str(), crc); // include CRC
} else {
LOG_TRACE("Incomplete Rx: %s", Helpers::data_to_hex(data, length).c_str()); // include CRC
LOG_TRACE("Incomplete Rx: %s (crc: %02X)", Helpers::data_to_hex(data, length).c_str(), crc); // include CRC
}
return;
}
@@ -276,6 +276,7 @@ void TxService::start() {
// sends a 1 byte poll which is our own deviceID
void TxService::send_poll() const {
// LOG_DEBUG("Ack %02X",ems_bus_id() ^ ems_mask());
// if (tx_mode() != EMS_TXMODE_OFF && ems_bus_id() != 0x0D) { // use 0x0D for tests without poll-Ack
if (tx_mode() != EMS_TXMODE_OFF) {
EMSuart::send_poll(ems_bus_id() ^ ems_mask());
}
@@ -572,12 +573,11 @@ bool TxService::send_raw(const char * telegram_data) {
return false;
}
// since the telegram data is a const, make a copy. add 1 to grab the \0 EOS
char telegram[strlen(telegram_data) + 1];
strlcpy(telegram, telegram_data, sizeof(telegram));
// since the telegram data is a const, make a copy
char * telegram = strdup(telegram_data);
uint8_t count = 0;
uint8_t data[2 + strlen(telegram) / 3];
uint8_t data[256]; // max raw telegram length
// get values
char * p = strtok(telegram, " ,"); // delimiter
@@ -585,7 +585,7 @@ bool TxService::send_raw(const char * telegram_data) {
data[count++] = (uint8_t)strtol(p, 0, 16);
p = strtok(nullptr, " ,");
}
free(telegram);
// check valid length
if (count < 4) {
return false;
+95 -16
View File
@@ -45,7 +45,8 @@ Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
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));
register_telegram_type(0x0B65, "Roomschedule", true, MAKE_PF_CB(process_roomSchedule));
register_telegram_type(0xF7, "MenuConfig", false, MAKE_PF_CB(process_roomConfig));
// 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)
@@ -88,11 +89,12 @@ void Connect::process_RCTime(std::shared_ptr<const Telegram> telegram) {
void Connect::register_device_values_room(std::shared_ptr<Connect::RoomCircuit> room) {
auto tag = DeviceValueTAG::TAG_SRC1 + 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->humidity_, DeviceValueType::UINT8, 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_(selRoomTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_seltemp), 5, 30);
register_device_value(tag, &room->mode_, DeviceValueType::ENUM, FL_(enum_mode2), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode));
// register_device_value(tag, &room->coolmode_, DeviceValueType::BOOL, FL_(coolingOn), DeviceValueUOM::NONE, MAKE_CF_CB(set_coolmode));
register_device_value(tag, &room->name_, DeviceValueType::STRING, FL_(name), DeviceValueUOM::NONE, MAKE_CF_CB(set_name));
register_device_value(tag, &room->childlock_, DeviceValueType::BOOL, FL_(childlock), DeviceValueUOM::NONE, MAKE_CF_CB(set_childlock));
register_device_value(tag, &room->icon_, DeviceValueType::ENUM, FL_(enum_icons), FL_(icon), DeviceValueUOM::NONE, MAKE_CF_CB(set_icon));
@@ -133,16 +135,8 @@ void Connect::process_roomThermostat(std::shared_ptr<const Telegram> telegram) {
if (!Mqtt::ha_enabled() || (Helpers::hasValue(rc->mode_) && Helpers::hasValue(rc->icon_))) {
has_update(telegram, rc->seltemp_, 3);
}
// calculate dew temperature
if (rc->humidity_ >= 0 && rc->humidity_ <= 100) {
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);
}
has_update(rc->dewtemp_, Helpers::calc_dew(rc->temp_, rc->humidity_));
}
// gateway(0x48) W gateway(0x50), ?(0x0B42), data: 01 // icon in offset 0
@@ -168,11 +162,33 @@ void Connect::process_roomThermostatSettings(std::shared_ptr<const Telegram> tel
if (rc == nullptr) {
return;
}
has_enumupdate(telegram, rc->mode_, 0, {3, 1, 0}); // modes off, manual auto
// has_enumupdate(telegram, rc->mode_, 0, {3, 1, 0}); // modes off, manual auto
// has_update(telegram, rc->mode_, 0); // modes: auto, heat, cool, off
// has_update(telegram, rc->tempautotemp_, 1); // FF means off
// has_update(telegram, rc->manualtemp_, 3);
// has_update(telegram, rc->coolmode_, 4); // 01-cooling, 00-heating
// has_update(telegram, rc->coolautotemp_, 5);
// has_update(telegram, rc->cooltemp_, 6);
has_update(telegram, rc->childlock_, 7);
uint8_t mh = 0xFF;
uint8_t mc = 0xFF;
telegram->read_value(mh, 0);
telegram->read_value(mc, 4);
if (mc == 3) {
rc->coolmode_ = 1;
rc->mode_ = 0;
} else if (mh == 3) {
rc->coolmode_ = 0;
rc->mode_ = 0;
} else if (mc == 1) {
rc->coolmode_ = 1;
rc->mode_ = 1;
} else if (mh == 1) {
rc->coolmode_ = 0;
rc->mode_ = 1;
} else {
rc->mode_ = 2;
}
}
// unknown telegrams, needs fetch
@@ -203,6 +219,53 @@ void Connect::process_roomSchedule(std::shared_ptr<const Telegram> telegram) {
toggle_fetch(telegram->type_id, false); // fetch only once if all is initialized
}
void Connect::process_roomConfig(std::shared_ptr<const Telegram> telegram) {
if (telegram->offset == 0 && telegram->message_length > 3) {
uint16_t t = 0;
telegram->read_value(t, 1);
if (t >= 0x0AB5 && t <= 0x0AB5 + 15) {
auto rc = room_circuit(t - 0x0AB5);
if (rc == nullptr) {
return;
}
uint8_t bits = telegram->message_data[3];
if (rc->mode_ == 2) {
if (bits & 0x02) {
rc->coolmode_ = 0;
} else if (bits & 0x20) {
rc->coolmode_ = 1;
}
}
/*
switch (bits & 0x6A) {
case 0: // cooling off
rc->mode_ = 0;
rc->coolmode_ = 1;
break;
case 0x40: // manual cooling or heating off
rc->mode_ = 1;
rc->coolmode_ = 1;
break;
case 0x20: // auto cooling
rc->mode_ = 2;
rc->coolmode_ = 1;
break;
case 0x48: // manual heating
case 0x08: // manual heating
rc->mode_ = 1;
rc->coolmode_ = 0;
break;
case 0x42: // auto heating
case 0x02: // auto heating
rc->mode_ = 2;
rc->coolmode_ = 0;
break;
}
*/
}
}
}
// Settings:
bool Connect::set_mode(const char * value, const int8_t id) {
@@ -216,7 +279,7 @@ bool Connect::set_mode(const char * value, const int8_t id) {
return false;
}
}
write_command(0xBB5 + rc->room(), 0, v); // no validate, mode change is broadcasted
write_command(0xBB5 + rc->room(), rc->coolmode_ == 1 ? 4 : 0, v); // no validate, mode change is broadcasted
return true;
}
@@ -226,10 +289,13 @@ bool Connect::set_seltemp(const char * value, const int8_t id) {
return false;
}
float v;
if (Helpers::value2float(value, v)) {
if (Helpers::value2temperature(value, v)) {
// depends on mode, auto (2 for enum_mode2, 0 for enum_mode8) set in offset 1
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));
if (rc->coolmode_ == 1) {
write_command(0xBB5 + rc->room(), rc->mode_ == 2 ? 5 : 6, v == -1 ? 0xFF : uint8_t(v * 2));
} else {
write_command(0xBB5 + rc->room(), rc->mode_ == 2 ? 1 : 3, v == -1 ? 0xFF : uint8_t(v * 2));
}
return true;
}
return false;
@@ -257,6 +323,19 @@ bool Connect::set_name(const char * value, const int8_t id) {
return true;
}
bool Connect::set_coolmode(const char * value, const int8_t id) {
auto rc = room_circuit(id - DeviceValueTAG::TAG_SRC1);
if (rc == nullptr) {
return false;
}
bool b;
if (Helpers::value2bool(value, b)) {
write_command(0xBB5 + rc->room(), 4, b ? 1 : 0, 0xBB5 + rc->room());
return true;
}
return false;
}
bool Connect::set_childlock(const char * value, const int8_t id) {
auto rc = room_circuit(id - DeviceValueTAG::TAG_SRC1);
if (rc == nullptr) {
+6 -1
View File
@@ -34,15 +34,18 @@ class Connect : public EMSdevice {
}
~RoomCircuit() = default;
int16_t temp_;
int8_t humidity_;
uint8_t humidity_;
uint8_t seltemp_;
uint8_t mode_;
char name_[51];
int16_t dewtemp_;
uint8_t childlock_;
uint8_t icon_;
uint8_t coolmode_;
// uint8_t tempautotemp_;
// uint8_t manualtemp_;
// uint8_t coolautotemp_;
// uint8_t cooltemp_;
uint8_t room() {
return room_;
@@ -62,9 +65,11 @@ class Connect : public EMSdevice {
void process_roomThermostatParam(std::shared_ptr<const Telegram> telegram);
void process_roomThermostatData(std::shared_ptr<const Telegram> telegram);
void process_roomSchedule(std::shared_ptr<const Telegram> telegram);
void process_roomConfig(std::shared_ptr<const Telegram> 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);
bool set_coolmode(const char * value, const int8_t id);
bool set_childlock(const char * value, const int8_t id);
bool set_icon(const char * value, const int8_t id);
+264 -90
View File
@@ -175,7 +175,9 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
register_telegram_type(set_typeids[i], "RC300Set", false, MAKE_PF_CB(process_RC300Set), 29);
register_telegram_type(summer_typeids[i], "RC300Summer", false, MAKE_PF_CB(process_RC300Summer), 14);
register_telegram_type(curve_typeids[i], "RC300Curves", false, MAKE_PF_CB(process_RC300Curve), 9);
register_telegram_type(summer2_typeids[i], "RC300Summer2", false, MAKE_PF_CB(process_RC300Summer2), 8);
if (model != EMSdevice::EMS_DEVICE_FLAG_UI800) {
register_telegram_type(summer2_typeids[i], "RC300Sumr2", false, MAKE_PF_CB(process_RC300Summer2), 8);
}
}
const size_t set2_size = set2_typeids.size();
for (uint8_t i = 0; i < set2_size; i++) {
@@ -207,7 +209,9 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
register_telegram_type(0x16E, "Absent", true, MAKE_PF_CB(process_Absent), 1);
register_telegram_type(0xBF, "ErrorMessage", false, MAKE_PF_CB(process_ErrorMessageBF));
register_telegram_type(0xC0, "RCErrorMessage", false, MAKE_PF_CB(process_RCErrorMessage2));
register_telegram_type(0x470, "RC300Summer2", true, MAKE_PF_CB(process_RC300Summer2), 8);
if (model == EMSdevice::EMS_DEVICE_FLAG_UI800) {
register_telegram_type(0x470, "RC300Summer3", true, MAKE_PF_CB(process_RC300Summer3), 8);
}
EMSESP::send_read_request(0xC0, device_id, 0, 20); // read last errorcode on start (only published on errors)
// JUNKERS/HT3
@@ -543,7 +547,7 @@ uint8_t Thermostat::HeatingCircuit::get_mode() const {
} else {
return HeatingCircuit::Mode::OFF;
}
} else if (model == EMSdevice::EMS_DEVICE_FLAG_BC400 || model == EMSdevice::EMS_DEVICE_FLAG_CR120) {
} else if (model == EMSdevice::EMS_DEVICE_FLAG_BC400 || model == EMSdevice::EMS_DEVICE_FLAG_UI800 || model == EMSdevice::EMS_DEVICE_FLAG_CR120) {
if (mode_new == 0) {
return HeatingCircuit::Mode::OFF;
} else if (mode_new == 1) {
@@ -607,7 +611,7 @@ uint8_t Thermostat::HeatingCircuit::get_mode_type() const {
return HeatingCircuit::Mode::ON;
}
} else if (model == EMSdevice::EMS_DEVICE_FLAG_RC300 || model == EMSdevice::EMS_DEVICE_FLAG_R3000 || model == EMSdevice::EMS_DEVICE_FLAG_BC400
|| model == EMSdevice::EMS_DEVICE_FLAG_HMC310) {
|| model == EMSdevice::EMS_DEVICE_FLAG_UI800 || model == EMSdevice::EMS_DEVICE_FLAG_HMC310) {
if (modetype == 0) {
return HeatingCircuit::Mode::ECO;
} else if (modetype == 1) {
@@ -806,16 +810,16 @@ void Thermostat::process_RemoteTemp(std::shared_ptr<const Telegram> telegram) {
// e.g. "38 10 FF 00 03 7B 08 24 00 4B"
void Thermostat::process_RemoteHumidity(std::shared_ptr<const Telegram> telegram) {
// has_update(telegram, dewtemperature_, 0); // this is int8
has_update(telegram, humidity_, 1);
has_update(telegram, dewtemperature_, 2); // this is int16
// some thermostats use short telegram with int8 dewpoint, https://github.com/emsesp/EMS-ESP32/issues/1491
if (telegram->offset == 0 && telegram->message_length < 4) {
int8_t dew = dewtemperature_ / 10;
telegram->read_value(dew, 0);
if (dew != EMS_VALUE_INT8_NOTSET && dewtemperature_ != dew * 10) {
dewtemperature_ = dew * 10;
has_update(dewtemperature_);
}
// but it's not a dewpoint in offset 0, removed, see https://github.com/emsesp/EMS-ESP32/issues/3135
has_update(telegram, humidity_, 1);
// has_update(telegram, dewtemperature_, 2); // this is int16
int16_t dew = EMS_VALUE_INT16_NOTSET;
if (telegram->read_value(dew, 2)) {
has_update(dewtemperature_, dew);
} else if (telegram->offset == 0 && telegram->message_length < 4) {
dew = Helpers::calc_dew(tempsensor1_, humidity_);
has_update(dewtemperature_, dew);
}
}
@@ -1264,18 +1268,25 @@ void Thermostat::process_RC300Summer(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, hc->comfortPointTemp, 12);
}
// type 0x470, only BC400/UI800
// (0x470), data: 01 0E 01 01 02 17 04 48
void Thermostat::process_RC300Summer3(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, hpoperatingmode, 0);
has_update(telegram, summertemp, 1);
has_update(telegram, heatondelay, 2);
has_update(telegram, heatoffdelay, 3);
has_update(telegram, instantstart, 4);
has_update(telegram, coolstart, 5);
has_update(telegram, coolondelay, 6);
has_update(telegram, cooloffdelay, 7);
}
// types 0x471 ff summer2_typeids
// (0x473), data: 00 11 04 01 01 1C 08 04
void Thermostat::process_RC300Summer2(std::shared_ptr<const Telegram> telegram) {
auto hc = heating_circuit(telegram);
if (hc == nullptr) {
// telegram 0x470 see https://github.com/emsesp/EMS-ESP32/issues/2686
if (telegram->type_id == 0x470 && telegram->message_length > 2) {
hc = heating_circuit(1);
summer2_typeids[0] = 0x470;
} else {
return;
}
return;
}
if (hc->statusbyte & 1) {
has_update(telegram, hc->summersetmode, 0);
@@ -1334,7 +1345,7 @@ void Thermostat::process_RC300WWmode(std::shared_ptr<const Telegram> telegram) {
// circulation pump see: https://github.com/Th3M3/buderus_ems-wiki/blob/master/Einstellungen%20der%20Bedieneinheit%20RC310.md
has_update(telegram, dhw->wwCircPump_, 1); // FF=off, 0=on ?
if (model() == EMSdevice::EMS_DEVICE_FLAG_BC400 || model() == EMSdevice::EMS_DEVICE_FLAG_HMC310) {
if (model() == EMSdevice::EMS_DEVICE_FLAG_BC400 || model() == EMSdevice::EMS_DEVICE_FLAG_UI800 || model() == EMSdevice::EMS_DEVICE_FLAG_HMC310) {
has_enumupdate(telegram, dhw->wwMode_, 2, {0, 5, 1, 2, 4});
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_R3000) {
// https://github.com/emsesp/EMS-ESP32/pull/1722#discussion_r1582823521
@@ -1455,14 +1466,21 @@ void Thermostat::process_HPMode(std::shared_ptr<const Telegram> telegram) {
}
// 0x467 ff HP settings
// thermostat(0x10) -W-> Me(0x0B), ?(0x0467), data: 0A 15 00 01 04 (from #1187)
// thermostat(0x10) W me(0x0B), HPSet(0x0467), data: 14 13 02 01 02 (from #3144)
void Thermostat::process_HPSet(std::shared_ptr<const Telegram> telegram) {
auto hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
has_update(telegram, hc->dewoffset, 4); // 7-35°C
has_update(telegram, hc->roomtempdiff, 3); // 1-10K
has_update(telegram, hc->hpminflowtemp, 0); // 2-10K
if (hc->control == 3 || hc->control == 6) { // control with humidity
has_update(telegram, hc->hpminflowtemp, 0); // 7-35°C
} else {
has_update(telegram, hc->hpminflowtemp, 1); // 7-35°C
}
// has_update(telegram, hc->roomtempoffset, 2); // 1-10K // not implemented, mentioned in #3144
has_update(telegram, hc->roomtempdiff, 3); // 1-10K
has_update(telegram, hc->dewoffset, 4); // 2-10K
}
// type 0x41 - data from the RC30 thermostat(0x10) - 14 bytes long
@@ -1720,9 +1738,18 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
}
// check clock
time_t now = time(nullptr);
tm * tm_ = localtime(&now);
bool tset_ = tm_->tm_year > 110; // year 2010 and up, time is valid
time_t now = time(nullptr);
bool sync = false;
// change to thermostat timezone if different
EMSESP::esp32React.getNTPSettingsService()->read([&](const NTPSettings & settings) {
sync = settings.thermostat_sync != 0;
if (settings.thermostat_sync == 2) {
setenv("TZ", settings.tzFormatT.c_str(), 1);
tzset();
}
});
tm * tm_ = localtime(&now);
bool tset_ = tm_->tm_year > 110; // year 2010 and up, time is valid
tm_->tm_year = (telegram->message_data[0] & 0x7F) + 100; // IVT
tm_->tm_mon = telegram->message_data[1] - 1;
tm_->tm_mday = telegram->message_data[3];
@@ -1738,20 +1765,27 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
strftime(newdatetime, sizeof(dateTime_), "%d.%m.%Y %H:%M", tm_);
has_update(dateTime_, newdatetime, sizeof(dateTime_));
bool ivtclock = (telegram->message_data[0] & 0x80) == 0x80; // dont sync ivt-clock, #439
bool junkersclock = model() == EMSdevice::EMS_DEVICE_FLAG_JUNKERS;
time_t ttime = mktime(tm_); // thermostat time
bool ivtclock = (telegram->message_data[0] & 0x80) == 0x80; // dont sync ivt-clock, #439
tm_->tm_isdst = -1; // determine dst
time_t ttime = mktime(tm_); // make thermostat time
// change back emsesp timezone
EMSESP::esp32React.getNTPSettingsService()->read([&](const NTPSettings & settings) {
if (settings.thermostat_sync == 2) {
setenv("TZ", settings.tzFormat.c_str(), 1);
tzset();
}
});
// correct thermostat clock if we have valid ntp time, and could write the command
if (!ivtclock && !junkersclock && tset_ && EMSESP::system_.ntp_connected() && !EMSESP::system_.readonly_mode() && has_command(&dateTime_)) {
double difference = difftime(now, ttime);
if (sync && !ivtclock && tset_ && EMSESP::system_.ntp_connected() && !EMSESP::system_.readonly_mode() && has_command(&dateTime_)) {
int difference = difftime(now, ttime);
if (difference > 15 || difference < -15) {
if (setTimeRetry < 3) {
if (!use_dst) {
set_datetime("ntp", 0); // set from NTP without dst
LOG_INFO("thermostat time correction from ntp, ignoring dst");
LOG_INFO("thermostat time correction %d seconds from ntp, ignoring dst", difference);
} else {
set_datetime("ntp", -1); // set from NTP
LOG_INFO("thermostat time correction from ntp");
LOG_INFO("thermostat time correction %d seconds from ntp", difference);
}
setTimeRetry++;
}
@@ -1761,10 +1795,6 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
}
#ifndef EMSESP_STANDALONE
if (!tset_ && tm_->tm_year > 110) { // emsesp clock not set, but thermostat clock
if (ivtclock) {
tm_->tm_isdst = -1; // determine dst
ttime = mktime(tm_); // thermostat time
}
struct timeval newnow = {.tv_sec = ttime, .tv_usec = 0};
#if CONFIG_IDF_TARGET_ESP32C3
// unknown how to set time on C3
@@ -1915,7 +1945,11 @@ bool Thermostat::set_hpminflowtemp(const char * value, const int8_t id) {
}
int v;
if (Helpers::value2temperature(value, v)) {
write_command(hp_typeids[hc->hc()], 0, v, hp_typeids[hc->hc()]);
if (hc->control == 3 || hc->control == 6) { // remotes with humidity
write_command(hp_typeids[hc->hc()], 0, v, hp_typeids[hc->hc()]);
} else {
write_command(hp_typeids[hc->hc()], 1, v, hp_typeids[hc->hc()]);
}
return true;
}
return false;
@@ -2355,7 +2389,7 @@ bool Thermostat::set_control(const char * value, const int8_t id) {
}
// BC400
// 1-RC100, 2-RC100H, 3-RC200
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_BC400) {
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_BC400 || model() == EMSdevice::EMS_DEVICE_FLAG_UI800) {
if (Helpers::value2enum(value, ctrl, FL_(enum_control2))) {
write_command(hpmode_typeids[hc->hc()], 3, ctrl);
hc->control = ctrl; // set in advance, dont wait for verify
@@ -2435,7 +2469,7 @@ bool Thermostat::set_wwmode(const char * value, const int8_t id) {
return false;
}
write_command(0xB0, 2, set, 0xB0);
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_BC400 || model() == EMSdevice::EMS_DEVICE_FLAG_HMC310) {
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_BC400 || model() == EMSdevice::EMS_DEVICE_FLAG_UI800 || model() == EMSdevice::EMS_DEVICE_FLAG_HMC310) {
if (!Helpers::value2enum(value, set, FL_(enum_wwMode4), {0, 5, 1, 2, 4})) { // off, eco+, eco, comfort, auto
return false;
}
@@ -2590,30 +2624,36 @@ bool Thermostat::set_cooling(const char * value, const int8_t id) {
// set cooling delays
bool Thermostat::set_coolondelay(const char * value, const int8_t id) {
float f;
if (!Helpers::value2float(value, f)) {
return false;
}
if (model() == EMSdevice::EMS_DEVICE_FLAG_UI800) {
write_command(0x470, 6, (uint8_t)(f * 4), 0x470);
return true;
}
auto hc = heating_circuit(id);
if (hc == nullptr) {
return false;
}
int v;
if (!Helpers::value2number(value, v)) {
return false;
}
write_command(summer2_typeids[hc->hc()], 6, v, summer2_typeids[hc->hc()]);
write_command(summer2_typeids[hc->hc()], 6, (uint8_t)f, summer2_typeids[hc->hc()]);
return true;
}
bool Thermostat::set_cooloffdelay(const char * value, const int8_t id) {
float f;
if (!Helpers::value2float(value, f)) {
return false;
}
if (model() == EMSdevice::EMS_DEVICE_FLAG_UI800) {
write_command(0x470, 7, (uint8_t)(f * 4), 0x470);
return true;
}
auto hc = heating_circuit(id);
if (hc == nullptr) {
return false;
}
int v;
if (!Helpers::value2number(value, v)) {
return false;
}
write_command(summer2_typeids[hc->hc()], 7, v, summer2_typeids[hc->hc()]);
write_command(summer2_typeids[hc->hc()], 7, (uint8_t)f, summer2_typeids[hc->hc()]);
return true;
}
@@ -3038,6 +3078,15 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) {
if (dt == "ntp") {
time_t now = time(nullptr);
tm * tm_ = localtime(&now);
EMSESP::esp32React.getNTPSettingsService()->read([&](const NTPSettings & settings) {
if (settings.thermostat_sync == 2) {
setenv("TZ", settings.tzFormatT.c_str(), 1);
tzset();
tm_ = localtime(&now);
setenv("TZ", settings.tzFormat.c_str(), 1);
tzset();
}
});
if (tm_->tm_year < 110) { // no valid time
return false;
}
@@ -3053,11 +3102,7 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) {
data[5] = tm_->tm_sec;
data[6] = (tm_->tm_wday + 6) % 7; // Bosch counts from Mo, time from Su
data[7] = (id == 0) ? 2 : tm_->tm_isdst + 2; // set DST and flag for ext. clock
if (model() == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
data[6]++; // Junkers use 1-7;
data[7] = 0;
}
} else if (dt.length() == 23) {
} else if (dt.length() == 23 || dt.length() == 21) {
data[0] = (dt[7] - '0') * 100 + (dt[8] - '0') * 10 + (dt[9] - '0'); // year
data[1] = (dt[3] - '0') * 10 + (dt[4] - '0'); // month
data[2] = (dt[11] - '0') * 10 + (dt[12] - '0'); // hour
@@ -3065,10 +3110,35 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) {
data[4] = (dt[14] - '0') * 10 + (dt[15] - '0'); // min
data[5] = (dt[17] - '0') * 10 + (dt[18] - '0'); // sec
data[6] = (dt[20] - '0'); // day of week, Mo:0
data[7] = (dt[22] - '0') + 2; // DST and flag
if (model() == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
data[7] = 0;
}
data[7] = dt.length() == 21 ? 2 : (dt[22] - '0') + 2; // DST and flag
} else if (dt.length() == 19 || dt.length() == 16) {
time_t now = time(nullptr);
tm * tm_ = localtime(&now);
data[0] = (dt[7] - '0') * 100 + (dt[8] - '0') * 10 + (dt[9] - '0'); // year
tm_->tm_year = 100 + data[0];
data[1] = (dt[3] - '0') * 10 + (dt[4] - '0'); // month
tm_->tm_mon = data[1] - 1;
tm_->tm_hour = data[2] = (dt[11] - '0') * 10 + (dt[12] - '0'); // hour
tm_->tm_mday = data[3] = (dt[0] - '0') * 10 + (dt[1] - '0'); // day
tm_->tm_min = data[4] = (dt[14] - '0') * 10 + (dt[15] - '0'); // min
tm_->tm_sec = data[5] = dt.length() == 16 ? 0 : (dt[17] - '0') * 10 + (dt[18] - '0'); // sec
tm_->tm_isdst = -1;
// use thermostat time zone to determine day of week and daylight saving
EMSESP::esp32React.getNTPSettingsService()->read([&](const NTPSettings & settings) {
if (settings.thermostat_sync == 2) {
setenv("TZ", settings.tzFormatT.c_str(), 1);
tzset();
auto t = mktime(tm_);
tm_ = localtime(&t);
setenv("TZ", settings.tzFormat.c_str(), 1);
tzset();
} else {
auto t = mktime(tm_);
tm_ = localtime(&t);
}
});
data[6] = (tm_->tm_wday + 6) % 7; // Bosch counts from Mo, time from Su
data[7] = tm_->tm_isdst + 2; // set DST and flag for ext. clock
} else {
LOG_WARNING("Set date: invalid data, wrong length");
return false;
@@ -3078,6 +3148,11 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) {
return false;
}
if (model() == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
data[6]++; // Junkers use 1-7 for day of the week
data[7] = 0; // no dst setting
}
// LOG_INFO("Setting date and time: %02d.%02d.2%03d-%02d:%02d:%02d-%d-%d", data[3], data[1], data[0], data[2], data[4], data[5], data[6], data[7]);
write_command(EMS_TYPE_time, 0, data, 8, EMS_TYPE_time);
@@ -3142,6 +3217,7 @@ bool Thermostat::set_mode(const char * value, const int8_t id) {
mode_list = FL_(enum_mode3);
break;
case EMSdevice::EMS_DEVICE_FLAG_BC400:
case EMSdevice::EMS_DEVICE_FLAG_UI800:
case EMSdevice::EMS_DEVICE_FLAG_CR120:
mode_list = FL_(enum_mode2);
break;
@@ -3279,6 +3355,7 @@ bool Thermostat::set_mode_n(const uint8_t mode, const int8_t id) {
offset = EMS_OFFSET_RC35Set_mode;
break;
case EMSdevice::EMS_DEVICE_FLAG_BC400:
case EMSdevice::EMS_DEVICE_FLAG_UI800:
case EMSdevice::EMS_DEVICE_FLAG_CR120:
offset = EMS_OFFSET_RCPLUSSet_mode_new;
break;
@@ -3318,7 +3395,7 @@ bool Thermostat::set_mode_n(const uint8_t mode, const int8_t id) {
// set hc->mode temporary until validate is received
if (model_ == EMSdevice::EMS_DEVICE_FLAG_RC10) {
hc->mode = set_mode_value >> 1;
} else if (model_ == EMSdevice::EMS_DEVICE_FLAG_BC400 || model_ == EMSdevice::EMS_DEVICE_FLAG_CR120) {
} else if (model_ == EMSdevice::EMS_DEVICE_FLAG_BC400 || model_ == EMSdevice::EMS_DEVICE_FLAG_UI800 || model_ == EMSdevice::EMS_DEVICE_FLAG_CR120) {
hc->mode_new = set_mode_value;
} else if (model_ == EMSdevice::EMS_DEVICE_FLAG_RC300 || model_ == EMSdevice::EMS_DEVICE_FLAG_R3000 || model_ == EMSdevice::EMS_DEVICE_FLAG_HMC310
|| model_ == EMSdevice::EMS_DEVICE_FLAG_RC100) {
@@ -3335,13 +3412,19 @@ bool Thermostat::set_mode_n(const uint8_t mode, const int8_t id) {
// sets the thermostat summermode for RC300
bool Thermostat::set_summermode(const char * value, const int8_t id) {
uint8_t set;
if (model() == EMSdevice::EMS_DEVICE_FLAG_UI800) {
if (Helpers::value2enum(value, set, FL_(enum_hpoperatingmode))) {
write_command(0x470, 0, set, 0x470);
return true;
}
return false;
}
auto hc = heating_circuit(id);
if (hc == nullptr) {
return false;
}
uint8_t set;
if (is_received(summer2_typeids[hc->hc()])) {
if ((hc->statusbyte & 1) && Helpers::value2enum(value, set, FL_(enum_summermode))) {
write_command(summer2_typeids[hc->hc()], 0, set, summer2_typeids[hc->hc()]);
@@ -3487,40 +3570,52 @@ bool Thermostat::set_boosttime(const char * value, const int8_t id) {
}
bool Thermostat::set_heatondelay(const char * value, const int8_t id) {
float f;
if (!Helpers::value2float(value, f)) {
return false;
}
if (model() == EMSdevice::EMS_DEVICE_FLAG_UI800) {
write_command(0x470, 2, (uint8_t)(f * 4), 0x470);
return true;
}
auto hc = heating_circuit(id);
if (hc == nullptr) {
return false;
}
int v;
if (!Helpers::value2number(value, v)) {
return false;
}
write_command(summer2_typeids[hc->hc()], 2, (uint8_t)v, summer2_typeids[hc->hc()]);
write_command(summer2_typeids[hc->hc()], 2, (uint8_t)f, summer2_typeids[hc->hc()]);
return true;
}
bool Thermostat::set_heatoffdelay(const char * value, const int8_t id) {
float f;
if (!Helpers::value2float(value, f)) {
return false;
}
if (model() == EMSdevice::EMS_DEVICE_FLAG_UI800) {
write_command(0x470, 3, (uint8_t)(f * 4), 0x470);
return true;
}
auto hc = heating_circuit(id);
if (hc == nullptr) {
return false;
}
int v;
if (!Helpers::value2number(value, v)) {
return false;
}
write_command(summer2_typeids[hc->hc()], 3, (uint8_t)v, summer2_typeids[hc->hc()]);
write_command(summer2_typeids[hc->hc()], 3, (uint8_t)f, summer2_typeids[hc->hc()]);
return true;
}
bool Thermostat::set_instantstart(const char * value, const int8_t id) {
auto hc = heating_circuit(id);
if (hc == nullptr) {
return false;
}
int v;
if (!Helpers::value2number(value, v)) {
return false;
}
if (model() == EMSdevice::EMS_DEVICE_FLAG_UI800) {
write_command(0x470, 4, (uint8_t)v, 0x470);
return true;
}
auto hc = heating_circuit(id);
if (hc == nullptr) {
return false;
}
write_command(summer2_typeids[hc->hc()], 4, (uint8_t)v, summer2_typeids[hc->hc()]);
return true;
}
@@ -4045,6 +4140,10 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
validate_typeid = set_typeids[hc->hc()];
switch (mode) {
case HeatingCircuit::Mode::SUMMER:
if (model == EMSdevice::EMS_DEVICE_FLAG_UI800) {
write_command(0x470, 1, temperature, 0x470);
return true;
}
if (is_received(summer2_typeids[hc->hc()])) {
offset = 0x01;
set_typeid = summer2_typeids[hc->hc()];
@@ -4056,6 +4155,10 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
factor = 1;
break;
case HeatingCircuit::Mode::COOLSTART:
if (model == EMSdevice::EMS_DEVICE_FLAG_UI800) {
write_command(0x470, 5, temperature, 0x470);
return true;
}
offset = 5;
set_typeid = summer2_typeids[hc->hc()];
validate_typeid = set_typeid;
@@ -4144,7 +4247,7 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
if (model == EMSdevice::EMS_DEVICE_FLAG_CR120 && mode_ == HeatingCircuit::Mode::MANUAL) {
offset = 22; // manual offset CR120
} else if (mode_ == HeatingCircuit::Mode::MANUAL) {
offset = 10; // manual offset
offset = hc->hpoperatingstate == 2 ? 17 : 10; // cooling or manual offset
} else {
offset = 8; // auto offset
// special case to reactivate auto temperature, see #737, #746
@@ -4443,6 +4546,7 @@ void Thermostat::register_device_values() {
case EMSdevice::EMS_DEVICE_FLAG_RC300:
case EMSdevice::EMS_DEVICE_FLAG_R3000:
case EMSdevice::EMS_DEVICE_FLAG_BC400:
case EMSdevice::EMS_DEVICE_FLAG_UI800:
case EMSdevice::EMS_DEVICE_FLAG_CR120:
case EMSdevice::EMS_DEVICE_FLAG_HMC310:
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
@@ -4560,6 +4664,70 @@ void Thermostat::register_device_values() {
register_device_value(
DeviceValueTAG::TAG_DEVICE_DATA, &pvLowerCool_, DeviceValueType::INT8, FL_(pvLowerCool), DeviceValueUOM::K, MAKE_CF_CB(set_pvLowerCool), -5, 0);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &absent_, DeviceValueType::BOOL, FL_(absent), DeviceValueUOM::NONE, MAKE_CF_CB(set_absent));
if (model() == EMSdevice::EMS_DEVICE_FLAG_UI800) {
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&hpoperatingmode,
DeviceValueType::ENUM,
FL_(enum_hpoperatingmode),
FL_(hpoperatingmode),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_summermode));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&summertemp,
DeviceValueType::UINT8,
FL_(summertemp),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_summertemp),
10,
30);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&instantstart,
DeviceValueType::UINT8,
FL_(instantstart),
DeviceValueUOM::K,
MAKE_CF_CB(set_instantstart),
1,
10);
register_device_value(
DeviceValueTAG::TAG_DEVICE_DATA, &coolstart, DeviceValueType::UINT8, FL_(coolstart), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_coolstart), 20, 35);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&heatondelay,
DeviceValueType::UINT8,
DeviceValueNumOp::DV_NUMOP_DIV4,
FL_(heatondelay),
DeviceValueUOM::HOURS,
MAKE_CF_CB(set_heatondelay),
1,
48);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&heatoffdelay,
DeviceValueType::UINT8,
DeviceValueNumOp::DV_NUMOP_DIV4,
FL_(heatoffdelay),
DeviceValueUOM::HOURS,
MAKE_CF_CB(set_heatoffdelay),
1,
48);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&coolondelay,
DeviceValueType::UINT8,
DeviceValueNumOp::DV_NUMOP_DIV4,
FL_(coolondelay),
DeviceValueUOM::HOURS,
MAKE_CF_CB(set_coolondelay),
1,
48);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&cooloffdelay,
DeviceValueType::UINT8,
DeviceValueNumOp::DV_NUMOP_DIV4,
FL_(cooloffdelay),
DeviceValueUOM::HOURS,
MAKE_CF_CB(set_cooloffdelay),
1,
48);
}
break;
case EMSdevice::EMS_DEVICE_FLAG_RC10:
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
@@ -4902,9 +5070,10 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
case EMSdevice::EMS_DEVICE_FLAG_RC300:
case EMSdevice::EMS_DEVICE_FLAG_R3000:
case EMSdevice::EMS_DEVICE_FLAG_BC400:
case EMSdevice::EMS_DEVICE_FLAG_UI800:
case EMSdevice::EMS_DEVICE_FLAG_CR120:
case EMSdevice::EMS_DEVICE_FLAG_HMC310:
if (model == EMSdevice::EMS_DEVICE_FLAG_BC400 || model == EMSdevice::EMS_DEVICE_FLAG_CR120) {
if (model == EMSdevice::EMS_DEVICE_FLAG_BC400 || model == EMSdevice::EMS_DEVICE_FLAG_UI800 || model == EMSdevice::EMS_DEVICE_FLAG_CR120) {
register_device_value(tag, &hc->mode_new, DeviceValueType::ENUM, FL_(enum_mode2), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode));
} else {
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode));
@@ -4997,6 +5166,8 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
register_device_value(tag, &hc->hpminflowtemp, DeviceValueType::UINT8, FL_(hpminflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_hpminflowtemp));
if (model == EMSdevice::EMS_DEVICE_FLAG_BC400) {
register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_control2), FL_(control), DeviceValueUOM::NONE, MAKE_CF_CB(set_control));
} else if (model == EMSdevice::EMS_DEVICE_FLAG_UI800) {
register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_control3), FL_(control), DeviceValueUOM::NONE, MAKE_CF_CB(set_control));
} else {
register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_control1), FL_(control), DeviceValueUOM::NONE, MAKE_CF_CB(set_control));
}
@@ -5012,14 +5183,16 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
101);
register_device_value(tag, &hc->remotehum, DeviceValueType::CMD, FL_(remotehum), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_remotehum), -1, 101);
}
register_device_value(tag, &hc->heatondelay, DeviceValueType::UINT8, FL_(heatondelay), DeviceValueUOM::HOURS, MAKE_CF_CB(set_heatondelay), 1, 48);
register_device_value(tag, &hc->heatoffdelay, DeviceValueType::UINT8, FL_(heatoffdelay), DeviceValueUOM::HOURS, MAKE_CF_CB(set_heatoffdelay), 1, 48);
register_device_value(tag, &hc->instantstart, DeviceValueType::UINT8, FL_(instantstart), DeviceValueUOM::K, MAKE_CF_CB(set_instantstart), 1, 10);
if (model != EMSdevice::EMS_DEVICE_FLAG_UI800) {
register_device_value(tag, &hc->heatondelay, DeviceValueType::UINT8, FL_(heatondelay), DeviceValueUOM::HOURS, MAKE_CF_CB(set_heatondelay), 1, 48);
register_device_value(tag, &hc->heatoffdelay, DeviceValueType::UINT8, FL_(heatoffdelay), DeviceValueUOM::HOURS, MAKE_CF_CB(set_heatoffdelay), 1, 48);
register_device_value(tag, &hc->coolondelay, DeviceValueType::UINT8, FL_(coolondelay), DeviceValueUOM::HOURS, MAKE_CF_CB(set_coolondelay), 1, 48);
register_device_value(tag, &hc->cooloffdelay, DeviceValueType::UINT8, FL_(cooloffdelay), DeviceValueUOM::HOURS, MAKE_CF_CB(set_cooloffdelay), 1, 48);
register_device_value(tag, &hc->instantstart, DeviceValueType::UINT8, FL_(instantstart), DeviceValueUOM::K, MAKE_CF_CB(set_instantstart), 1, 10);
register_device_value(tag, &hc->coolstart, DeviceValueType::UINT8, FL_(coolstart), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_coolstart), 20, 35);
}
register_device_value(tag, &hc->boost, DeviceValueType::BOOL, FL_(boost), DeviceValueUOM::NONE, MAKE_CF_CB(set_boost));
register_device_value(tag, &hc->boosttime, DeviceValueType::UINT8, FL_(boosttime), DeviceValueUOM::HOURS, MAKE_CF_CB(set_boosttime));
register_device_value(tag, &hc->coolstart, DeviceValueType::UINT8, FL_(coolstart), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_coolstart), 20, 35);
register_device_value(tag, &hc->coolondelay, DeviceValueType::UINT8, FL_(coolondelay), DeviceValueUOM::HOURS, MAKE_CF_CB(set_coolondelay), 1, 48);
register_device_value(tag, &hc->cooloffdelay, DeviceValueType::UINT8, FL_(cooloffdelay), DeviceValueUOM::HOURS, MAKE_CF_CB(set_cooloffdelay), 1, 48);
register_device_value(tag,
&hc->switchProgMode,
DeviceValueType::ENUM,
@@ -5037,8 +5210,8 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
MAKE_CF_CB(set_redthreshold),
12,
22);
register_device_value(tag, &hc->solarInfl, DeviceValueType::UINT8, FL_(solarinfl), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_solarinfl), -5, -1);
register_device_value(tag, &hc->currSolarInfl, DeviceValueType::UINT8, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(currsolarinfl), DeviceValueUOM::DEGREES);
register_device_value(tag, &hc->solarInfl, DeviceValueType::INT8, FL_(solarinfl), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_solarinfl), -5, 0);
register_device_value(tag, &hc->currSolarInfl, DeviceValueType::INT8, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(currsolarinfl), DeviceValueUOM::DEGREES);
register_device_value(tag, &hc->heatingpid, DeviceValueType::ENUM, FL_(enum_PID), FL_(heatingPID), DeviceValueUOM::NONE, MAKE_CF_CB(set_heatingpid));
register_device_value(tag, &hc->pumpopt, DeviceValueType::BOOL, FL_(pumpopt), DeviceValueUOM::NONE, MAKE_CF_CB(set_pumpopt));
register_device_value(tag,
@@ -5359,9 +5532,10 @@ void Thermostat::register_device_values_dhw(std::shared_ptr<Thermostat::DhwCircu
case EMSdevice::EMS_DEVICE_FLAG_RC300:
case EMSdevice::EMS_DEVICE_FLAG_R3000:
case EMSdevice::EMS_DEVICE_FLAG_BC400:
case EMSdevice::EMS_DEVICE_FLAG_UI800:
case EMSdevice::EMS_DEVICE_FLAG_CR120:
case EMSdevice::EMS_DEVICE_FLAG_HMC310:
if (model() == EMSdevice::EMS_DEVICE_FLAG_BC400 || model() == EMSdevice::EMS_DEVICE_FLAG_HMC310) {
if (model() == EMSdevice::EMS_DEVICE_FLAG_BC400 || model() == EMSdevice::EMS_DEVICE_FLAG_UI800 || model() == EMSdevice::EMS_DEVICE_FLAG_HMC310) {
register_device_value(tag, &dhw->wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode4), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode));
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_R3000) {
register_device_value(tag, &dhw->wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode5), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode));
+12 -1
View File
@@ -241,7 +241,7 @@ class Thermostat : public EMSdevice {
// check to see if the thermostat is a hybrid of the R300
inline bool isRC300() const {
return (model() == EMSdevice::EMS_DEVICE_FLAG_RC300 || model() == EMSdevice::EMS_DEVICE_FLAG_R3000 || model() == EMSdevice::EMS_DEVICE_FLAG_BC400
|| model() == EMSdevice::EMS_DEVICE_FLAG_CR120 || model() == EMSdevice::EMS_DEVICE_FLAG_HMC310);
|| model() == EMSdevice::EMS_DEVICE_FLAG_CR120 || model() == EMSdevice::EMS_DEVICE_FLAG_HMC310 || model() == EMSdevice::EMS_DEVICE_FLAG_UI800);
}
inline uint8_t id2dhw(const int8_t id) const { // returns telegram offset for TAG(id)
@@ -296,6 +296,16 @@ class Thermostat : public EMSdevice {
uint8_t absent_;
uint8_t hasSolar_;
// BC400/UI800 telegram 0x470
uint8_t hpoperatingmode;
uint8_t summertemp;
uint8_t heatondelay;
uint8_t heatoffdelay;
uint8_t instantstart;
uint8_t coolstart;
uint8_t coolondelay;
uint8_t cooloffdelay;
// HybridHP
uint8_t hybridStrategy_; // co2 = 1, cost = 2, temperature = 3, mix = 4
int8_t switchOverTemp_; // degrees
@@ -451,6 +461,7 @@ class Thermostat : public EMSdevice {
void process_RC300Set2(std::shared_ptr<const Telegram> telegram);
void process_RC300Summer(std::shared_ptr<const Telegram> telegram);
void process_RC300Summer2(std::shared_ptr<const Telegram> telegram);
void process_RC300Summer3(std::shared_ptr<const Telegram> telegram);
void process_RC300WWmode(std::shared_ptr<const Telegram> telegram);
void process_RC300WWmode2(std::shared_ptr<const Telegram> telegram);
void process_RC300WWtemp(std::shared_ptr<const Telegram> telegram);
+40 -39
View File
@@ -28,9 +28,41 @@ Water::Water(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
dhw_ = device_id - EMSdevice::EMS_DEVICE_ID_DHW1;
int8_t tag = DeviceValueTAG::TAG_DHW1 + dhw_;
if (flags == EMSdevice::EMS_DEVICE_FLAG_SM100) { // device_id 0x2A, DHW3
// telegram handlers
if (flags == EMSdevice::EMS_DEVICE_FLAG_IPM) {
dhw_ = 0;
tag = DeviceValueTAG::TAG_DHW1;
register_telegram_type(0x34, "UBAMonitorWW", false, MAKE_PF_CB(process_IPMMonitorWW));
register_telegram_type(0x1E, "HydrTemp", false, MAKE_PF_CB(process_IPMHydrTemp));
register_telegram_type(0x33, "UBAParameterWW", true, MAKE_PF_CB(process_IPMParameterWW), 12);
// register_telegram_type(0x10D, "wwNTCStatus", false, MAKE_PF_CB(process_wwNTCStatus));
// device values...
register_device_value(tag, &wwSelTemp_, DeviceValueType::UINT8, FL_(wwSelTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwSelTemp));
register_device_value(tag, &wwTemp_, DeviceValueType::UINT16, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(wwTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &wwTemp2_, DeviceValueType::UINT16, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(wwCurTemp2), DeviceValueUOM::DEGREES);
register_device_value(tag, &HydrTemp_, DeviceValueType::UINT16, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(hydrTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &wwPump_, DeviceValueType::BOOL, FL_(wwPump), DeviceValueUOM::NONE);
register_device_value(tag, &wwFlowTempOffset_, DeviceValueType::UINT8, FL_(wwFlowTempOffset), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_wwFlowTempOffset));
register_device_value(tag, &wwHystOn_, DeviceValueType::INT8, FL_(wwHystOn), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_wwHystOn));
register_device_value(tag, &wwHystOff_, DeviceValueType::INT8, FL_(wwHystOff), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_wwHystOff));
register_device_value(tag, &wwDisinfectionTemp_, DeviceValueType::UINT8, FL_(wwDisinfectionTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwDisinfectionTemp));
register_device_value(tag, &wwCirc_, DeviceValueType::BOOL, FL_(wwCirc), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwCirc));
register_device_value(tag, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode), FL_(wwCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwCircMode));
} else if (tag == DeviceValueTAG::TAG_DHW1 || tag == DeviceValueTAG::TAG_DHW2) { // MM100 or SM100
register_telegram_type(0x331 + dhw_, "MMPLUSStatusMessage_WWC", false, MAKE_PF_CB(process_MMPLUSStatusMessage_WWC));
register_telegram_type(0x313 + dhw_, "MMPLUSConfigMessage_WWC", true, MAKE_PF_CB(process_MMPLUSConfigMessage_WWC), 11);
// register_telegram_type(0x33B + type_offset, "MMPLUSSetMessage_WWC", true, MAKE_PF_CB(process_MMPLUSSetMessage_WWC));
// device values...
register_device_value(tag, &wwTemp_, DeviceValueType::UINT16, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(wwTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &wwStatus_, DeviceValueType::INT8, FL_(wwTempStatus), DeviceValueUOM::NONE);
register_device_value(tag, &wwPump_, DeviceValueType::BOOL, FL_(wwPump), DeviceValueUOM::NONE);
register_device_value(tag, &wwMaxTemp_, DeviceValueType::UINT8, FL_(wwMaxTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwMaxTemp));
register_device_value(tag, &wwDiffTemp_, DeviceValueType::INT8, FL_(wwDiffTemp), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_wwDiffTemp));
register_device_value(tag, &wwDisinfectionTemp_, DeviceValueType::UINT8, FL_(wwDisinfectionTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwDisinfectionTemp));
register_device_value(tag, &wwRedTemp_, DeviceValueType::UINT8, FL_(wwRedTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwRedTemp));
register_device_value(tag, &wwRequiredTemp_, DeviceValueType::UINT8, FL_(wwRequiredTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwRequiredTemp));
register_device_value(tag, &wwCirc_, DeviceValueType::BOOL, FL_(wwCirc), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwCirc));
register_device_value(tag, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode), FL_(wwCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwCircMode));
} else { // device_id 0x2A, DHW3, SM100
register_telegram_type(0x07D6 + dhw_ - 2, "SM100wwTemperature", false, MAKE_PF_CB(process_SM100wwTemperature));
register_telegram_type(0x07E0 + dhw_ - 2, "SM100wwStatus2", true, MAKE_PF_CB(process_SM100wwStatus2), 10);
register_telegram_type(0x07A6, "SM100wwParam", true, MAKE_PF_CB(process_SM100wwParam), 20); // same telegram for all circuits
@@ -67,41 +99,6 @@ Water::Water(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
register_device_value(tag, &wwRetValve_, DeviceValueType::UINT8, FL_(valveReturn), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_wwRetValve));
register_device_value(tag, &wwDeltaTRet_, DeviceValueType::UINT8, FL_(deltaTRet), DeviceValueUOM::K, MAKE_CF_CB(set_wwDeltaTRet));
register_device_value(tag, &errorDisp_, DeviceValueType::ENUM, FL_(enum_errorDisp), FL_(errorDisp), DeviceValueUOM::NONE, MAKE_CF_CB(set_errorDisp));
} else if (flags == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) { // dhw1 and dhw 2
register_telegram_type(0x331 + dhw_, "MMPLUSStatusMessage_WWC", false, MAKE_PF_CB(process_MMPLUSStatusMessage_WWC));
register_telegram_type(0x313 + dhw_, "MMPLUSConfigMessage_WWC", true, MAKE_PF_CB(process_MMPLUSConfigMessage_WWC), 11);
// register_telegram_type(0x33B + type_offset, "MMPLUSSetMessage_WWC", true, MAKE_PF_CB(process_MMPLUSSetMessage_WWC));
// device values...
register_device_value(tag, &wwTemp_, DeviceValueType::UINT16, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(wwTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &wwStatus_, DeviceValueType::INT8, FL_(wwTempStatus), DeviceValueUOM::NONE);
register_device_value(tag, &wwPump_, DeviceValueType::BOOL, FL_(wwPump), DeviceValueUOM::NONE);
register_device_value(tag, &wwMaxTemp_, DeviceValueType::UINT8, FL_(wwMaxTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwMaxTemp));
register_device_value(tag, &wwDiffTemp_, DeviceValueType::INT8, FL_(wwDiffTemp), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_wwDiffTemp));
register_device_value(tag, &wwDisinfectionTemp_, DeviceValueType::UINT8, FL_(wwDisinfectionTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwDisinfectionTemp));
register_device_value(tag, &wwRedTemp_, DeviceValueType::UINT8, FL_(wwRedTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwRedTemp));
register_device_value(tag, &wwRequiredTemp_, DeviceValueType::UINT8, FL_(wwRequiredTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwRequiredTemp));
register_device_value(tag, &wwCirc_, DeviceValueType::BOOL, FL_(wwCirc), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwCirc));
register_device_value(tag, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode), FL_(wwCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwCircMode));
} else if (flags == EMSdevice::EMS_DEVICE_FLAG_IPM) {
dhw_ = 0;
tag = DeviceValueTAG::TAG_DHW1;
register_telegram_type(0x34, "UBAMonitorWW", false, MAKE_PF_CB(process_IPMMonitorWW));
register_telegram_type(0x1E, "HydrTemp", false, MAKE_PF_CB(process_IPMHydrTemp));
register_telegram_type(0x33, "UBAParameterWW", true, MAKE_PF_CB(process_IPMParameterWW), 12);
// register_telegram_type(0x10D, "wwNTCStatus", false, MAKE_PF_CB(process_wwNTCStatus));
// device values...
register_device_value(tag, &wwSelTemp_, DeviceValueType::UINT8, FL_(wwSelTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwSelTemp));
register_device_value(tag, &wwTemp_, DeviceValueType::UINT16, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(wwTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &wwTemp2_, DeviceValueType::UINT16, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(wwCurTemp2), DeviceValueUOM::DEGREES);
register_device_value(tag, &HydrTemp_, DeviceValueType::UINT16, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(hydrTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &wwPump_, DeviceValueType::BOOL, FL_(wwPump), DeviceValueUOM::NONE);
register_device_value(tag, &wwFlowTempOffset_, DeviceValueType::UINT8, FL_(wwFlowTempOffset), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_wwFlowTempOffset));
register_device_value(tag, &wwHystOn_, DeviceValueType::INT8, FL_(wwHystOn), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_wwHystOn));
register_device_value(tag, &wwHystOff_, DeviceValueType::INT8, FL_(wwHystOff), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_wwHystOff));
register_device_value(tag, &wwDisinfectionTemp_, DeviceValueType::UINT8, FL_(wwDisinfectionTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwDisinfectionTemp));
register_device_value(tag, &wwCirc_, DeviceValueType::BOOL, FL_(wwCirc), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwCirc));
register_device_value(tag, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode), FL_(wwCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwCircMode));
}
}
@@ -361,7 +358,11 @@ bool Water::set_wwCircTc(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x33B + dhw_, 4, b ? 0x01 : 0x00, 0x33B + dhw_);
if (flags() == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) {
write_command(0x33B + dhw_, 4, b ? 0x01 : 0x00, 0x33B + dhw_);
} else { // SM100
write_command(0x7A5, 4, b ? 0xFF : 0x00, 0x7A5);
}
return true;
}
+1 -1
View File
@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.8.2"
#define EMSESP_APP_VERSION "3.8.3"
+6
View File
@@ -35,6 +35,11 @@ WebAPIService::WebAPIService(AsyncWebServer * server, SecurityManager * security
// POST|GET api/{device}
// POST|GET api/{device}/{entity}
void WebAPIService::webAPIService(AsyncWebServerRequest * request, JsonVariant json) {
static uint64_t lastcall = 0;
if (uuid::get_uptime_ms() - lastcall < 50 && request->method() == HTTP_GET) {
request->send(429); //too many requests
return;
}
JsonDocument input_doc; // has no body JSON so create dummy as empty input object
JsonObject input;
// if no body then treat it as a secure GET
@@ -49,6 +54,7 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request, JsonVariant j
input["data"] = json.as<std::string>();
}
parse(request, input);
lastcall = uuid::get_uptime_ms();
}
// for POSTS accepting plain text data
+3 -1
View File
@@ -77,7 +77,9 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web
if (entityItem.ram == 2) { // NVS
char key[sizeof(entityItem.name) + 2];
snprintf(key, sizeof(key), "c:%s", entityItem.name);
EMSESP::nvs_.remove(key);
if (EMSESP::nvs_.isKey(key)) {
EMSESP::nvs_.remove(key);
}
}
if (entityItem.ram) { // save name/value pairs for change checking
doc[entityItem.name] = entityItem.value;
+23 -13
View File
@@ -76,7 +76,9 @@ StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webSchedu
for (ScheduleItem & scheduleItem : webScheduler.scheduleItems) {
char key[sizeof(scheduleItem.name) + 2];
snprintf(key, sizeof(key), "s:%s", scheduleItem.name);
EMSESP::nvs_.remove(key);
if (EMSESP::nvs_.isKey(key)) {
EMSESP::nvs_.remove(key);
}
}
webScheduler.scheduleItems.clear();
EMSESP::webSchedulerService.ha_reset();
@@ -345,7 +347,11 @@ uint8_t WebSchedulerService::count_entities(bool cmd_only) {
// execute scheduled command
bool WebSchedulerService::command(const char * name, const std::string & command, const std::string & data) {
std::string cmd = Helpers::toLower(command);
if (cmd == "system/message") {
EMSESP::logger().info("Message: %s", data.c_str()); // send to log
Mqtt::queue_publish(F_(message), data); // send to MQTT if enabled
return true;
}
// check http commands. e.g.
// tasmota(get): http://<tasmotaIP>/cm?cmnd=power%20ON
// shelly(get): http://<shellyIP>/relais/0?turn=on
@@ -353,7 +359,11 @@ bool WebSchedulerService::command(const char * name, const std::string & command
JsonDocument doc;
if (deserializeJson(doc, cmd) == DeserializationError::Ok) {
HTTPClient * http = new HTTPClient;
std::string url = doc["url"] | "";
#ifndef EMSESP_STANDALONE
http->setConnectTimeout(10000);
http->setTimeout(10000);
#endif
std::string url = doc["url"] | "";
// for a GET with parameters replace commands with values
// don't search the complete url, it may contain a devicename in path
auto q = url.find_first_of('?');
@@ -401,6 +411,7 @@ bool WebSchedulerService::command(const char * name, const std::string & command
return true;
}
// we can add other json tests here
delete http;
}
doc.clear();
@@ -443,8 +454,7 @@ bool WebSchedulerService::command(const char * name, const std::string & command
// queue schedules to be handled executed in scheduler-loop
bool WebSchedulerService::onChange(const char * cmd) {
for (ScheduleItem & scheduleItem : *scheduleItems_) {
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_ONCHANGE
&& Helpers::toLower(scheduleItem.time.c_str()).find(Helpers::toLower(cmd)) != std::string::npos) {
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_ONCHANGE && Helpers::toLower(scheduleItem.time.c_str()) == Helpers::toLower(cmd)) {
cmd_changed_.push_back(&scheduleItem);
return true;
}
@@ -480,15 +490,14 @@ void WebSchedulerService::loop() {
static uint32_t last_uptime_min = 0;
static uint32_t last_uptime_sec = 0;
if (!raw_value.empty()) { // process a value from system/message command
computed_value = compute(raw_value);
raw_value.clear();
}
// get list of scheduler events and exit if it's empty
if (scheduleItems_->empty()) {
return;
}
// do not execute any command in the first 60 secondes
if (uuid::get_uptime_sec() < 60) {
return;
}
// check if we have onChange events
while (!cmd_changed_.empty()) {
@@ -532,7 +541,8 @@ void WebSchedulerService::loop() {
// retry startup commands not yet executed
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min == 0
&& scheduleItem.retry_cnt < MAX_STARTUP_RETRIES) {
scheduleItem.retry_cnt = command(scheduleItem.name, scheduleItem.cmd.c_str(), scheduleItem.value.c_str()) ? 0xFF : scheduleItem.retry_cnt + 1;
scheduleItem.retry_cnt =
command(scheduleItem.name, scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str())) ? 0xFF : scheduleItem.retry_cnt + 1;
}
// scheduled timer commands
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min > 0
@@ -543,10 +553,10 @@ void WebSchedulerService::loop() {
last_uptime_min = uptime_min;
}
// check calender, sync to RTC, only execute if year is valid
// check calender, sync to RTC, only execute if year is valid and uptime more than a minute
time_t now = time(nullptr);
tm * tm = localtime(&now);
if (tm->tm_min != last_tm_min && tm->tm_year > 120) {
if (tm->tm_min != last_tm_min && tm->tm_year > 120 && uuid::get_uptime_sec() > 60) {
// find the real dow and minute from RTC
uint8_t real_dow = 1 << tm->tm_wday; // 1 is Sunday
uint16_t real_min = tm->tm_hour * 60 + tm->tm_min;
-3
View File
@@ -90,9 +90,6 @@ class WebSchedulerService : public StatefulService<WebScheduler> {
std::string get_metrics_prometheus();
std::string raw_value;
std::string computed_value;
#if defined(EMSESP_TEST)
void load_test_data();
#endif
+3
View File
@@ -267,6 +267,9 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
}
settings.locale = root["locale"] | EMSESP_DEFAULT_LOCALE;
if (settings.locale == "cz") { // convert from older settings file
settings.locale = "cs";
}
EMSESP::system_.locale(settings.locale);
if (Mqtt::ha_enabled() && original_settings.locale != settings.locale) {
add_flags(ChangeFlags::MQTT);
+5
View File
@@ -177,6 +177,10 @@ void WebStatusService::systemStatus(AsyncWebServerRequest * request) {
// we're ready to do the actual restart ASAP
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_RESTART_REQUESTED);
}
if (EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_ERROR_UPLOAD) {
// error is reported, back to normal state
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_NORMAL);
}
#endif
@@ -241,6 +245,7 @@ void WebStatusService::action(AsyncWebServerRequest * request, JsonVariant json)
if (!ok) {
EMSESP::logger().err("Action '%s' failed", action.c_str());
request->send(400); // bad request
delete response;
return;
}