diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index 821a44efc..0006be584 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -30,6 +30,9 @@ - allow device name to be customized [#1174](https://github.com/emsesp/EMS-ESP32/issues/1174) - Modbus support by @mheyse [#1744](https://github.com/emsesp/EMS-ESP32/issues/1744) - System Message command [#1854](https://github.com/emsesp/EMS-ESP32/issues/1854) +- scheduler can use web get/post for values and commands [#1806](https://github.com/emsesp/EMS-ESP32/issues/1806) +- RT800 remote emulation [#1867](https://github.com/emsesp/EMS-ESP32/issues/1867) +- RC310 cooling parameters [#1857](https://github.com/emsesp/EMS-ESP32/issues/1857) ## Fixed diff --git a/src/device_library.h b/src/device_library.h index 41730b710..a2ae90543 100644 --- a/src/device_library.h +++ b/src/device_library.h @@ -122,7 +122,7 @@ {192, DeviceType::THERMOSTAT, "FW120", DeviceFlags::EMS_DEVICE_FLAG_JUNKERS}, // Thermostat remote - 0x38 -{ 3, DeviceType::THERMOSTAT, "RT800", DeviceFlags::EMS_DEVICE_FLAG_RC100H}, +{ 3, DeviceType::THERMOSTAT, "RT800/RC220", DeviceFlags::EMS_DEVICE_FLAG_RC100H}, {200, DeviceType::THERMOSTAT, "RC100H", DeviceFlags::EMS_DEVICE_FLAG_RC100H}, {249, DeviceType::THERMOSTAT, "TR120RF/CR20RF", DeviceFlags::EMS_DEVICE_FLAG_RC100H}, diff --git a/src/devices/thermostat.cpp b/src/devices/thermostat.cpp index ea57ee4a7..e086adf91 100644 --- a/src/devices/thermostat.cpp +++ b/src/devices/thermostat.cpp @@ -1138,7 +1138,7 @@ void Thermostat::process_RC300Summer(std::shared_ptr telegram) { has_update(telegram, hc->fastHeatup, 10); } -// types 0x471 ff +// types 0x471 ff summer2_typeids // (0x473), data: 00 11 04 01 01 1C 08 04 void Thermostat::process_RC300Summer2(std::shared_ptr telegram) { auto hc = heating_circuit(telegram); @@ -1156,6 +1156,9 @@ void Thermostat::process_RC300Summer2(std::shared_ptr telegram) has_update(telegram, hc->heatondelay, 2); has_update(telegram, hc->heatoffdelay, 3); has_update(telegram, hc->instantstart, 4); + has_update(telegram, hc->coolstart, 5); + has_update(telegram, hc->coolondelay, 6); + has_update(telegram, hc->cooloffdelay, 7); } // types 0x29B ff @@ -2248,6 +2251,35 @@ bool Thermostat::set_cooling(const char * value, const int8_t id) { return true; } +// set cooling delays +bool Thermostat::set_coolondelay(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; + } + write_command(summer2_typeids[hc->hc()], 6, v, summer2_typeids[hc->hc()]); + return true; +} + +bool Thermostat::set_cooloffdelay(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; + } + write_command(summer2_typeids[hc->hc()], 7, v, summer2_typeids[hc->hc()]); + return true; +} + // sets the thermostat ww circulation working mode, where mode is a string bool Thermostat::set_wwcircmode(const char * value, const int8_t id) { uint8_t dhw = id2dhw(id); @@ -3564,6 +3596,12 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co validate_typeid = set_typeid; factor = 1; break; + case HeatingCircuit::Mode::COOLSTART: + offset = 5; + set_typeid = summer2_typeids[hc->hc()]; + validate_typeid = set_typeid; + factor = 1; + break; case HeatingCircuit::Mode::MANUAL: if (model == EMSdevice::EMS_DEVICE_FLAG_CR120) { offset = 22; // manual offset CR120 @@ -4470,6 +4508,9 @@ void Thermostat::register_device_values_hc(std::shared_ptrinstantstart, DeviceValueType::UINT8, FL_(instantstart), DeviceValueUOM::K, MAKE_CF_CB(set_instantstart), 1, 10); 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); break; case EMSdevice::EMS_DEVICE_FLAG_CRF: diff --git a/src/devices/thermostat.h b/src/devices/thermostat.h index c1da03c52..afb7097f2 100644 --- a/src/devices/thermostat.h +++ b/src/devices/thermostat.h @@ -98,6 +98,9 @@ class Thermostat : public EMSdevice { uint8_t hpmode; uint8_t cooling; uint8_t coolingon; + uint8_t coolstart; // starttemperature 20-35°C + uint8_t coolondelay; // 1-48 hours + uint8_t cooloffdelay; // 1-48 hours // RC300 uint8_t heatoffdelay; // 1-48h uint8_t heatondelay; // 1-48h @@ -152,6 +155,7 @@ class Thermostat : public EMSdevice { DAYLOW, DAYMID, REMOTESELTEMP, + COOLSTART, UNKNOWN }; @@ -562,6 +566,9 @@ class Thermostat : public EMSdevice { inline bool set_remoteseltemp(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::REMOTESELTEMP); } + inline bool set_coolstart(const char * value, const int8_t id) { + return set_temperature_value(value, id, HeatingCircuit::Mode::COOLSTART); + } // set functions - these don't use the id/hc, the parameters are ignored bool set_wwmode(const char * value, const int8_t id); @@ -646,6 +653,8 @@ class Thermostat : public EMSdevice { bool set_hpminflowtemp(const char * value, const int8_t id); bool set_hpmode(const char * value, const int8_t id); bool set_cooling(const char * value, const int8_t id); + bool set_coolondelay(const char * value, const int8_t id); + bool set_cooloffdelay(const char * value, const int8_t id); }; } // namespace emsesp diff --git a/src/emsesp.cpp b/src/emsesp.cpp index 18ff6ac2e..00cd93e1a 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -1489,7 +1489,7 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) { // if telegram is longer read next part with offset +25 for ems+ or +27 for ems1.0 // not for response to raw send commands without read_id set - if ((response_id_ == 0 || read_id_ > 0) && (length >= 31) && (txservice_.read_next_tx(data[3], length) == read_id_)) { + if ((response_id_ == 0 || read_id_ > 0) && (txservice_.read_next_tx(data[3], length) == read_id_)) { read_next_ = true; txservice_.send(); } else { diff --git a/src/locale_common.h b/src/locale_common.h index d1f7f9b14..0426b05fe 100644 --- a/src/locale_common.h +++ b/src/locale_common.h @@ -182,8 +182,7 @@ MAKE_NOTRANSLATION(rc100, "RC100") MAKE_NOTRANSLATION(rc100h, "RC100H") MAKE_NOTRANSLATION(tc100, "TC100") MAKE_NOTRANSLATION(rc120rf, "RC120RF") -MAKE_NOTRANSLATION(rc220, "RC220") -MAKE_NOTRANSLATION(rt800, "RT800") +MAKE_NOTRANSLATION(rc220, "RC220/RT800") MAKE_NOTRANSLATION(single, "single") MAKE_NOTRANSLATION(dash, "-") MAKE_NOTRANSLATION(BLANK, "") @@ -347,7 +346,7 @@ MAKE_ENUM(enum_j_control, FL_(off), FL_(fb10), FL_(fb100)) 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_(rt800), FL_(single)) // BC400 +MAKE_ENUM(enum_control2, FL_(off), FL_(dash), FL_(rc100), FL_(rc100h), FL_(dash), FL_(rc120rf), FL_(rc220), FL_(single)) // BC400 MAKE_ENUM(enum_switchmode, FL_(off), FL_(eco), FL_(comfort), FL_(heat)) diff --git a/src/locale_translations.h b/src/locale_translations.h index 8401a5c63..024c5b0cf 100644 --- a/src/locale_translations.h +++ b/src/locale_translations.h @@ -733,6 +733,9 @@ MAKE_TRANSLATION(dewoffset, "dewoffset", "dew point offset", "Taupunkt Differenz MAKE_TRANSLATION(roomtempdiff, "roomtempdiff", "room temp difference", "Raumtemperatur Differenz", "Verschiltemperatuur kamertemp", "", "różnica temp. pomieszczenia", "", "", "oda sıcaklığı farkı", "differenza temperatura ambiente", "rozdiel izbovej teploty") // TODO translate MAKE_TRANSLATION(hpminflowtemp, "hpminflowtemp", "HP min. flow temp.", "WP minimale Vorlauftemperatur", "Minimale aanvoertemperatuur WP", "", "pompa ciepła, min. temperatura przepływu", "", "", "yüksek güç minimum akış sıcaklığı", "temperatura minima di mandata", "VT min. teplota prietoku.") // TODO translate MAKE_TRANSLATION(hpcooling, "cooling", "cooling", "Kühlen", "Koelen", "Kyler", "chłodzenie", "kjøling", "refroidissement", "soğuma", "raffreddamento", "chladenie") +MAKE_TRANSLATION(coolstart, "coolstart", "cooling starttemp", "Kühlbetrieb ab") // TODO translate +MAKE_TRANSLATION(coolondelay, "coolondelay", "cooling on delay", "Einschaltverzögerung Kühlen") // TODO translate +MAKE_TRANSLATION(cooloffdelay, "cooloffdelay", "cooling off delay", "Ausschaltverzögerung Kühlen") // TODO translate // heatpump and RC100H MAKE_TRANSLATION(airHumidity, "airhumidity", "relative air humidity", "relative Luftfeuchte", "Relatieve luchtvochtigheid", "Relativ Luftfuktighet", "wilgotność względna w pomieszczeniu", "luftfuktighet", "humidité relative air", "havadaki bağıl nem", "umidità relativa aria", "relatívna vlhkosť vzduchu") diff --git a/src/telegram.cpp b/src/telegram.cpp index fc2183c94..45d99d2c6 100644 --- a/src/telegram.cpp +++ b/src/telegram.cpp @@ -638,11 +638,12 @@ void TxService::retry_tx(const uint8_t operation, const uint8_t * data, const ui uint16_t TxService::read_next_tx(const uint8_t offset, const uint8_t length) { uint8_t old_length = telegram_last_->type_id > 0xFF ? length - 7 : length - 5; uint8_t next_length = telegram_last_->type_id > 0xFF ? EMS_MAX_TELEGRAM_MESSAGE_LENGTH - 2 : EMS_MAX_TELEGRAM_MESSAGE_LENGTH; - uint8_t next_offset = telegram_last_->offset + old_length; + uint8_t next_offset = offset + old_length; uint8_t message_data = (UINT8_MAX - next_offset) >= next_length ? next_length : UINT8_MAX - next_offset; // check telegram, offset and overflow // some telegrams only reply with one byte less, but have higher offsets (0x10) - if (old_length >= (next_length - 1) && telegram_last_->offset == offset) { + // some reply with higher offset than requestes and have not_set values intermediate (0xEA) + if (old_length >= (next_length - 1) || telegram_last_->offset < offset) { add(Telegram::Operation::TX_READ, telegram_last_->dest, telegram_last_->type_id, next_offset, &message_data, 1, 0, true); return telegram_last_->type_id; } diff --git a/src/version.h b/src/version.h index bfd5521a5..7ba5e93f1 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.7.0-dev.25" +#define EMSESP_APP_VERSION "3.7.0-dev.26" diff --git a/src/web/WebSchedulerService.cpp b/src/web/WebSchedulerService.cpp index 88abddcb1..41f3f0d99 100644 --- a/src/web/WebSchedulerService.cpp +++ b/src/web/WebSchedulerService.cpp @@ -18,6 +18,7 @@ #include "emsesp.h" #include "WebSchedulerService.h" +#include namespace emsesp { @@ -330,6 +331,33 @@ bool WebSchedulerService::has_commands() { // execute scheduled command bool WebSchedulerService::command(const char * name, const char * cmd, const char * data) { + // check http commands. e.g. + // tasmota(get): http:///cm?cmnd=power%20ON + // shelly(get): http:///relais/0?turn=on + const char * c = strchr(cmd, '{'); + if (c) { // parse json + JsonDocument doc; + int httpResult = 0; + if (DeserializationError::Ok == deserializeJson(doc, c)) { + HTTPClient http; + String url = doc["url"]; + if (http.begin(url)) { + for (JsonPair p : doc["header"].as()) { + http.addHeader(p.key().c_str(), p.value().as().c_str()); + } + String value = doc["value"] | ""; + if (value.length()) { + httpResult = http.POST(value); + } else if (data && data[0] != '\0') { // post + httpResult = http.POST(String(data)); + } else { + httpResult = http.GET(); + } + http.end(); + } + } + return httpResult > 0; + } JsonDocument doc_input; JsonObject input = doc_input.to(); if (strlen(data)) { // empty data queries a value @@ -390,12 +418,12 @@ void WebSchedulerService::condition() { #ifdef EMESESP_DEBUG // emsesp::EMSESP::logger().debug("condition match: %s", match.c_str()); #endif - if (!match.empty() && match[0] == '1') { - if (scheduleItem.retry_cnt == 0xFF) { // default unswitched - scheduleItem.retry_cnt = command(scheduleItem.name.c_str(), scheduleItem.cmd.c_str(), compute(scheduleItem.value).c_str()) ? 1 : 0xFF; - } - } else if (scheduleItem.retry_cnt == 1) { + if (match.length() == 1 && match[0] == '1' && scheduleItem.retry_cnt == 0xFF) { + scheduleItem.retry_cnt = command(scheduleItem.name.c_str(), scheduleItem.cmd.c_str(), compute(scheduleItem.value).c_str()) ? 1 : 0xFF; + } else if (match.length() == 1 && match[0] == '0' && scheduleItem.retry_cnt == 1) { scheduleItem.retry_cnt = 0xFF; + } else if (match.length() != 1) { // the match is not boolean + emsesp::EMSESP::logger().debug("condition result: %s", match.c_str()); } } } diff --git a/src/web/shuntingYard.hpp b/src/web/shuntingYard.hpp index d4f3a7e25..7fcd1be93 100644 --- a/src/web/shuntingYard.hpp +++ b/src/web/shuntingYard.hpp @@ -63,6 +63,22 @@ std::deque exprToTokens(const std::string & expr) { for (const auto * p = expr.c_str(); *p; ++p) { if (isblank(*p)) { // do nothing + } else if (*p == '{') { // json is stored as string including {} + const auto * b = p; + ++p; + uint8_t i = 1; + while (*p && i > 0) { + i += (*p == '{') ? 1 : (*p == '}') ? -1 : 0; + ++p; + } + if (*p) { + ++p; + } + const auto s = std::string(b, p); + tokens.push_back(Token{Token::Type::String, s, -3}); + if (*p == '\0') { + --p; + } } else if (*p >= 'a' && *p <= 'z') { const auto * b = p; while ((*p >= 'a' && *p <= 'z') || (*p == '_')) { @@ -579,7 +595,52 @@ std::string calculate(const std::string & expr) { // check for multiple instances of ? : std::string compute(const std::string & expr) { - auto expr_new = expr; + auto expr_new = emsesp::Helpers::toLower(expr); + + // search json with url: + auto f = expr_new.find_first_of("{"); + while (f != std::string::npos) { + auto e = f + 1; + for (uint8_t i = 1; i > 0; e++) { + if (e >= expr_new.length()) { + return ""; + } else if (expr_new[e] == '}') { + i--; + } else if (expr_new[e] == '{') { + i++; + } + } + std::string cmd = expr_new.substr(f, e - f).c_str(); + JsonDocument doc; + if (DeserializationError::Ok == deserializeJson(doc, cmd)) { + HTTPClient http; + String url = doc["url"]; + if (http.begin(url)) { + int httpResult = 0; + for (JsonPair p : doc["header"].as()) { + http.addHeader(p.key().c_str(), p.value().as().c_str()); + } + String data = doc["value"] | ""; + if (data.length()) { + httpResult = http.POST(data); + } else { + httpResult = http.GET(); + } + if (httpResult > 0) { + std::string result = emsesp::Helpers::toLower(http.getString().c_str()); + String key = doc["key"] | ""; + doc.clear(); + if (key.length() && DeserializationError::Ok == deserializeJson(doc, result)) { + result = doc[key.c_str()].as(); + } + expr_new.replace(f, e - f, result.c_str()); + } + http.end(); + } + } + f = expr_new.find_first_of("{", e); + } + // positions: q-questionmark, c-colon auto q = expr_new.find_first_of("?"); while (q != std::string::npos) {