/* * EMS-ESP - https://github.com/emsesp/EMS-ESP * Copyright 2020 Paul Derbyshire * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "thermostat.h" namespace emsesp { REGISTER_FACTORY(Thermostat, EMSdevice::DeviceType::THERMOSTAT); uuid::log::Logger Thermostat::logger_{F_(thermostat), uuid::log::Facility::CONSOLE}; Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand) : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { uint8_t actual_master_thermostat = EMSESP::actual_master_thermostat(); // what we're actually using uint8_t master_thermostat = EMSESP_DEFAULT_MASTER_THERMOSTAT; EMSESP::webSettingsService.read([&](WebSettings & settings) { master_thermostat = settings.master_thermostat; // what the user has defined }); uint8_t model = this->model(); // if we're on auto mode, register this thermostat if it has a device id of 0x10, 0x17 or 0x18 // or if its the master thermostat we defined // see https://github.com/emsesp/EMS-ESP/issues/362#issuecomment-629628161 if ((master_thermostat == device_id) || ((master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT) && (device_id < 0x19) && ((actual_master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT) || (device_id < actual_master_thermostat)))) { EMSESP::actual_master_thermostat(device_id); actual_master_thermostat = device_id; // reserve_telegram_functions(20); // reserve some space for the telegram registries, to avoid memory fragmentation // common telegram handlers register_telegram_type(EMS_TYPE_RCOutdoorTemp, F("RCOutdoorTemp"), false, MAKE_PF_CB(process_RCOutdoorTemp)); register_telegram_type(EMS_TYPE_RCTime, F("RCTime"), false, MAKE_PF_CB(process_RCTime)); register_telegram_type(0xA2, F("RCError"), false, MAKE_PF_CB(process_RCError)); register_telegram_type(0x12, F("RCErrorMessage"), false, MAKE_PF_CB(process_RCErrorMessage)); } // RC10 if (model == EMSdevice::EMS_DEVICE_FLAG_RC10) { monitor_typeids = {0xB1}; set_typeids = {0xB0}; for (uint8_t i = 0; i < monitor_typeids.size(); i++) { register_telegram_type(monitor_typeids[i], F("RC10Monitor"), false, MAKE_PF_CB(process_RC10Monitor)); register_telegram_type(set_typeids[i], F("RC10Set"), false, MAKE_PF_CB(process_RC10Set)); } // RC35 } else if ((model == EMSdevice::EMS_DEVICE_FLAG_RC35) || (model == EMSdevice::EMS_DEVICE_FLAG_RC30_N)) { monitor_typeids = {0x3E, 0x48, 0x52, 0x5C}; set_typeids = {0x3D, 0x47, 0x51, 0x5B}; timer_typeids = {0x3F, 0x49, 0x53, 0x5D}; timer2_typeids = {0x42, 0x4C, 0x56, 0x60}; for (uint8_t i = 0; i < monitor_typeids.size(); i++) { register_telegram_type(monitor_typeids[i], F("RC35Monitor"), false, MAKE_PF_CB(process_RC35Monitor)); register_telegram_type(set_typeids[i], F("RC35Set"), false, MAKE_PF_CB(process_RC35Set)); register_telegram_type(timer_typeids[i], F("RC35Timer"), false, MAKE_PF_CB(process_RC35Timer)); register_telegram_type(timer2_typeids[i], F("RC35Timer2"), false, MAKE_PF_CB(process_RC35Timer)); } register_telegram_type(EMS_TYPE_IBASettings, F("IBASettings"), true, MAKE_PF_CB(process_IBASettings)); register_telegram_type(EMS_TYPE_wwSettings, F("WWSettings"), true, MAKE_PF_CB(process_RC35wwSettings)); register_telegram_type(0x38, F("WWTimer"), true, MAKE_PF_CB(process_RC35wwTimer)); register_telegram_type(0x39, F("WWCircTimer"), true, MAKE_PF_CB(process_RC35wwTimer)); // RC20 } else if (model == EMSdevice::EMS_DEVICE_FLAG_RC20) { monitor_typeids = {0x91}; set_typeids = {0xA8}; curve_typeids = {0x90}; timer_typeids = {0x8F}; if (actual_master_thermostat == device_id) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) { register_telegram_type(monitor_typeids[i], F("RC20Monitor"), false, MAKE_PF_CB(process_RC20Monitor)); register_telegram_type(set_typeids[i], F("RC20Set"), false, MAKE_PF_CB(process_RC20Set)); register_telegram_type(curve_typeids[i], F("RC20Temp"), false, MAKE_PF_CB(process_RC20Temp)); register_telegram_type(timer_typeids[i], F("RC20Timer"), false, MAKE_PF_CB(process_RC20Timer)); } } // remote thermostat uses only 0xAF, register it also for master (in case of early detect) register_telegram_type(0xAF, F("RC20Remote"), false, MAKE_PF_CB(process_RC20Remote)); // RC20 newer } else if ((model == EMSdevice::EMS_DEVICE_FLAG_RC20_N) || (model == EMSdevice::EMS_DEVICE_FLAG_RC25)) { monitor_typeids = {0xAE}; set_typeids = {0xAD}; if (actual_master_thermostat == device_id) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) { register_telegram_type(monitor_typeids[i], F("RC20Monitor"), false, MAKE_PF_CB(process_RC20Monitor_2)); register_telegram_type(set_typeids[i], F("RC20Set"), false, MAKE_PF_CB(process_RC20Set_2)); } } register_telegram_type(0xAF, F("RC20Remote"), false, MAKE_PF_CB(process_RC20Remote)); // RC30 } else if (model == EMSdevice::EMS_DEVICE_FLAG_RC30) { monitor_typeids = {0x41}; set_typeids = {0xA7}; curve_typeids = {0x40}; timer_typeids = {0x3F}; for (uint8_t i = 0; i < monitor_typeids.size(); i++) { register_telegram_type(monitor_typeids[i], F("RC30Monitor"), false, MAKE_PF_CB(process_RC30Monitor)); register_telegram_type(set_typeids[i], F("RC30Set"), false, MAKE_PF_CB(process_RC30Set)); register_telegram_type(curve_typeids[i], F("RC30Temp"), false, MAKE_PF_CB(process_RC30Temp)); register_telegram_type(timer_typeids[i], F("RC30Timer"), false, MAKE_PF_CB(process_RC30Timer)); } register_telegram_type(EMS_TYPE_RC30wwSettings, F("RC30WWSettings"), true, MAKE_PF_CB(process_RC30wwSettings)); // EASY } else if (model == EMSdevice::EMS_DEVICE_FLAG_EASY) { monitor_typeids = {0x0A}; set_typeids = {}; register_telegram_type(monitor_typeids[0], F("EasyMonitor"), true, MAKE_PF_CB(process_EasyMonitor)); // CRF } else if (model == EMSdevice::EMS_DEVICE_FLAG_CRF) { monitor_typeids = {0x02A5, 0x02A6, 0x02A7, 0x02A8}; set_typeids = {}; for (uint8_t i = 0; i < monitor_typeids.size(); i++) { register_telegram_type(monitor_typeids[i], F("CRFMonitor"), false, MAKE_PF_CB(process_CRFMonitor)); } // RC300/RC100 } else if ((model == EMSdevice::EMS_DEVICE_FLAG_RC300) || (model == EMSdevice::EMS_DEVICE_FLAG_RC100)) { monitor_typeids = {0x02A5, 0x02A6, 0x02A7, 0x02A8, 0x02A9, 0x02AA, 0x02AB, 0x02AC}; set_typeids = {0x02B9, 0x02BA, 0x02BB, 0x02BC, 0x02BD, 0x02BE, 0x02BF, 0x02C0}; summer_typeids = {0x02AF, 0x02B0, 0x02B1, 0x02B2, 0x02B3, 0x02B4, 0x02B5, 0x02B6}; curve_typeids = {0x029B, 0x029C, 0x029D, 0x029E, 0x029F, 0x02A0, 0x02A1, 0x02A2}; summer2_typeids = {0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0476, 0x0477, 0x0478}; for (uint8_t i = 0; i < monitor_typeids.size(); i++) { register_telegram_type(monitor_typeids[i], F("RC300Monitor"), false, MAKE_PF_CB(process_RC300Monitor)); register_telegram_type(set_typeids[i], F("RC300Set"), false, MAKE_PF_CB(process_RC300Set)); register_telegram_type(summer_typeids[i], F("RC300Summer"), false, MAKE_PF_CB(process_RC300Summer)); register_telegram_type(curve_typeids[i], F("RC300Curves"), false, MAKE_PF_CB(process_RC300Curve)); register_telegram_type(summer2_typeids[i], F("RC300Summer2"), false, MAKE_PF_CB(process_RC300Summer2)); } register_telegram_type(0x2F5, F("RC300WWmode"), true, MAKE_PF_CB(process_RC300WWmode)); register_telegram_type(0x31B, F("RC300WWtemp"), true, MAKE_PF_CB(process_RC300WWtemp)); register_telegram_type(0x31D, F("RC300WWmode2"), false, MAKE_PF_CB(process_RC300WWmode2)); register_telegram_type(0x31E, F("RC300WWmode2"), false, MAKE_PF_CB(process_RC300WWmode2)); register_telegram_type(0x23A, F("RC300OutdoorTemp"), true, MAKE_PF_CB(process_RC300OutdoorTemp)); register_telegram_type(0x267, F("RC300Floordry"), false, MAKE_PF_CB(process_RC300Floordry)); register_telegram_type(0x240, F("RC300Settings"), true, MAKE_PF_CB(process_RC300Settings)); // JUNKERS/HT3 } else if (model == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) { if (actual_master_thermostat == device_id) { monitor_typeids = {0x016F, 0x0170, 0x0171, 0x0172}; for (uint8_t i = 0; i < monitor_typeids.size(); i++) { register_telegram_type(monitor_typeids[i], F("JunkersMonitor"), false, MAKE_PF_CB(process_JunkersMonitor)); } if (has_flags(EMS_DEVICE_FLAG_JUNKERS_OLD)) { // FR120, FR100 set_typeids = {0x0179, 0x017A, 0x017B, 0x017C}; for (uint8_t i = 0; i < monitor_typeids.size(); i++) { register_telegram_type(set_typeids[i], F("JunkersSet"), false, MAKE_PF_CB(process_JunkersSet2)); } } else { set_typeids = {0x0165, 0x0166, 0x0167, 0x0168}; for (uint8_t i = 0; i < monitor_typeids.size(); i++) { register_telegram_type(set_typeids[i], F("JunkersSet"), false, MAKE_PF_CB(process_JunkersSet)); } } } register_telegram_type(0xBB, F("HybridSettings"), true, MAKE_PF_CB(process_JunkersHybridSettings)); register_telegram_type(0x23, F("JunkersSetMixer"), true, MAKE_PF_CB(process_JunkersSetMixer)); register_telegram_type(0x123, F("JunkersRemote"), false, MAKE_PF_CB(process_JunkersRemoteMonitor)); } if (actual_master_thermostat != device_id) { return; // don't fetch data if more than 1 thermostat } // // this next section is only for the master thermostat.... // LOG_DEBUG(F("Setting this thermostat (device ID 0x%02X) to be the master"), device_id); // register device values for common values (not heating circuit) register_device_values(); // only for for the master-thermostat, go a query all the heating circuits. This is only done once. // The automatic fetch will from now on only update the active heating circuits for (uint8_t i = 0; i < monitor_typeids.size(); i++) { EMSESP::send_read_request(monitor_typeids[i], device_id); } for (uint8_t i = 0; i < set_typeids.size(); i++) { EMSESP::send_read_request(set_typeids[i], device_id); } EMSESP::send_read_request(EMS_TYPE_RCTime, device_id); EMSESP::send_read_request(0x12, device_id); // read last error (only published on errors) EMSESP::send_read_request(0xA2, device_id); // read errorCode (only published on errors) } // returns the heating circuit object based on the hc number // of nullptr if it doesn't exist yet std::shared_ptr Thermostat::heating_circuit(const uint8_t hc_num) { // if hc_num is 0 then return the first existing hc in the list if (hc_num == AUTO_HEATING_CIRCUIT) { for (const auto & heating_circuit : heating_circuits_) { if (heating_circuit->is_active()) { return heating_circuit; } } } // otherwise find a match for (const auto & heating_circuit : heating_circuits_) { if ((heating_circuit->hc_num() == hc_num) && heating_circuit->is_active()) { return heating_circuit; } } return nullptr; // not found } // determine which heating circuit the type ID is referring too // returns pointer to the HeatingCircuit or nullptr if it can't be found // if its a new one, the heating circuit object will be created and also the fetch flags set std::shared_ptr Thermostat::heating_circuit(std::shared_ptr telegram) { // look through the Monitor and Set arrays to see if there is a match uint8_t hc_num = 0; bool toggle_ = false; // search monitor message types for (uint8_t i = 0; i < monitor_typeids.size(); i++) { if (monitor_typeids[i] == telegram->type_id) { hc_num = i + 1; toggle_ = true; break; } } // not found, search status message/set types if (hc_num == 0) { for (uint8_t i = 0; i < set_typeids.size(); i++) { if (set_typeids[i] == telegram->type_id) { hc_num = i + 1; break; } } } // not found, search summer message types if (hc_num == 0) { for (uint8_t i = 0; i < summer_typeids.size(); i++) { if (summer_typeids[i] == telegram->type_id) { hc_num = i + 1; break; } } } // not found, search summer message types if (hc_num == 0) { for (uint8_t i = 0; i < summer2_typeids.size(); i++) { if (summer2_typeids[i] == telegram->type_id) { hc_num = i + 1; break; } } } // not found, search heating_curve message types if (hc_num == 0) { for (uint8_t i = 0; i < curve_typeids.size(); i++) { if (curve_typeids[i] == telegram->type_id) { hc_num = i + 1; break; } } } // not found, search timer message types if (hc_num == 0) { for (uint8_t i = 0; i < timer_typeids.size(); i++) { if (timer_typeids[i] == telegram->type_id) { hc_num = i + 1; break; } } } // not found, search timer message types if (hc_num == 0) { for (uint8_t i = 0; i < timer2_typeids.size(); i++) { if (timer2_typeids[i] == telegram->type_id) { hc_num = i + 1; break; } } } // not found, search device-id types for remote thermostats if (telegram->src >= 0x18 && telegram->src <= 0x1B) { hc_num = telegram->src - 0x17; toggle_ = true; } // not found, search device-id types for remote thermostats if (telegram->dest >= 0x20 && telegram->dest <= 0x27) { hc_num = telegram->dest - 0x20; } // still didn't recognize it, ignore it if (hc_num == 0) { return nullptr; } // if we have the heating circuit already present, returns its object reference // otherwise create a new object and add it for (const auto & heating_circuit : heating_circuits_) { if (heating_circuit->hc_num() == hc_num) { return heating_circuit; } } // register new heatingcircuits only on active monitor telegrams if (!toggle_) { return nullptr; } /* * at this point we have discovered a new heating circuit */ // if it's the first set the status flag if (heating_circuits_.empty()) { strlcpy(status_, "online", sizeof(status_)); } // create a new heating circuit object and add to the list auto new_hc = std::make_shared(hc_num, model()); heating_circuits_.push_back(new_hc); // sort based on hc number so there's a nice order when displaying // NOTE temporarily commented out the HC sorting until I'm 100% sure the return object still references the newly created object // not sure if new_hc and heating_circuits_.back() will still reference the new HC after its sorted - to check! // std::sort(heating_circuits_.begin(), heating_circuits_.end()); // register the device values register_device_values_hc(new_hc); // don't fetch telegrams if not the master if (device_id() != EMSESP::actual_master_thermostat()) { return new_hc; } // set the flag saying we want its data during the next auto fetch // monitor is broadcasted, but not frequently in some thermostats (IVT, #356) toggle_fetch(monitor_typeids[hc_num - 1], toggle_); if (set_typeids.size()) { toggle_fetch(set_typeids[hc_num - 1], toggle_); } if (summer_typeids.size()) { toggle_fetch(summer_typeids[hc_num - 1], toggle_); } if (curve_typeids.size()) { toggle_fetch(curve_typeids[hc_num - 1], toggle_); } if (timer_typeids.size()) { toggle_fetch(timer_typeids[hc_num - 1], toggle_); } if (timer2_typeids.size()) { toggle_fetch(timer2_typeids[hc_num - 1], toggle_); } if (summer2_typeids.size()) { toggle_fetch(summer2_typeids[hc_num - 1], toggle_); } return new_hc; // return back point to new HC object } // add the HVAC/Climate HA component for the HC void Thermostat::add_ha_climate(std::shared_ptr hc) const { if (!Mqtt::ha_enabled()) { hc->climate = EMS_VALUE_UINT_NOTSET; return; } if (Helpers::hasValue(hc->selTemp) && is_readable(&hc->selTemp)) { if (Helpers::hasValue(hc->roomTemp) && is_readable(&hc->roomTemp)) { hc->climate = 1; } else { hc->climate = 0; } } else { hc->climate = EMS_VALUE_UINT_NOTSET; } } // decodes the thermostat mode for the heating circuit based on the thermostat type // modes are off, manual, auto, day, night and holiday uint8_t Thermostat::HeatingCircuit::get_mode() const { uint8_t model = get_model(); if (!Helpers::hasValue(mode)) { return HeatingCircuit::Mode::UNKNOWN; } if (model == EMSdevice::EMS_DEVICE_FLAG_RC10) { if (mode == 2) { return HeatingCircuit::Mode::DAY; } else if (mode == 1) { return HeatingCircuit::Mode::NIGHT; } else if (mode == 0) { return HeatingCircuit::Mode::OFF; } } else if (model == EMSdevice::EMS_DEVICE_FLAG_RC20) { if (mode == 0) { return HeatingCircuit::Mode::OFF; } else if (mode == 1) { return HeatingCircuit::Mode::MANUAL; } else if (mode == 2) { return HeatingCircuit::Mode::AUTO; } } else if (model == EMSdevice::EMS_DEVICE_FLAG_CRF) { if (mode == 0) { return HeatingCircuit::Mode::AUTO; } else if (mode == 1) { return HeatingCircuit::Mode::OFF; } } else if ((model == EMSdevice::EMS_DEVICE_FLAG_RC300) || (model == EMSdevice::EMS_DEVICE_FLAG_RC100)) { if (mode == 0) { return HeatingCircuit::Mode::MANUAL; } else if (mode == 1) { return HeatingCircuit::Mode::AUTO; } } else if (model == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) { if (mode == 1) { return HeatingCircuit::Mode::MANUAL; } else if (mode == 2) { return HeatingCircuit::Mode::AUTO; } else if (mode == 3) { return HeatingCircuit::Mode::HOLIDAY; } } else { // default for all other thermostats if (mode == 0) { return HeatingCircuit::Mode::NIGHT; } else if (mode == 1) { return HeatingCircuit::Mode::DAY; } else if (mode == 2) { return HeatingCircuit::Mode::AUTO; } } return HeatingCircuit::Mode::UNKNOWN; } // figures out the thermostat day/night mode depending on the thermostat type // mode types are day, night, eco, comfort uint8_t Thermostat::HeatingCircuit::get_mode_type() const { uint8_t model = get_model(); if (model == EMS_DEVICE_FLAG_JUNKERS) { if (modetype == 2) { return HeatingCircuit::Mode::HEAT; } else if (modetype == 1) { return HeatingCircuit::Mode::ECO; } else if (modetype == 0) { return HeatingCircuit::Mode::NOFROST; } } else if ((model == EMS_DEVICE_FLAG_RC35) || (model == EMS_DEVICE_FLAG_RC30_N)) { if (modetype == 0) { return HeatingCircuit::Mode::NIGHT; } else if (modetype == 1) { return HeatingCircuit::Mode::DAY; } } else if (model == EMS_DEVICE_FLAG_CRF) { if (modetype == 0) { return HeatingCircuit::Mode::OFF; } else if (modetype == 1) { return HeatingCircuit::Mode::ON; } } else if (model == EMS_DEVICE_FLAG_RC300) { if (modetype == 0) { return HeatingCircuit::Mode::ECO; } else if (modetype == 1) { return HeatingCircuit::Mode::COMFORT; } } else if (model == EMS_DEVICE_FLAG_RC100) { return HeatingCircuit::Mode::DAY; // no other modes on these devices } return HeatingCircuit::Mode::DAY; } // decodes the thermostat mode based on the thermostat type // works with both modes and mode_types std::string Thermostat::mode_tostring(uint8_t mode) { switch (mode) { case HeatingCircuit::Mode::OFF: return read_flash_string(F_(off)); case HeatingCircuit::Mode::MANUAL: return read_flash_string(F_(manual)); case HeatingCircuit::Mode::DAY: return read_flash_string(F_(day)); case HeatingCircuit::Mode::NIGHT: return read_flash_string(F_(night)); case HeatingCircuit::Mode::ECO: return read_flash_string(F_(eco)); case HeatingCircuit::Mode::COMFORT: return read_flash_string(F_(comfort)); case HeatingCircuit::Mode::HEAT: return read_flash_string(F_(heat)); case HeatingCircuit::Mode::HOLIDAY: return read_flash_string(F_(holiday)); case HeatingCircuit::Mode::NOFROST: return read_flash_string(F_(nofrost)); case HeatingCircuit::Mode::AUTO: return read_flash_string(F_(auto)); case HeatingCircuit::Mode::SUMMER: return read_flash_string(F_(summer)); case HeatingCircuit::Mode::OFFSET: return read_flash_string(F_(offset)); case HeatingCircuit::Mode::DESIGN: return read_flash_string(F_(design)); case HeatingCircuit::Mode::MINFLOW: return read_flash_string(F_(minflow)); case HeatingCircuit::Mode::MAXFLOW: return read_flash_string(F_(maxflow)); case HeatingCircuit::Mode::ROOMINFLUENCE: return read_flash_string(F_(roominfluence[0])); case HeatingCircuit::Mode::FLOWOFFSET: return read_flash_string(F_(flowtempoffset[0])); case HeatingCircuit::Mode::TEMPAUTO: return read_flash_string(F_(tempauto)); case HeatingCircuit::Mode::NOREDUCE: return read_flash_string(F_(noreduce)); default: return read_flash_string(F_(unknown)); } } // type 0xB1 - data from the RC10 thermostat (0x17) // Data: 04 23 00 BA 00 00 00 BA void Thermostat::process_RC10Monitor(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } uint8_t mode = 1 << hc->mode; telegram->read_value(mode, 0); // 1: nofrost, 2: night, 4: day has_update(hc->mode, mode >> 1); // store as enum 0, 1, 2 has_update(telegram, hc->selTemp, 1, 1); // is * 2, force as single byte has_update(telegram, hc->roomTemp, 2); // is * 10 has_update(telegram, hc->reduceminutes, 5); add_ha_climate(hc); } // type 0xB0 - for reading the mode from the RC10 thermostat (0x17) // Data: 00 FF 00 1C 20 08 01 void Thermostat::process_RC10Set(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } has_update(telegram, ibaClockOffset_, 0); has_update(telegram, backlight_, 1); has_update(telegram, wwMode_, 2); has_update(telegram, hc->nighttemp, 3); has_update(telegram, hc->daytemp, 4); has_update(telegram, hc->reducehours, 5); has_update(telegram, heatingpid_, 6); } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" // type 0xB2, mode setting Data: 04 00 // not used, we read mode from monitor 0xB1 void Thermostat::process_RC10Set_2(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } // uint8_t mode = 1 << hc->mode; // telegram->read_value(mode, 0); // 1: nofrost, 2: night, 4: day // has_update(hc->mode, mode >> 1); // store as enum 0, 1, 2 } #pragma GCC diagnostic pop // 0xA8 - for reading the mode from the RC20 thermostat (0x17) // RC20Set(0xA8), data: 01 00 FF F6 01 06 00 01 0D 01 00 FF FF 01 02 02 02 00 00 05 1E 05 1E 02 1C 00 FF 00 00 26 02 void Thermostat::process_RC20Set(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } has_update(telegram, hc->mode, 23); // 0:off, 1:manual, 2:auto // has_update(telegram, hc->nofrosttemp, 24); // guess, not confirmed yet, maybe nighttemp? // has_update(telegram, hc->tempautotemp, 28); // no need to read this has_update(telegram, hc->manualtemp, 29); } // 0x90 - for reading curve temperature from the RC20 thermostat (0x17) // void Thermostat::process_RC20Temp(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } has_update(telegram, hc->nighttemp, 3); // 0:off, 1:manual, 2:auto has_update(telegram, hc->daylowtemp, 4); has_update(telegram, hc->daymidtemp, 5); has_update(telegram, hc->daytemp, 6); } // 0x8F - for reading timer from the RC20 thermostat (0x17) // data: 04 5D 01 78 24 5D 21 6E 43 5D 41 78 64 5D 61 78 84 5D 81 78 E7 90 E7 90 E7 90 E7 // data: 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 (offset 27) // data: E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 90 E7 (offset 54) // data: 90 E7 90 01 00 00 01 01 00 01 01 00 01 01 00 01 01 00 00 (offset 81) void Thermostat::process_RC20Timer(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } if ((telegram->message_length == 2 && telegram->offset < 83 && !(telegram->offset & 1)) || (!telegram->offset && telegram->message_length > 1 && !strlen(hc->switchtime1))) { char data[sizeof(hc->switchtime1)]; uint8_t no = telegram->offset / 2; uint8_t day = telegram->message_data[0] >> 5; uint8_t temp = telegram->message_data[0] & 7; uint8_t time = telegram->message_data[1]; std::string sday = read_flash_string(FL_(enum_dayOfWeek)[day]); if (day == 7) { snprintf(data, sizeof(data), "%02d not_set", no); } else { snprintf(data, sizeof(data), "%02d %s %02d:%02d T%d", no, sday.c_str(), time / 6, 10 * (time % 6), temp); } strlcpy(hc->switchtime1, data, sizeof(hc->switchtime1)); has_update(hc->switchtime1); // always publish } } // type 0xAE - data from the RC20 thermostat (0x17) - not for RC20's // 17 00 AE 00 80 12 2E 00 D0 00 00 64 (#data=8) // https://github.com/emsesp/EMS-ESP/issues/361 void Thermostat::process_RC20Monitor_2(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } // modes byte 0,1: day: 8002, night: 0000, auto-day:0402, auto-night:0400 has_bitupdate(telegram, hc->modetype, 1, 1); // day/night has_update(telegram, hc->selTemp, 2, 1); // is * 2, force as single byte has_update(telegram, hc->roomTemp, 3); // is * 10 has_bitupdate(telegram, hc->summermode, 1, 0); add_ha_climate(hc); } // 0xAD - for reading the mode from the RC20/ES72 thermostat (0x17) // see https://github.com/emsesp/EMS-ESP/issues/334#issuecomment-611698259 // offset: 01-nighttemp, 02-daytemp, 03-mode, 0B-program(1-9), 0D-setpoint_roomtemp(temporary) // 17 00 AD 00 01 27 29 01 4B 05 01 FF 28 19 0A 02 00 00 // RC25(0x17) -> All(0x00), ?(0xAD), data: 01 27 2D 00 44 05 01 FF 28 19 0A 07 00 00 F6 12 5A 11 00 28 05 05 00 void Thermostat::process_RC20Set_2(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } has_update(telegram, hc->heatingtype, 0); has_update(telegram, hc->nighttemp, 1); // is * 2, has_update(telegram, hc->daytemp, 2); // is * 2, has_update(telegram, hc->mode, 3); has_enumupdate(telegram, hc->program, 11, 1); // 1 .. 9 predefined programs has_update(telegram, hc->tempautotemp, 13); // RC25 extension: has_update(telegram, ibaMinExtTemperature_, 14); has_update(telegram, hc->minflowtemp, 15); has_update(telegram, hc->maxflowtemp, 16); has_update(telegram, hc->summertemp, 17); } // 0xAF - for reading the roomtemperature from the RC20/ES72 thermostat (0x18, 0x19, ..) void Thermostat::process_RC20Remote(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } has_update(telegram, hc->remotetemp, 0); } // type 0x0165, ff void Thermostat::process_JunkersSet(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } has_update(telegram, hc->daytemp, 17); // is * 2 has_update(telegram, hc->nighttemp, 16); // is * 2 has_update(telegram, hc->nofrosttemp, 15); // is * 2 has_update(telegram, hc->control, 1); // remote: 0-off, 1-FB10, 2-FB100 has_enumupdate(telegram, hc->program, 13, 1); // 1-6: 1 = A, 2 = B,... has_enumupdate(telegram, hc->mode, 14, 1); // 0 = nofrost, 1 = eco, 2 = heat, 3 = auto } // type 0x0179, ff void Thermostat::process_JunkersSet2(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } has_update(telegram, hc->daytemp, 7); // is * 2 has_update(telegram, hc->nighttemp, 6); // is * 2 has_update(telegram, hc->nofrosttemp, 5); // is * 2 has_enumupdate(telegram, hc->program, 10, 1); // 1-6: 1 = A, 2 = B,... has_enumupdate(telegram, hc->mode, 4, 1); // 0 = nofrost, 1 = eco, 2 = heat, 3 = auto } // type 0x123 - FR10/FR110 Junkers as remote void Thermostat::process_JunkersRemoteMonitor(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } has_update(telegram, hc->remotetemp, 0); // roomTemp from remote } // type 0xA3 - for external temp settings from the the RC* thermostats (e.g. RC35) void Thermostat::process_RCOutdoorTemp(std::shared_ptr telegram) { has_update(telegram, dampedoutdoortemp_, 0); has_update(telegram, tempsensor1_, 3); // sensor 1 - is * 10 has_update(telegram, tempsensor2_, 5); // sensor 2 - is * 10 } // 0x91 - data from the RC20 thermostat (0x17) - 15 bytes long // RC20Monitor(0x91), data: 90 2A 00 D5 1A 00 00 05 00 5A 04 00 D6 00 // offset 8: setburnpower to boiler, offset 9: setflowtemp to boiler (thermostat: targetflowtemp) send via 0x1A void Thermostat::process_RC20Monitor(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } has_update(telegram, hc->selTemp, 1, 1); // is * 2, force as single byte has_update(telegram, hc->roomTemp, 2); // is * 10 add_ha_climate(hc); } // type 0x0A - data from the Nefit Easy/TC100 thermostat (0x18) - 31 bytes long void Thermostat::process_EasyMonitor(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } has_update(telegram, hc->roomTemp, 8); // is * 100 has_update(telegram, hc->selTemp, 10); // is * 100 add_ha_climate(hc); } // Settings Parameters - 0xA5 - RC30_1 void Thermostat::process_IBASettings(std::shared_ptr telegram) { // 22 - display line on RC35 // display on Thermostat: 0 int. temp, 1 int. setpoint, 2 ext. temp., 3 burner temp., 4 ww temp, 5 functioning mode, 6 time, 7 data, 8 smoke temp has_update(telegram, ibaMainDisplay_, 0); has_update(telegram, ibaLanguage_, 1); // language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian has_update(telegram, ibaCalIntTemperature_, 2); // offset int. temperature sensor, by * 0.1 Kelvin has_update(telegram, ibaBuildingType_, 6); // building type: 0 = light, 1 = medium, 2 = heavy has_update(telegram, ibaMinExtTemperature_, 5); // min ext temp for heating curve, in deg., 0xF6=-10, 0x0 = 0, 0xFF=-1 has_update(telegram, ibaClockOffset_, 12); // offset (in sec) to clock, 0xff = -1 s, 0x02 = 2 s has_update(telegram, ibaDamping_, 21); // damping 0-off, 0xff-on } // Settings WW 0x37 - RC35 void Thermostat::process_RC35wwSettings(std::shared_ptr telegram) { has_update(telegram, wwProgMode_, 0); // 0-like hc, 0xFF own prog has_update(telegram, wwCircProg_, 1); // 0-like hc, 0xFF own prog has_update(telegram, wwMode_, 2); // 0-off, 1-on, 2-auto has_update(telegram, wwCircMode_, 3); // 0-off, 1-on, 2-auto has_update(telegram, wwDisinfecting_, 4); // 0-off, 0xFF on has_update(telegram, wwDisinfectDay_, 5); // 0-6 Day of week, 7 every day has_update(telegram, wwDisinfectHour_, 6); has_update(telegram, wwMaxTemp_, 8); // Limiter 60 degrees has_update(telegram, wwOneTimeKey_, 9); // 0-off, 0xFF on } // Settings WW 0x3A - RC30 void Thermostat::process_RC30wwSettings(std::shared_ptr telegram) { has_update(telegram, wwMode_, 0); // 0-on, 1-off, 2-auto has_update(telegram, wwWhenModeOff_, 1); // 0-off, 0xFF on has_update(telegram, wwDisinfecting_, 2); // 0-off, 0xFF on has_update(telegram, wwDisinfectDay_, 3); // 0-6 Day of week, 7 every day has_update(telegram, wwDisinfectHour_, 4); } // type 0x38 (ww) and 0x39 (circ) void Thermostat::process_RC35wwTimer(std::shared_ptr telegram) { if ((telegram->message_length == 2 && telegram->offset < 83 && !(telegram->offset & 1)) || (!telegram->offset && telegram->type_id == 0x38 && !strlen(wwSwitchTime_) && telegram->message_length > 1) || (!telegram->offset && telegram->type_id == 0x39 && !strlen(wwCircSwitchTime_) && telegram->message_length > 1)) { uint8_t no = telegram->offset / 2; uint8_t day = telegram->message_data[0] >> 5; uint8_t on = telegram->message_data[0] & 1; uint8_t time = telegram->message_data[1]; std::string sday = read_flash_string(FL_(enum_dayOfWeek)[day]); char data[sizeof(wwSwitchTime_)]; if (day == 7) { snprintf(data, sizeof(data), "%02d not_set", no); } else { snprintf(data, sizeof(data), "%02d %s %02d:%02d %s", no, sday.c_str(), time / 6, 10 * (time % 6), on ? "on" : "off"); } if (telegram->type_id == 0x38) { strlcpy(wwSwitchTime_, data, sizeof(wwSwitchTime_)); has_update(wwSwitchTime_); } else { strlcpy(wwCircSwitchTime_, data, sizeof(wwCircSwitchTime_)); has_update(wwCircSwitchTime_); } if (is_fetch(telegram->type_id)) { toggle_fetch(telegram->type_id, false); // dont fetch again } } } // type 0x6F - FR10/FR50/FR100/FR110/FR120 Junkers void Thermostat::process_JunkersMonitor(std::shared_ptr telegram) { // ignore single byte telegram messages if (telegram->message_length <= 1) { return; } std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } has_enumupdate(telegram, hc->modetype, 0, 1); // 1 = nofrost, 2 = eco, 3 = heat has_update(telegram, hc->selTemp, 2); // value is * 10 if ((hc->control == 1) || (hc->control == 2)) { has_update(telegram, hc->roomTemp, 6); // roomTemp from remote } else { has_update(telegram, hc->roomTemp, 4); // value is * 10 } add_ha_climate(hc); } // 0xBB Heatpump optimization // ?(0xBB), data: 00 00 00 00 00 00 00 00 00 00 00 FF 02 0F 1E 0B 1A 00 14 03 void Thermostat::process_JunkersHybridSettings(std::shared_ptr telegram) { has_enumupdate(telegram, hybridStrategy_, 12, 1); // cost = 2, temperature = 3, mix = 4 has_update(telegram, switchOverTemp_, 13); // full degrees has_update(telegram, energyCostRatio_, 14); // is *10 has_update(telegram, fossileFactor_, 15); // is * 10 has_update(telegram, electricFactor_, 16); // is * 10 has_update(telegram, delayBoiler_, 18); // minutes has_update(telegram, tempDiffBoiler_, 19); // relative degrees } void Thermostat::process_JunkersSetMixer(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } has_update(telegram, hc->targetflowtemp, 0); } // type 0x02A5 - data from Worchester CRF200 void Thermostat::process_CRFMonitor(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } has_update(telegram, hc->roomTemp, 0); // is * 10 has_bitupdate(telegram, hc->modetype, 2, 0); // bit 0, modetype (off=0 , on=1) has_bitupdate(telegram, hc->mode, 2, 4); // bit 4, mode (auto=0, off=1) has_update(telegram, hc->selTemp, 6, 1); // is * 2, force as single byte has_update(telegram, hc->targetflowtemp, 4); add_ha_climate(hc); } // type 0x02A5 - data from the Nefit RC1010/3000 thermostat (0x18) and RC300/310s on 0x10 void Thermostat::process_RC300Monitor(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } has_update(telegram, hc->roomTemp, 0); // is * 10 has_bitupdate(telegram, hc->modetype, 10, 1); has_bitupdate(telegram, hc->mode, 10, 0); // bit 1, mode (auto=1 or manual=0) // if manual, take the current setpoint temp at pos 6 // if auto, take the next setpoint temp at pos 7 // pos 3 is the current target temp and sometimes can be 0 // see https://github.com/emsesp/EMS-ESP/issues/256#issuecomment-585171426 // pos 3 actual setpoint (optimized), i.e. changes with temporary change, summer/holiday-modes // pos 6 actual setpoint according to programmed changes eco/comfort // pos 7 next setpoint in the future, time to next setpoint in pos 8/9 has_update(telegram, hc->selTemp, 3, 1); // is * 2, force as single byte has_bitupdate(telegram, hc->summermode, 2, 4); has_update(telegram, hc->targetflowtemp, 4); has_update(telegram, hc->curroominfl, 27); add_ha_climate(hc); } // type 0x02B9 EMS+ for reading from RC300/RC310 thermostat void Thermostat::process_RC300Set(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } // NOTE when setting the room temp we pick from two values, hopefully one is correct! // manual is position 10 // comfort is position 2, there are 3 levels in pos 3, 2, 1 // eco is position 4 // auto is position 8, temporary until next switch // actual setpoint taken from RC300Monitor (MichaelDvP 12.06.2020) // has_update(telegram, hc->selTemp, 8, 1); // single byte conversion, value is * 2 - auto? // has_update(telegram, hc->selTemp, 10, 1); // single byte conversion, value is * 2 - manual // check why mode is both in the Monitor and Set for the RC300. It'll be read twice! // has_update(telegram, hc->mode, 0); // Auto = xFF, Manual = x00 eg. 10 00 FF 08 01 B9 FF has_update(telegram, hc->daytemp, 2); // is * 2 has_update(telegram, hc->nighttemp, 4); // is * 2 // has_update(telegram, hc->tempautotemp, 8); // is * 2 // manipulate tempautotemp to show -1°C (with scale 0.5°C) if value is 0xFF // see https://github.com/emsesp/EMS-ESP32/issues/321 int8_t tat = hc->tempautotemp; telegram->read_value(tat, 8); if ((uint8_t)tat == 0xFF) { tat = -2; } has_update(hc->tempautotemp, tat); has_update(telegram, hc->manualtemp, 10); // is * 2 has_enumupdate(telegram, hc->program, 11, 1); // timer program 1 or 2 has_enumupdate(telegram, hc->reducemode1, 5, 1); // 1-outdoor temp threshold, 2-room temp threshold, 3-reduced mode has_update(telegram, hc->reducetemp, 9); has_update(telegram, hc->noreducetemp, 12); } // types 0x2AF ff void Thermostat::process_RC300Summer(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } has_update(telegram, hc->roominfluence, 0); has_update(telegram, hc->roominfl_factor, 1); // is * 10 has_update(telegram, hc->offsettemp, 2); if (!is_fetch(summer2_typeids[hc->hc()])) { has_update(telegram, hc->summertemp, 6); has_update(telegram, hc->summersetmode, 7); } if (hc->heatingtype < 3) { has_update(telegram, hc->designtemp, 4); } else { has_update(telegram, hc->designtemp, 5); } has_update(telegram, hc->minflowtemp, 8); has_update(telegram, hc->fastHeatup, 10); } // types 0x471 ff // (0x473), data: 00 11 04 01 01 1C 08 04 void Thermostat::process_RC300Summer2(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } has_update(telegram, hc->hpoperatingmode, 0); has_update(telegram, hc->summertemp, 1); } // types 0x29B ff void Thermostat::process_RC300Curve(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } has_update(telegram, hc->controlmode, 0); // 1-outdoor, 2-simple, 3-MPC, 4-room, 5-power, 6-const has_update(telegram, hc->heatingtype, 1); // 1=radiator, 2=convector, 3=floor has_update(telegram, hc->nofrosttemp, 6); if (hc->heatingtype < 3) { has_update(telegram, hc->maxflowtemp, 8); } else { has_update(telegram, hc->maxflowtemp, 7); } } // types 0x31B (and 0x31C?) void Thermostat::process_RC300WWtemp(std::shared_ptr telegram) { has_update(telegram, wwSetTemp_, 0); has_update(telegram, wwSetTempLow_, 1); } // type 02F5 // RC300WWmode(0x2F5), data: 01 FF 04 00 00 00 08 05 00 08 04 00 00 00 00 00 00 00 00 00 01 void Thermostat::process_RC300WWmode(std::shared_ptr telegram) { // circulation pump see: https://github.com/Th3M3/buderus_ems-wiki/blob/master/Einstellungen%20der%20Bedieneinheit%20RC310.md has_update(telegram, wwCircPump_, 1); // FF=off, 0=on ? has_update(telegram, wwMode_, 2); // 0=off, 1=low, 2=high, 3=auto, 4=own prog has_update(telegram, wwCircMode_, 3); // 0=off, 1=on, 2=auto, 4=own? has_update(telegram, wwChargeDuration_, 10); // value in steps of 15 min has_update(telegram, wwCharge_, 11); // boolv0xFF on has_update(telegram, wwDisinfecting_, 5); // 0-off, 0xFF on has_update(telegram, wwDisinfectHour_, 6); // value in steps of 15 min has_update(telegram, wwDisinfectDay_, 7); // 0-6 Day of week, 7 every day has_update(telegram, wwDailyHeating_, 8); // 0-off, 0xFF on has_update(telegram, wwDailyHeatTime_, 9); // value in steps of 15 min } // types 0x31D and 0x31E // RC300WWmode2(0x31D), data: 00 00 09 07 void Thermostat::process_RC300WWmode2(std::shared_ptr telegram) { // 0x31D for WW system 1, 0x31E for WW system 2 // pos 1 = holiday mode // pos 2 = current status of DHW setpoint // pos 3 = current status of DHW circulation pump if (telegram->type_id == 0x031D) { has_update(telegram, wwExtra1_, 0); // 0=no, 1=yes } else { has_update(telegram, wwExtra2_, 0); // 0=no, 1=yes } } // 0x23A damped outdoor temp void Thermostat::process_RC300OutdoorTemp(std::shared_ptr telegram) { has_update(telegram, dampedoutdoortemp2_, 0); // is *10 } // 0x240 RC300 parameter void Thermostat::process_RC300Settings(std::shared_ptr telegram) { has_enumupdate(telegram, ibaBuildingType_, 9, 1); // 1=light, 2=medium, 3=heavy has_update(telegram, ibaMinExtTemperature_, 10); } // 0x267 RC300 floordrying void Thermostat::process_RC300Floordry(std::shared_ptr telegram) { has_update(telegram, floordrystatus_, 0); has_update(telegram, floordrytemp_, 1); } // type 0x41 - data from the RC30 thermostat(0x10) - 14 bytes long void Thermostat::process_RC30Monitor(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } has_update(telegram, hc->selTemp, 1, 1); // is * 2, force as single byte has_update(telegram, hc->roomTemp, 2); add_ha_climate(hc); } // type 0xA7 - for reading the mode from the RC30 thermostat (0x10) and all the installation settings void Thermostat::process_RC30Set(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } has_update(telegram, ibaLanguage_, 0); // language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian has_update(telegram, ibaCalIntTemperature_, 1); // offset int. temperature sensor, by * 0.1 Kelvin has_update(telegram, autodst_, 2); // Automatic change Daylight Saving time: (0x00 = off, 0xFF = on) has_update(telegram, ibaBuildingType_, 4); // building type: 0 = light, 1 = medium, 2 = heavy has_update(telegram, ibaClockOffset_, 10); // offset (in sec) to clock, 0xff = -1 s, 0x02 = 2 s has_update(telegram, backlight_, 12); // Keyboard lighting: (0x00 = off, 0xFF = on) has_update(telegram, mixingvalves_, 17); // Number of Mixing Valves: (0x00=0, 0x01=1, 0x02=2) has_update(telegram, brightness_, 18); // Screen brightness 0F=dark F1=light has_update(telegram, hc->mode, 23); has_update(telegram, offtemp_, 24); // Set Temperature when mode is Off / 10 (e.g.: 0x0F = 7.5 degrees Celsius) has_update(telegram, heatingpid_, 25); // PID setting 00=1 01=2 02=3 has_update(telegram, preheating_, 26); // Preheating in the clock program: (0x00 = off, 0xFF = on) } // type 0x40 (HC1) - for reading the operating mode from the RC30 thermostat (0x10) void Thermostat::process_RC30Temp(std::shared_ptr telegram) { // check to see we have a valid type. heating: 1 radiator, 2 convectors, 3 floors if (telegram->offset == 0 && telegram->message_data[0] == 0x00) { return; } std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } has_update(telegram, hc->heatingtype, 0); // 0- off, 1-radiator, 2-convector, 3-floor has_update(telegram, hc->controlmode, 1); // 0-outdoortemp, 1-roomtemp has_update(telegram, hc->nighttemp, 3); // is * 2 (T1) has_update(telegram, hc->daylowtemp, 4); // is * 2 (T2) has_update(telegram, hc->daymidtemp, 5); // is * 2 (T3) has_update(telegram, hc->daytemp, 6); // is * 2 (T4) has_update(telegram, hc->holidaytemp, 7); // is * 2 } // type 0x3F (HC1) - timer setting for RC30 void Thermostat::process_RC30Timer(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } if ((telegram->message_length == 2 && telegram->offset < 83 && !(telegram->offset & 1)) || (!telegram->offset && telegram->message_length > 1 && !strlen(hc->switchtime1))) { char data[sizeof(hc->switchtime1)]; uint8_t no = telegram->offset / 2; uint8_t day = telegram->message_data[0] >> 5; uint8_t temp = telegram->message_data[0] & 7; uint8_t time = telegram->message_data[1]; std::string sday = read_flash_string(FL_(enum_dayOfWeek)[day]); if (day == 7) { snprintf(data, sizeof(data), "%02d not_set", no); } else { snprintf(data, sizeof(data), "%02d %s %02d:%02d T%d", no, sday.c_str(), time / 6, 10 * (time % 6), temp); } strlcpy(hc->switchtime1, data, sizeof(hc->switchtime1)); has_update(hc->switchtime1); // always publish } has_update(telegram, hc->program, 84); // 0 .. 10, 0-userprogram 1, 10-userprogram 2 has_update(telegram, hc->pause, 85); // time in hours has_update(telegram, hc->party, 86); // time in hours if (telegram->message_length + telegram->offset >= 92 && telegram->offset <= 87) { char data[sizeof(hc->vacation)]; snprintf(data, sizeof(data), "%02d.%02d.%04d-%02d.%02d.%04d", telegram->message_data[87 - telegram->offset], telegram->message_data[88 - telegram->offset], telegram->message_data[89 - telegram->offset] + 2000, telegram->message_data[90 - telegram->offset], telegram->message_data[91 - telegram->offset], telegram->message_data[92 - telegram->offset] + 2000); has_update(hc->vacation, data, sizeof(hc->vacation)); } if (telegram->message_length + telegram->offset >= 98 && telegram->offset <= 93) { char data[sizeof(hc->holiday)]; snprintf(data, sizeof(data), "%02d.%02d.%04d-%02d.%02d.%04d", telegram->message_data[93 - telegram->offset], telegram->message_data[94 - telegram->offset], telegram->message_data[95 - telegram->offset] + 2000, telegram->message_data[96 - telegram->offset], telegram->message_data[97 - telegram->offset], telegram->message_data[98 - telegram->offset] + 2000); has_update(hc->holiday, data, sizeof(hc->holiday)); } } // type 0x3E (HC1), 0x48 (HC2), 0x52 (HC3), 0x5C (HC4) - data from the RC35 thermostat (0x10) - 16 bytes void Thermostat::process_RC35Monitor(std::shared_ptr telegram) { // exit if the 15th byte (second from last) is 0x00, which I think is calculated flow setpoint temperature // with weather controlled RC35s this value is >=5, otherwise can be zero and our setpoint temps will be incorrect // see https://github.com/emsesp/EMS-ESP/issues/373#issuecomment-627907301 if (telegram->offset > 0 || telegram->message_length < 15) { return; } if (telegram->message_data[14] == 0x00) { return; } std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } has_update(telegram, hc->selTemp, 2, 1); // is * 2, force to single byte, is 0 in summermode has_update(telegram, hc->roomTemp, 3); // is * 10 - or 0x7D00 if thermostat is mounted on boiler has_bitupdate(telegram, hc->modetype, 1, 1); has_bitupdate(telegram, hc->summermode, 1, 0); has_bitupdate(telegram, hc->holidaymode, 0, 5); has_update(telegram, hc->targetflowtemp, 14); add_ha_climate(hc); } // type 0x3D (HC1), 0x47 (HC2), 0x51 (HC3), 0x5B (HC4) - Working Mode Heating - for reading the mode from the RC35 thermostat (0x10) void Thermostat::process_RC35Set(std::shared_ptr telegram) { // check to see we have a valid type. heating: 1 radiator, 2 convectors, 3 floors, 4 room supply if (telegram->offset == 0 && telegram->message_data[0] == 0x00) { return; } std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } has_update(telegram, hc->heatingtype, 0); // 0- off, 1-radiator, 2-convector, 3-floor has_update(telegram, hc->nighttemp, 1); // is * 2 has_update(telegram, hc->daytemp, 2); // is * 2 has_update(telegram, hc->holidaytemp, 3); // is * 2 has_update(telegram, hc->roominfluence, 4); // is * 1 has_update(telegram, hc->offsettemp, 6); // is * 2 has_update(telegram, hc->mode, 7); // night, day, auto has_update(telegram, hc->wwprio, 21); // 0xFF for on has_update(telegram, hc->summertemp, 22); // is * 1 has_update(telegram, hc->nofrosttemp, 23); // is * 1 has_update(telegram, hc->nofrostmode, 28); // 0-off, 1-outdoor, 2-roomtemp 5°C has_update(telegram, hc->flowtempoffset, 24); // is * 1, only in mixed circuits has_update(telegram, hc->reducemode, 25); // 0-nofrost, 1-reduce, 2-roomhold, 3-outdoorhold has_update(telegram, hc->control, 26); // 0-off, 1-RC20 (remote), 2-RC35 has_update(telegram, hc->controlmode, 33); // 0-outdoortemp, 1-roomtemp has_update(telegram, hc->tempautotemp, 37); has_update(telegram, hc->noreducetemp, 38); // outdoor temperature for no reduce has_update(telegram, hc->reducetemp, 39); // temperature for off/reduce has_update(telegram, hc->vacreducetemp, 40); // temperature for off/reduce in vacations has_update(telegram, hc->vacreducemode, 41); // vacations reduce mode has_update(telegram, hc->minflowtemp, 16); // RC35 stores values for floorheating in different position if (hc->heatingtype == 3 && model() == EMS_DEVICE_FLAG_RC35) { has_update(telegram, hc->designtemp, 36); // is * 1 has_update(telegram, hc->maxflowtemp, 35); // is * 1 } else { // radiator/convector has_update(telegram, hc->designtemp, 17); // is * 1 has_update(telegram, hc->maxflowtemp, 15); // is * 1 } } // type 0x3F (HC1), 0x49 (HC2), 0x53 (HC3), 0x5D (HC4) - timer setting void Thermostat::process_RC35Timer(std::shared_ptr telegram) { std::shared_ptr hc = heating_circuit(telegram); if (hc == nullptr) { return; } uint8_t prog = telegram->type_id == timer_typeids[hc->hc()] ? 0 : 1; if ((telegram->message_length == 2 && telegram->offset < 83 && !(telegram->offset & 1)) || (!telegram->offset && telegram->message_length > 1 && !prog && !strlen(hc->switchtime1)) || (!telegram->offset && telegram->message_length > 1 && prog && !strlen(hc->switchtime2))) { char data[sizeof(hc->switchtime1)]; uint8_t no = telegram->offset / 2; uint8_t day = telegram->message_data[0] >> 5; uint8_t on = telegram->message_data[0] & 1; uint8_t time = telegram->message_data[1]; std::string sday = read_flash_string(FL_(enum_dayOfWeek)[day]); if (day == 7) { snprintf(data, sizeof(data), "%02d not_set", no); } else { snprintf(data, sizeof(data), "%02d %s %02d:%02d %s", no, sday.c_str(), time / 6, 10 * (time % 6), on ? "on" : "off"); } if (!prog) { strlcpy(hc->switchtime1, data, sizeof(hc->switchtime1)); has_update(hc->switchtime1); // always publish } else { strlcpy(hc->switchtime2, data, sizeof(hc->switchtime2)); has_update(hc->switchtime2); if (is_fetch(telegram->type_id)) { toggle_fetch(telegram->type_id, false); } } } has_update(telegram, hc->program, 84); // 0 .. 10, 0-userprogram 1, 10-userprogram 2 has_update(telegram, hc->pause, 85); // time in hours has_update(telegram, hc->party, 86); // time in hours if (telegram->message_length + telegram->offset >= 92 && telegram->offset <= 87) { char data[sizeof(hc->vacation)]; snprintf(data, sizeof(data), "%02d.%02d.%04d-%02d.%02d.%04d", telegram->message_data[87 - telegram->offset], telegram->message_data[88 - telegram->offset], telegram->message_data[89 - telegram->offset] + 2000, telegram->message_data[90 - telegram->offset], telegram->message_data[91 - telegram->offset], telegram->message_data[92 - telegram->offset] + 2000); has_update(hc->vacation, data, sizeof(hc->vacation)); } if (telegram->message_length + telegram->offset >= 98 && telegram->offset <= 93) { char data[sizeof(hc->holiday)]; snprintf(data, sizeof(data), "%02d.%02d.%04d-%02d.%02d.%04d", telegram->message_data[93 - telegram->offset], telegram->message_data[94 - telegram->offset], telegram->message_data[95 - telegram->offset] + 2000, telegram->message_data[96 - telegram->offset], telegram->message_data[97 - telegram->offset], telegram->message_data[98 - telegram->offset] + 2000); has_update(hc->holiday, data, sizeof(hc->holiday)); } } // process_RCTime - type 0x06 - date and time from a thermostat - 14 bytes long void Thermostat::process_RCTime(std::shared_ptr telegram) { if (telegram->offset > 0 || telegram->message_length < 5) { return; } if (flags() == EMS_DEVICE_FLAG_EASY) { return; // not supported } if (telegram->message_length < 7) { return; } if ((telegram->message_data[7] & 0x0C) && has_command(&dateTime_)) { // date and time not valid set_datetime("ntp", -1); // set from NTP return; } // check clock time_t now = time(nullptr); 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]; tm_->tm_hour = telegram->message_data[2]; tm_->tm_min = telegram->message_data[4]; tm_->tm_sec = telegram->message_data[5]; tm_->tm_isdst = telegram->message_data[7] & 0x01; // render date to DD.MM.YYYY HH:MM and publish char newdatetime[sizeof(dateTime_)]; strftime(newdatetime, sizeof(dateTime_), "%d.%m.%G %H:%M", tm_); has_update(dateTime_, newdatetime, sizeof(dateTime_)); bool ivtclock = (telegram->message_data[0] & 0x80) == 0x80; // dont sync ivt-clock, #439 time_t ttime = mktime(tm_); // thermostat time // correct thermostat clock if we have valid ntp time, and could write the command if (!ivtclock && tset_ && EMSESP::system_.ntp_connected() && !EMSESP::system_.readonly_mode() && has_command(&dateTime_)) { double difference = difftime(now, ttime); if (difference > 15 || difference < -15) { set_datetime("ntp", -1); // set from NTP LOG_INFO(F("thermostat time correction from ntp")); } } #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}; settimeofday(&newnow, nullptr); LOG_INFO(F("ems-esp time set from thermostat")); } #endif } // process_RCError - type 0xA2 - error message - 14 bytes long // 10 00 A2 00 41 32 32 03 30 00 02 00 00 00 00 00 00 02 CRC // A 2 2 816 void Thermostat::process_RCError(std::shared_ptr telegram) { if (telegram->offset > 0 || telegram->message_length < 5) { return; } telegram->read_value(errorNumber_, 3); char code[sizeof(errorCode_)]; code[0] = telegram->message_data[0]; code[1] = telegram->message_data[1]; code[2] = telegram->message_data[2]; snprintf(&code[3], sizeof(code) - 3, "(%d)", errorNumber_); has_update(errorCode_, code, sizeof(errorCode_)); } // 0x12 error log // RCErrorMessage(0x12), data: 32 32 03 30 95 0A 0A 15 18 00 01 19 32 32 03 30 95 0A 09 05 18 00 01 19 31 38 03 // RCErrorMessage(0x12), data: 39 95 08 09 0F 19 00 01 17 64 31 03 34 95 07 10 08 00 00 01 70 (offset 27) void Thermostat::process_RCErrorMessage(std::shared_ptr telegram) { if (telegram->offset > 0 || telegram->message_length < 11) { return; } // data: displaycode(2), errornumber(2), year, month, hour, day, minute, duration(2), src-addr if (telegram->message_data[4] & 0x80) { // valid date char code[sizeof(lastCode_)] = {0}; uint16_t codeNo = EMS_VALUE_USHORT_NOTSET; code[0] = telegram->message_data[0]; code[1] = telegram->message_data[1]; code[2] = 0; telegram->read_value(codeNo, 2); uint16_t year = (telegram->message_data[4] & 0x7F) + 2000; uint8_t month = telegram->message_data[5]; uint8_t day = telegram->message_data[7]; uint8_t hour = telegram->message_data[6]; uint8_t min = telegram->message_data[8]; uint16_t duration = EMS_VALUE_SHORT_NOTSET; telegram->read_value(duration, 9); snprintf(&code[2], sizeof(code) - 2, "(%d) %02d.%02d.%d %02d:%02d (%d min)", codeNo, day, month, year, hour, min, duration); has_update(lastCode_, code, sizeof(lastCode_)); } } /* * * *** settings *** * */ // 0xBB Hybrid pump bool Thermostat::set_hybridStrategy(const char * value, const int8_t id) { uint8_t v; if (!Helpers::value2enum(value, v, FL_(enum_hybridStrategy))) { return false; } write_command(0xBB, 12, v + 1, 0xBB); return true; } bool Thermostat::set_switchOverTemp(const char * value, const int8_t id) { int v; if (!Helpers::value2temperature(value, v)) { return false; } write_command(0xBB, 13, v, 0xBB); return true; } bool Thermostat::set_energyCostRatio(const char * value, const int8_t id) { float v; if (!Helpers::value2float(value, v)) { return false; } write_command(0xBB, 14, (uint8_t)(v * 10), 0xBB); return true; } bool Thermostat::set_fossileFactor(const char * value, const int8_t id) { float v; if (!Helpers::value2float(value, v)) { return false; } write_command(0xBB, 15, (uint8_t)(v * 10), 0xBB); return true; } bool Thermostat::set_electricFactor(const char * value, const int8_t id) { float v; if (!Helpers::value2float(value, v)) { return false; } write_command(0xBB, 16, (uint8_t)(v * 10), 0xBB); return true; } bool Thermostat::set_delayBoiler(const char * value, const int8_t id) { int v; if (!Helpers::value2number(value, v)) { return false; } write_command(0xBB, 18, v, 0xBB); return true; } bool Thermostat::set_tempDiffBoiler(const char * value, const int8_t id) { int v; if (!Helpers::value2temperature(value, v, true)) { return false; } write_command(0xBB, 19, v, 0xBB); return true; } // 0xA5 - Set minimum external temperature bool Thermostat::set_minexttemp(const char * value, const int8_t id) { int mt = 0; if (!Helpers::value2temperature(value, mt)) { return false; } if ((model() == EMS_DEVICE_FLAG_RC20_N) || (model() == EMS_DEVICE_FLAG_RC25)) { write_command(0xAD, 14, mt, 0xAD); } else if ((model() == EMS_DEVICE_FLAG_RC300) || (model() == EMS_DEVICE_FLAG_RC100)) { write_command(0x240, 10, mt, 0x240); } else { write_command(EMS_TYPE_IBASettings, 5, mt, EMS_TYPE_IBASettings); } return true; } // 0xA5/0xA7 - Clock offset bool Thermostat::set_clockoffset(const char * value, const int8_t id) { int co = 0; if (!Helpers::value2number(value, co)) { return false; } if (model() == EMS_DEVICE_FLAG_RC30) { write_command(EMS_TYPE_RC30Settings, 10, co, EMS_TYPE_RC30Settings); } else { write_command(EMS_TYPE_IBASettings, 12, co, EMS_TYPE_IBASettings); } return true; } // 0xA5/0xA7 - Calibrate internal temperature bool Thermostat::set_calinttemp(const char * value, const int8_t id) { float ct = 0; if (!Helpers::value2temperature(value, ct, true)) { return false; } auto t = (int8_t)(ct * 10); LOG_DEBUG(F("Calibrating internal temperature to %d.%d C"), t / 10, t < 0 ? -t % 10 : t % 10); if (model() == EMS_DEVICE_FLAG_RC10) { write_command(0xB0, 0, t, 0xB0); } else if (model() == EMS_DEVICE_FLAG_RC30) { write_command(EMS_TYPE_RC30Settings, 1, t, EMS_TYPE_RC30Settings); } else { write_command(EMS_TYPE_IBASettings, 2, t, EMS_TYPE_IBASettings); } return true; } // 0xA5 - Set the display settings bool Thermostat::set_display(const char * value, const int8_t id) { int ds = 0; if (!Helpers::value2number(value, ds)) { return false; } write_command(EMS_TYPE_IBASettings, 0, ds, EMS_TYPE_IBASettings); return true; } // 0xA7 - Set Screen brightness bool Thermostat::set_brightness(const char * value, const int8_t id) { int bo = 0; if (!Helpers::value2number(value, bo, -15, 15)) { return false; } write_command(EMS_TYPE_RC30Settings, 18, bo, EMS_TYPE_RC30Settings); return true; } bool Thermostat::set_remotetemp(const char * value, const int8_t id) { float f = 0; if (!Helpers::value2temperature(value, f)) { return false; } uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { return false; } if (f > 100 || f < 0) { hc->remotetemp = EMS_VALUE_SHORT_NOTSET; } else { hc->remotetemp = (int16_t)(f * 10); } Roomctrl::set_remotetemp(hc->hc(), hc->remotetemp); has_update(hc->remotetemp); return true; } // 0xA5/0xA7 - Set the building settings bool Thermostat::set_building(const char * value, const int8_t id) { uint8_t bd = 0; if (!Helpers::value2enum(value, bd, FL_(enum_ibaBuildingType))) { return false; } if ((model() == EMS_DEVICE_FLAG_RC300) || (model() == EMS_DEVICE_FLAG_RC100)) { write_command(0x240, 9, bd + 1, 0x240); } else if (model() == EMS_DEVICE_FLAG_RC30) { write_command(EMS_TYPE_RC30Settings, 4, bd, EMS_TYPE_RC30Settings); } else { write_command(EMS_TYPE_IBASettings, 6, bd, EMS_TYPE_IBASettings); } return true; } // 0xB0/0xA7 - Set RC10 heating pid bool Thermostat::set_heatingpid(const char * value, const int8_t id) { uint8_t pid = 0; if (!Helpers::value2enum(value, pid, FL_(enum_PID))) { return false; } if (model() == EMS_DEVICE_FLAG_RC10) { write_command(0xB0, 6, pid, 0xB0); } else if (model() == EMS_DEVICE_FLAG_RC30) { write_command(EMS_TYPE_RC30Settings, 25, pid, EMS_TYPE_RC30Settings); } return true; } // 0xA5 - Set the damping settings bool Thermostat::set_damping(const char * value, const int8_t id) { bool dmp; if (Helpers::value2bool(value, dmp)) { write_command(EMS_TYPE_IBASettings, 21, dmp ? 0xFF : 0, EMS_TYPE_IBASettings); return true; } return false; } // 0xA5/0xA7 Set the language settings bool Thermostat::set_language(const char * value, const int8_t id) { uint8_t lg = 0; if (model() == EMS_DEVICE_FLAG_RC30) { if (!Helpers::value2enum(value, lg, FL_(enum_ibaLanguage_RC30))) { return false; } write_command(EMS_TYPE_RC30Settings, 0, lg, EMS_TYPE_RC30Settings); } else { if (!Helpers::value2enum(value, lg, FL_(enum_ibaLanguage))) { return false; } write_command(EMS_TYPE_IBASettings, 1, lg, EMS_TYPE_IBASettings); } return true; } // Set the control-mode for hc 0-off, 1-RC20, 2-RC3x bool Thermostat::set_control(const char * value, const int8_t id) { uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { return false; } uint8_t ctrl = 0; if (model() == EMS_DEVICE_FLAG_JUNKERS && !has_flags(EMS_DEVICE_FLAG_JUNKERS_OLD)) { if (Helpers::value2enum(value, ctrl, FL_(enum_j_control))) { write_command(set_typeids[hc->hc()], 1, ctrl); return true; } } else if (Helpers::value2enum(value, ctrl, FL_(enum_control))) { write_command(set_typeids[hc->hc()], EMS_OFFSET_RC35Set_control, ctrl); return true; } return false; } // sets the thermostat ww working mode, where mode is a string, ems and ems+ bool Thermostat::set_wwmode(const char * value, const int8_t id) { uint8_t set = 0xFF; if (model() == EMS_DEVICE_FLAG_RC10) { if (!Helpers::value2enum(value, set, FL_(enum_wwMode3))) { return false; } write_command(0xB0, 2, set, 0xB0); } else if ((model() == EMS_DEVICE_FLAG_RC300) || (model() == EMS_DEVICE_FLAG_RC100)) { if (!Helpers::value2enum(value, set, FL_(enum_wwMode))) { return false; } write_command(0x02F5, 2, set, 0x02F5); } else if (model() == EMS_DEVICE_FLAG_RC30) { if (!Helpers::value2enum(value, set, FL_(enum_wwMode3))) { return false; } write_command(EMS_TYPE_RC30wwSettings, 0, set, EMS_TYPE_RC30wwSettings); } else { if (!Helpers::value2enum(value, set, FL_(enum_wwMode2))) { return false; } write_command(EMS_TYPE_wwSettings, 2, set, EMS_TYPE_wwSettings); } return true; } //Set ww when thermostat mode is off (RC30) bool Thermostat::set_wwwhenmodeoff(const char * value, const int8_t id) { bool b = false; if (!Helpers::value2bool(value, b)) { return false; } write_command(EMS_TYPE_RC30wwSettings, 1, b ? 0xFF : 0x00, EMS_TYPE_RC30wwSettings); return true; } // Set ww temperature, ems+ bool Thermostat::set_wwtemp(const char * value, const int8_t id) { int t = 0; if (!Helpers::value2temperature(value, t, false, 0, 90)) { return false; } write_command(0x031B, 0, t, 0x031B); return true; } // Set ww low temperature, ems+ bool Thermostat::set_wwtemplow(const char * value, const int8_t id) { int t = 0; if (!Helpers::value2temperature(value, t)) { return false; } write_command(0x031B, 1, t, 0x031B); return true; } // Set ww charge RC300, ems+ bool Thermostat::set_wwcharge(const char * value, const int8_t id) { bool b = false; if (!Helpers::value2bool(value, b)) { return false; } write_command(0x02F5, 11, b ? 0xFF : 0x00, 0x02F5); return true; } // Set ww charge duration in steps of 15 min, ems+ bool Thermostat::set_wwchargeduration(const char * value, const int8_t id) { int t = 0xFF; if (!Helpers::value2number(value, t)) { return false; } t = (t + 8) / 15; write_command(0x2F5, 10, t, 0x02F5); return true; } // set ww prio bool Thermostat::set_wwprio(const char * value, const int8_t id) { uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { return false; } bool b; if (!Helpers::value2bool(value, b)) { return false; } write_command(set_typeids[hc->hc()], 21, b ? 0xFF : 0x00, set_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 set = 0xFF; if ((model() == EMS_DEVICE_FLAG_RC300) || (model() == EMS_DEVICE_FLAG_RC100)) { if (!Helpers::value2enum(value, set, FL_(enum_wwCircMode))) { return false; } write_command(0x02F5, 3, set, 0x02F5); return true; } if (!Helpers::value2enum(value, set, FL_(enum_wwMode2))) { return false; } write_command(EMS_TYPE_wwSettings, 3, set, EMS_TYPE_wwSettings); return true; } bool Thermostat::set_wwDailyHeating(const char * value, const int8_t id) { bool b = false; if (!Helpers::value2bool(value, b)) { return false; } write_command(0x2F5, 8, b ? 0xFF : 0x00, 0x2F5); return true; } bool Thermostat::set_wwDailyHeatTime(const char * value, const int8_t id) { int set; if (!Helpers::value2number(value, set)) { return false; } if ((model() == EMS_DEVICE_FLAG_RC300) || (model() == EMS_DEVICE_FLAG_RC100)) { uint8_t t = (set + 8) / 15; if (t > 95) { return false; } write_command(0x2F5, 9, t, 0x2F5); } return true; } bool Thermostat::set_wwDisinfect(const char * value, const int8_t id) { bool b = false; if (!Helpers::value2bool(value, b)) { return false; } if ((model() == EMS_DEVICE_FLAG_RC300) || (model() == EMS_DEVICE_FLAG_RC100)) { write_command(0x2F5, 5, b ? 0xFF : 0x00, 0x2F5); } else if (model() == EMS_DEVICE_FLAG_RC30) { write_command(EMS_TYPE_RC30wwSettings, 2, b ? 0xFF : 0x00, EMS_TYPE_RC30wwSettings); } else { write_command(0x37, 4, b ? 0xFF : 0x00, 0x37); } return true; } bool Thermostat::set_wwDisinfectDay(const char * value, const int8_t id) { uint8_t set = 0xFF; if (!Helpers::value2enum(value, set, FL_(enum_dayOfWeek))) { return false; } if ((model() == EMS_DEVICE_FLAG_RC300) || (model() == EMS_DEVICE_FLAG_RC100)) { write_command(0x2F5, 7, set, 0x2F5); } else if (model() == EMS_DEVICE_FLAG_RC30) { write_command(EMS_TYPE_RC30wwSettings, 3, set, EMS_TYPE_RC30wwSettings); } else { write_command(0x37, 5, set, 0x37); } return true; } bool Thermostat::set_wwDisinfectHour(const char * value, const int8_t id) { int set; if ((model() == EMS_DEVICE_FLAG_RC300) || (model() == EMS_DEVICE_FLAG_RC100)) { if (!Helpers::value2number(value, set, 0, 1431)) { return false; } write_command(0x2F5, 6, (set + 8) / 15, 0x2F5); } else if (model() == EMS_DEVICE_FLAG_RC30) { if (!Helpers::value2number(value, set, 0, 23)) { return false; } write_command(EMS_TYPE_RC30wwSettings, 4, set, EMS_TYPE_RC30wwSettings); } else { if (!Helpers::value2number(value, set, 0, 23)) { return false; } write_command(0x37, 6, set, 0x37); } return true; } bool Thermostat::set_wwMaxTemp(const char * value, const int8_t id) { int t = 0; if (!Helpers::value2temperature(value, t, false, 0, 90)) { return false; } write_command(0x37, 8, t, 0x37); return true; } bool Thermostat::set_wwOneTimeKey(const char * value, const int8_t id) { bool b = false; if (!Helpers::value2bool(value, b)) { return false; } write_command(0x37, 9, b ? 0xFF : 0x00, 0x37); return true; } // for RC10, 0xB0 or RC30, 0xA7 bool Thermostat::set_backlight(const char * value, const int8_t id) { bool b = false; if (!Helpers::value2bool(value, b)) { return false; } if (model() == EMS_DEVICE_FLAG_RC30) { write_command(EMS_TYPE_RC30Settings, 12, b ? 0xFF : 0x00, EMS_TYPE_RC30Settings); } else { write_command(0xB0, 1, b ? 0xFF : 0x00, 0xB0); } return true; } bool Thermostat::set_autodst(const char * value, const int8_t id) { bool b = false; if (!Helpers::value2bool(value, b)) { return false; } write_command(EMS_TYPE_RC30Settings, 2, b ? 0xFF : 0x00, EMS_TYPE_RC30Settings); return true; } bool Thermostat::set_preheating(const char * value, const int8_t id) { bool b = false; if (!Helpers::value2bool(value, b)) { return false; } write_command(EMS_TYPE_RC30Settings, 26, b ? 0xFF : 0x00, EMS_TYPE_RC30Settings); return true; } bool Thermostat::set_offtemp(const char * value, const int8_t id) { int ot = 0; if (!Helpers::value2temperature(value, ot, true)) { return false; } auto t = (int8_t)(ot * 2); write_command(EMS_TYPE_RC30Settings, 24, t, EMS_TYPE_RC30Settings); return true; } bool Thermostat::set_mixingvalves(const char * value, const int8_t id) { int m = 0; if (!Helpers::value2number(value, m, 0, 2)) { return false; } write_command(EMS_TYPE_RC30Settings, 17, m, EMS_TYPE_RC30Settings); return true; } bool Thermostat::set_wwProgMode(const char * value, const int8_t id) { uint8_t set = 0xFF; if (!Helpers::value2enum(value, set, FL_(enum_wwProgMode))) { return false; } write_command(0x37, 0, set ? 0xFF : 0x00, 0x37); return true; } bool Thermostat::set_wwCircProg(const char * value, const int8_t id) { uint8_t set = 0xFF; if (!Helpers::value2enum(value, set, FL_(enum_wwProgMode))) { return false; } write_command(0x37, 1, set ? 0xFF : 0x00, 0x37); return true; } // set the holiday as string dd.mm.yyyy-dd.mm.yyyy bool Thermostat::set_holiday(const char * value, const int8_t id, const bool vacation) { uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; std::shared_ptr hc = heating_circuit(hc_num); if ((hc == nullptr) || (value == nullptr)) { return false; } if (strlen(value) != 21) { return false; } uint8_t data[6]; data[0] = (value[0] - '0') * 10 + (value[1] - '0'); data[1] = (value[3] - '0') * 10 + (value[4] - '0'); data[2] = (value[7] - '0') * 100 + (value[8] - '0') * 10 + (value[9] - '0'); data[3] = (value[11] - '0') * 10 + (value[12] - '0'); data[4] = (value[14] - '0') * 10 + (value[15] - '0'); data[5] = (value[18] - '0') * 100 + (value[19] - '0') * 10 + (value[20] - '0'); if (data[0] > 31 || data[1] > 12 || data[3] > 31 || data[4] > 12) { return false; } if (!vacation || value[10] == '+') { // + for compatibility write_command(timer_typeids[hc->hc()], 93, data, 6, 0); } else { write_command(timer_typeids[hc->hc()], 87, data, 6, 0); } return true; } // set pause in hours bool Thermostat::set_pause(const char * value, const int8_t id) { uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { return false; } if (value == nullptr) { read_command(timer_typeids[hc->hc()], 85, 1); return true; } int hrs = 0; if (!Helpers::value2number(value, hrs)) { return false; } write_command(timer_typeids[hc->hc()], 85, hrs); return true; } // set partymode in hours bool Thermostat::set_party(const char * value, const int8_t id) { uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { return false; } if (value == nullptr) { read_command(timer_typeids[hc->hc()], 86, 1); return true; } int hrs = 0; if (!Helpers::value2number(value, hrs)) { return false; } write_command(timer_typeids[hc->hc()], 86, hrs); return true; } // set date&time as string dd.mm.yyyy-hh:mm:ss-dw-dst or "NTP" for setting to internet-time // dw - day of week (0..6), dst- summertime (0/1) // id is ignored bool Thermostat::set_datetime(const char * value, const int8_t id) { std::string dt; if (!Helpers::value2string(value, dt)) { return false; } uint8_t data[9]; if (dt == "ntp") { time_t now = time(nullptr); tm * tm_ = localtime(&now); if (tm_->tm_year < 110) { // no valid time return false; } if (!EMSESP::system_.ntp_connected()) { LOG_WARNING(F("Set date: no valid NTP data, setting from ESP Clock")); } data[0] = tm_->tm_year - 100; // Bosch counts from 2000 data[1] = tm_->tm_mon + 1; data[2] = tm_->tm_hour; data[3] = tm_->tm_mday; data[4] = tm_->tm_min; data[5] = tm_->tm_sec; 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 if (dt.length() == 23) { 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 data[3] = (dt[0] - '0') * 10 + (dt[1] - '0'); // day 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 } else { LOG_WARNING(F("Set date: invalid data, wrong length")); return false; } if (data[1] == 0 || data[1] > 12 || data[2] > 23 || data[3] == 0 || data[3] > 31 || data[4] > 59 || data[5] > 59 || data[6] > 6 || data[7] > 3) { LOG_WARNING(F("Invalid date/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]); return false; } // LOG_INFO(F("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); return true; } // set RC300 roominfluence factor bool Thermostat::set_roominfl_factor(const char * value, const int8_t id) { uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { return false; } float val = 0; if (!Helpers::value2float(value, val)) { return false; } write_command(summer_typeids[hc->hc()], 1, (uint8_t)(val * 10)); return true; } // sets the thermostat working mode, where mode is a string // converts string mode to HeatingCircuit::Mode bool Thermostat::set_mode(const char * value, const int8_t id) { if ((value == nullptr) || (strlen(value) >= 20)) { return false; } std::string mode; if (value[0] >= '0' && value[0] <= '9') { uint8_t num = value[0] - '0'; switch (model()) { case EMSdevice::EMS_DEVICE_FLAG_RC10: mode = read_flash_string(FL_(enum_mode6)[num]); break; case EMSdevice::EMS_DEVICE_FLAG_RC20: case EMSdevice::EMS_DEVICE_FLAG_RC20_N: mode = read_flash_string(FL_(enum_mode2)[num]); break; case EMSdevice::EMS_DEVICE_FLAG_RC25: case EMSdevice::EMS_DEVICE_FLAG_RC30: case EMSdevice::EMS_DEVICE_FLAG_RC35: case EMSdevice::EMS_DEVICE_FLAG_RC30_N: mode = read_flash_string(FL_(enum_mode3)[num]); break; case EMSdevice::EMS_DEVICE_FLAG_RC300: case EMSdevice::EMS_DEVICE_FLAG_RC100: mode = read_flash_string(FL_(enum_mode)[num]); break; case EMSdevice::EMS_DEVICE_FLAG_JUNKERS: mode = read_flash_string(FL_(enum_mode4)[num]); break; case EMSdevice::EMS_DEVICE_FLAG_CRF: mode = read_flash_string(FL_(enum_mode5)[num]); break; default: return false; } } else if (!Helpers::value2string(value, mode)) { return false; } uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; if (mode_tostring(HeatingCircuit::Mode::OFF) == mode) { return set_mode_n(HeatingCircuit::Mode::OFF, hc_num); } if (mode_tostring(HeatingCircuit::Mode::MANUAL) == mode) { return set_mode_n(HeatingCircuit::Mode::MANUAL, hc_num); } if (mode_tostring(HeatingCircuit::Mode::AUTO) == mode) { return set_mode_n(HeatingCircuit::Mode::AUTO, hc_num); } if (mode_tostring(HeatingCircuit::Mode::DAY) == mode) { return set_mode_n(HeatingCircuit::Mode::DAY, hc_num); } if (mode_tostring(HeatingCircuit::Mode::NIGHT) == mode) { return set_mode_n(HeatingCircuit::Mode::NIGHT, hc_num); } if (mode_tostring(HeatingCircuit::Mode::HEAT) == mode) { return set_mode_n(HeatingCircuit::Mode::HEAT, hc_num); } if (mode_tostring(HeatingCircuit::Mode::NOFROST) == mode) { return set_mode_n(HeatingCircuit::Mode::NOFROST, hc_num); } if (mode_tostring(HeatingCircuit::Mode::ECO) == mode) { return set_mode_n(HeatingCircuit::Mode::ECO, hc_num); } if (mode_tostring(HeatingCircuit::Mode::HOLIDAY) == mode) { return set_mode_n(HeatingCircuit::Mode::HOLIDAY, hc_num); } if (mode_tostring(HeatingCircuit::Mode::COMFORT) == mode) { return set_mode_n(HeatingCircuit::Mode::COMFORT, hc_num); } return false; } // Set the thermostat working mode // mode is HeatingCircuit::Mode bool Thermostat::set_mode_n(const uint8_t mode, const uint8_t hc_num) { // get hc based on number std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { return false; } uint8_t set_mode_value, offset; uint8_t hc_p = hc->hc(); uint16_t set_typeid = set_typeids[hc_p]; uint16_t validate_typeid = set_typeid; // set the value to send via EMS depending on the mode type switch (mode) { case HeatingCircuit::Mode::NIGHT: case HeatingCircuit::Mode::OFF: set_mode_value = 0; break; case HeatingCircuit::Mode::DAY: case HeatingCircuit::Mode::HEAT: case HeatingCircuit::Mode::MANUAL: case HeatingCircuit::Mode::NOFROST: set_mode_value = 1; break; default: // AUTO & ECO set_mode_value = 2; break; } uint8_t model_ = model(); switch (model_) { case EMSdevice::EMS_DEVICE_FLAG_RC10: offset = 0; validate_typeid = 0xB1; set_typeid = 0xB2; if (mode == HeatingCircuit::Mode::NOFROST || mode == HeatingCircuit::Mode::OFF) { set_mode_value = 1; } else if (mode == HeatingCircuit::Mode::NIGHT) { set_mode_value = 2; } else { // DAY set_mode_value = 4; } break; case EMSdevice::EMS_DEVICE_FLAG_RC20: offset = EMS_OFFSET_RC20Set_mode; break; case EMSdevice::EMS_DEVICE_FLAG_RC20_N: // ES72 case EMSdevice::EMS_DEVICE_FLAG_RC25: offset = EMS_OFFSET_RC20_2_Set_mode; break; case EMSdevice::EMS_DEVICE_FLAG_RC30: offset = EMS_OFFSET_RC30Set_mode; break; case EMSdevice::EMS_DEVICE_FLAG_RC35: case EMSdevice::EMS_DEVICE_FLAG_RC30_N: offset = EMS_OFFSET_RC35Set_mode; break; case EMSdevice::EMS_DEVICE_FLAG_RC300: case EMSdevice::EMS_DEVICE_FLAG_RC100: offset = EMS_OFFSET_RCPLUSSet_mode; validate_typeid = monitor_typeids[hc_p]; if (mode == HeatingCircuit::Mode::AUTO) { set_mode_value = 0xFF; // special value for auto } else { set_mode_value = 0; // everything else, like manual/day etc.. } break; case EMSdevice::EMS_DEVICE_FLAG_JUNKERS: if (has_flags(EMS_DEVICE_FLAG_JUNKERS_OLD)) { offset = EMS_OFFSET_JunkersSetMessage2_set_mode; } else { offset = EMS_OFFSET_JunkersSetMessage_set_mode; } // validate_typeid = monitor_typeids[hc_p]; if (mode == HeatingCircuit::Mode::NOFROST) { set_mode_value = 0x01; } else if (mode == HeatingCircuit::Mode::ECO || (mode == HeatingCircuit::Mode::NIGHT)) { set_mode_value = 0x02; } else if ((mode == HeatingCircuit::Mode::HEAT) || (mode == HeatingCircuit::Mode::DAY)) { set_mode_value = 0x03; // comfort } else if (mode == HeatingCircuit::Mode::AUTO) { set_mode_value = 0x04; } break; default: offset = 0; break; } // add the write command to the Tx queue // post validate is the corresponding monitor or set type IDs as they can differ per model write_command(set_typeid, offset, set_mode_value, validate_typeid); // 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_RC300 || model_ == EMSdevice::EMS_DEVICE_FLAG_RC100) { hc->mode = set_mode_value ? 1 : 0; } else if (model_ == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) { hc->mode = set_mode_value - 1; } else { hc->mode = set_mode_value; } has_update(&hc->mode); return true; } // sets the thermostat summermode for RC300 bool Thermostat::set_summermode(const char * value, const int8_t id) { uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { return false; } uint8_t set = 0xFF; if (is_fetch(summer2_typeids[hc->hc()])) { if (!Helpers::value2enum(value, set, FL_(enum_hpoperatingmode))) { return false; } write_command(summer2_typeids[hc->hc()], 0, set, summer2_typeids[hc->hc()]); } else { if (!Helpers::value2enum(value, set, FL_(enum_summermode))) { return false; } write_command(summer_typeids[hc->hc()], 7, set, summer_typeids[hc->hc()]); } return true; } // Set fastheatupfactor, ems+ bool Thermostat::set_fastheatup(const char * value, const int8_t id) { uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { return false; } int set = 0; if (!Helpers::value2number(value, set)) { return false; } write_command(summer_typeids[hc->hc()], 10, set, summer_typeids[hc->hc()]); return true; } // sets the thermostat reducemode for RC35 bool Thermostat::set_reducemode(const char * value, const int8_t id) { uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { return false; } uint8_t set = 0xFF; if (!Helpers::value2enum(value, set, FL_(enum_reducemode))) { return false; } write_command(set_typeids[hc->hc()], EMS_OFFSET_RC35Set_reducemode, set, set_typeids[hc->hc()]); return true; } // sets the thermostat reducemode1 for RC310 bool Thermostat::set_reducemode1(const char * value, const int8_t id) { uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { return false; } uint8_t set = 0xFF; if (model() == EMS_DEVICE_FLAG_RC300 || model() == EMS_DEVICE_FLAG_RC100) { if (Helpers::value2enum(value, set, FL_(enum_reducemode1))) { write_command(set_typeids[hc->hc()], 5, set + 1, set_typeids[hc->hc()]); } } if (set == 0xFF) { return false; } return true; } // sets the thermostat reducemode for RC35 vacations bool Thermostat::set_vacreducemode(const char * value, const int8_t id) { uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { return false; } uint8_t set = 0xFF; if (!Helpers::value2enum(value, set, FL_(enum_reducemode))) { return false; } write_command(set_typeids[hc->hc()], EMS_OFFSET_RC35Set_vacreducemode, set, set_typeids[hc->hc()]); return true; } // sets the thermostat nofrost mode for RC35 bool Thermostat::set_nofrostmode(const char * value, const int8_t id) { uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { return false; } uint8_t set = 0xFF; if (!Helpers::value2enum(value, set, FL_(enum_nofrostmode))) { return false; } write_command(set_typeids[hc->hc()], EMS_OFFSET_RC35Set_nofrostmode, set, set_typeids[hc->hc()]); return true; } // sets the thermostat heatingtype for RC35, RC300 bool Thermostat::set_heatingtype(const char * value, const int8_t id) { uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { return false; } uint8_t set = 0xFF; if (Helpers::value2enum(value, set, FL_(enum_heatingtype))) { if ((model() == EMS_DEVICE_FLAG_RC20_N) || (model() == EMS_DEVICE_FLAG_RC25)) { write_command(set_typeids[hc->hc()], 0, set, set_typeids[hc->hc()]); } else if (model() == EMS_DEVICE_FLAG_RC35 || model() == EMS_DEVICE_FLAG_RC30_N) { write_command(set_typeids[hc->hc()], 0, set, set_typeids[hc->hc()]); } else if (model() == EMS_DEVICE_FLAG_RC30) { write_command(curve_typeids[hc->hc()], 0, set, curve_typeids[hc->hc()]); } else { write_command(curve_typeids[hc->hc()], 1, set, curve_typeids[hc->hc()]); return true; } } return false; } // sets the thermostat controlmode for RC35, RC300 bool Thermostat::set_controlmode(const char * value, const int8_t id) { uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { return false; } uint8_t set = 0xFF; if (model() == EMS_DEVICE_FLAG_RC300 || model() == EMS_DEVICE_FLAG_RC100) { if (Helpers::value2enum(value, set, FL_(enum_controlmode))) { write_command(curve_typeids[hc->hc()], 0, set, curve_typeids[hc->hc()]); return true; } } else if (model() == EMS_DEVICE_FLAG_RC30) { if (Helpers::value2enum(value, set, FL_(enum_controlmode2))) { write_command(curve_typeids[hc->hc()], 1, set, curve_typeids[hc->hc()]); return true; } } else if (model() == EMS_DEVICE_FLAG_RC35 || model() == EMS_DEVICE_FLAG_RC30_N) { if (Helpers::value2enum(value, set, FL_(enum_controlmode2))) { write_command(set_typeids[hc->hc()], 33, set, set_typeids[hc->hc()]); return true; } } return false; } // sets the thermostat time for nightmode for RC10, telegrm 0xB0 bool Thermostat::set_reducehours(const char * value, const int8_t id) { uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { return false; } int v; if (Helpers::value2number(value, v)) { write_command(set_typeids[hc->hc()], 5, v, set_typeids[hc->hc()]); return true; } return false; } // sets a single switchtime in the thermostat program for RC35, ww, circ, hc.. bool Thermostat::set_switchtime(const char * value, const uint16_t type_id, char * out, size_t len) { if (value == nullptr) { return false; } uint8_t no = 44; // invalid value uint8_t day = 8; // invalid uint8_t on = 8; // invalid uint8_t time = 0x91; // invalid if (value[0] == '{') { StaticJsonDocument doc; DeserializationError error = deserializeJson(doc, value); if (error) { return false; } if (!doc.containsKey("no")) { return false; } no = doc["no"].as(); if (!doc.containsKey("day") || !doc.containsKey("mode") || !doc.containsKey("time")) { EMSESP::wait_validate(type_id); read_command(type_id, 2 * no, 2); return true; } const char * s_day = doc["day"]; if (!Helpers::value2enum(s_day, day, FL_(enum_dayOfWeek))) { return false; } const char * s_mode = doc["mode"]; const char * s_time = doc["time"]; if (model() == EMS_DEVICE_FLAG_RC35 || model() == EMS_DEVICE_FLAG_RC30_N) { bool b; if (Helpers::value2bool(s_mode, b)) { on = b ? 1 : 0; } if (strlen(s_time) == 5 && s_time[2] == ':') { time = 6 * ((s_time[0] - '0') * 10 + (s_time[1] - '0')) + (s_time[3] - '0'); } } else { Helpers::value2enum(s_mode, on, FL_(enum_switchmode)); if (strlen(s_time) == 5 && s_time[2] == ':') { time = 4 * ((s_time[0] - '0') * 10 + (s_time[1] - '0')) + ((s_time[3] - '0') * 10 + (s_time[4] - '0')) / 15; } } if (strncmp(s_mode, "not_set", 7) == 0) { day = 7; on = 7; time = 0x90; // LOG_INFO(F("switchtime %02d cleared"), no); } } else { if (strlen(value) > 1) { no = (value[0] - '0') * 10 + (value[1] - '0'); } if (strlen(value) == 2) { if (no < 42) { EMSESP::wait_validate(type_id); read_command(type_id, 2 * no, 2); return true; } return false; } if (strlen(value) > 4) { for (uint8_t i = 0; i < 7; i++) { if (!strncmp(&value[3], read_flash_string(FL_(enum_dayOfWeek)[i]).c_str(), 2)) { day = i; } } } if (strlen(value) > 10) { time = 6 * ((value[6] - '0') * 10 + (value[7] - '0')) + (value[9] - '0'); } if (strlen(value) > 13 && value[12] == 'o') { on = value[13] == 'n' ? 1 : 0; } else if (strlen(value) == 14 && value[12] == 'T') { on = value[13] - '0'; } else if (strlen(value) == 13) { on = value[12] - '0'; } if (strlen(value) >= 10 && strncmp(&value[3], "not_set", 7) == 0) { day = 7; on = 7; time = 0x90; // LOG_INFO(F("switchtime %02d cleared"), no); } } uint8_t data[2] = {0xE7, 0x90}; // unset switchtime if (day != 7 && on != 7) { data[0] = (day << 5) + on; data[1] = time; } uint8_t min_on = 1; uint8_t max_on = 4; if ((model() == EMS_DEVICE_FLAG_RC35) || (model() == EMS_DEVICE_FLAG_RC30_N)) { min_on = 0; max_on = 1; } if (no > 41 || time > 0x90 || ((on < min_on || on > max_on) && on != 7)) { // LOG_WARNING(F("Setting switchtime: Invalid data: %s"), value); // LOG_WARNING(F("Setting switchtime: Invalid data: %02d.%1d.0x%02X.%1d"), no, day, time, on); return false; } if (data[0] != 0xE7) { std::string sday = read_flash_string(FL_(enum_dayOfWeek)[day]); if (model() == EMS_DEVICE_FLAG_RC35 || model() == EMS_DEVICE_FLAG_RC30_N) { snprintf(out, len, "%02d %s %02d:%02d %s", no, sday.c_str(), time / 6, 10 * (time % 6), on ? "on" : "off"); } else if ((model() == EMS_DEVICE_FLAG_RC20) || (model() == EMS_DEVICE_FLAG_RC30)) { snprintf(out, len, "%02d %s %02d:%02d T%d", no, sday.c_str(), time / 6, 10 * (time % 6), on); } else { std::string son = read_flash_string(FL_(enum_switchmode)[on]); snprintf(out, len, "%02d %s %02d:%02d %s", no, sday.c_str(), time / 6, 10 * (time % 6), son.c_str()); } } else { snprintf(out, len, "%02d not_set", no); } write_command(type_id, no * 2, &data[0], 2, 0); return true; } // set switchtime for own1 program bool Thermostat::set_switchtime1(const char * value, const int8_t id) { uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { return false; } char out[sizeof(hc->switchtime1)] = {'\0'}; if (set_switchtime(value, timer_typeids[hc->hc()], out, sizeof(out))) { if (strlen(out)) { has_update(hc->switchtime1, out, sizeof(hc->switchtime1)); } return true; } return false; } // set switchtime for own2 program bool Thermostat::set_switchtime2(const char * value, const int8_t id) { uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { return false; } char out[sizeof(hc->switchtime2)] = {'\0'}; if (set_switchtime(value, timer_typeids[hc->hc()] + 3, out, sizeof(out))) { if (strlen(out)) { has_update(hc->switchtime2, out, sizeof(hc->switchtime2)); } return true; } return false; } // sets a single switchtime in the thermostat dhw program for RC35 bool Thermostat::set_wwCircSwitchTime(const char * value, const int8_t id) { char out[sizeof(wwCircSwitchTime_)] = {'\0'}; if (set_switchtime(value, 0x39, out, sizeof(out))) { if (strlen(out)) { has_update(wwCircSwitchTime_, out, sizeof(wwCircSwitchTime_)); } return true; } return false; } // sets a single switchtime in the thermostat circulation program for RC35 bool Thermostat::set_wwSwitchTime(const char * value, const int8_t id) { char out[sizeof(wwSwitchTime_)] = {'\0'}; if (set_switchtime(value, 0x38, out, sizeof(out))) { if (strlen(out)) { has_update(wwSwitchTime_, out, sizeof(wwSwitchTime_)); } return true; } return false; } // sets the thermostat program for RC35 and RC20 bool Thermostat::set_program(const char * value, const int8_t id) { uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { return false; } uint8_t set = 0xFF; if ((model() == EMS_DEVICE_FLAG_RC20_N) || (model() == EMS_DEVICE_FLAG_RC25)) { if (Helpers::value2enum(value, set, FL_(enum_progMode3))) { write_command(set_typeids[hc->hc()], 11, set + 1, set_typeids[hc->hc()]); } } else if (model() == EMS_DEVICE_FLAG_RC35 || model() == EMS_DEVICE_FLAG_RC30_N || model() == EMS_DEVICE_FLAG_RC30) { if (Helpers::value2enum(value, set, FL_(enum_progMode2))) { write_command(timer_typeids[hc->hc()], 84, set, timer_typeids[hc->hc()]); } } else if (model() == EMS_DEVICE_FLAG_RC300 || model() == EMS_DEVICE_FLAG_RC100) { if (Helpers::value2enum(value, set, FL_(enum_progMode))) { write_command(set_typeids[hc->hc()], 11, set + 1, set_typeids[hc->hc()]); } } else if (model() == EMS_DEVICE_FLAG_JUNKERS) { if (Helpers::value2enum(value, set, FL_(enum_progMode4))) { if (has_flags(EMS_DEVICE_FLAG_JUNKERS_OLD)) { write_command(set_typeids[hc->hc()], 10, set + 1, set_typeids[hc->hc()]); } else { write_command(set_typeids[hc->hc()], 13, set + 1, set_typeids[hc->hc()]); } } } if (set == 0xFF) { return false; } return true; } // Set the temperature of the thermostat // the id passed into this function is the heating circuit number bool Thermostat::set_temperature(const float temperature, const uint8_t mode, const uint8_t hc_num) { // get hc based on number std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { return false; } uint8_t model = this->model(); int8_t offset = -1; // we use -1 to check if there is a value uint8_t factor = 2; // some temperatures only use 1 uint16_t validate_typeid = monitor_typeids[hc->hc()]; uint16_t set_typeid = set_typeids[hc->hc()]; if (model == EMS_DEVICE_FLAG_RC10) { switch (mode) { case HeatingCircuit::Mode::NIGHT: offset = 3; break; case HeatingCircuit::Mode::DAY: offset = 4; break; case HeatingCircuit::Mode::AUTO: if (hc->get_mode() == HeatingCircuit::Mode::NIGHT) { offset = 3; } else { offset = 4; } break; default: break; } } else if (model == EMS_DEVICE_FLAG_RC20) { switch (mode) { case HeatingCircuit::Mode::NIGHT: offset = 3; set_typeid = curve_typeids[hc->hc()]; validate_typeid = set_typeid; break; case HeatingCircuit::Mode::DAYLOW: offset = 4; set_typeid = curve_typeids[hc->hc()]; validate_typeid = set_typeid; break; case HeatingCircuit::Mode::DAYMID: offset = 5; set_typeid = curve_typeids[hc->hc()]; validate_typeid = set_typeid; break; case HeatingCircuit::Mode::DAY: offset = 6; set_typeid = curve_typeids[hc->hc()]; validate_typeid = set_typeid; break; case HeatingCircuit::Mode::MANUAL: offset = EMS_OFFSET_RC20Set_temp_manual; break; case HeatingCircuit::Mode::AUTO: if (hc->get_mode() == HeatingCircuit::Mode::MANUAL) { offset = EMS_OFFSET_RC20Set_temp_manual; } else { offset = EMS_OFFSET_RC20Set_temp_auto; } break; default: break; } } else if (model == EMS_DEVICE_FLAG_RC30) { switch (mode) { case HeatingCircuit::Mode::NIGHT: // change the night temp set_typeid = curve_typeids[hc->hc()]; offset = EMS_OFFSET_RC30Temp_temp_night; break; case HeatingCircuit::Mode::DAYLOW: // change the offset temp set_typeid = curve_typeids[hc->hc()]; offset = EMS_OFFSET_RC30Temp_temp_daylow; break; case HeatingCircuit::Mode::DAYMID: // change the offset of flowtemp set_typeid = curve_typeids[hc->hc()]; offset = EMS_OFFSET_RC30Temp_temp_daymid; break; case HeatingCircuit::Mode::DAY: // change the day temp set_typeid = curve_typeids[hc->hc()]; offset = EMS_OFFSET_RC30Temp_temp_day; break; case HeatingCircuit::Mode::HOLIDAY: // change the holiday temp set_typeid = curve_typeids[hc->hc()]; offset = EMS_OFFSET_RC30Temp_temp_holiday; break; default: offset = EMS_OFFSET_RC30Set_temp; break; } } else if ((model == EMS_DEVICE_FLAG_RC300) || (model == EMS_DEVICE_FLAG_RC100)) { validate_typeid = set_typeids[hc->hc()]; switch (mode) { case HeatingCircuit::Mode::SUMMER: if (is_fetch(summer2_typeids[hc->hc()])) { offset = 0x01; set_typeid = summer2_typeids[hc->hc()]; } else { offset = 0x06; set_typeid = summer_typeids[hc->hc()]; } validate_typeid = set_typeid; factor = 1; break; case HeatingCircuit::Mode::MANUAL: offset = 0x0A; // manual offset break; case HeatingCircuit::Mode::TEMPAUTO: offset = 0x08; // auto offset if (temperature == -1) { factor = 1; // to write 0xFF } break; case HeatingCircuit::Mode::COMFORT: offset = 0x02; // comfort offset level 2 break; case HeatingCircuit::Mode::ECO: offset = 0x04; // eco offset break; case HeatingCircuit::Mode::OFFSET: offset = 2; set_typeid = summer_typeids[hc->hc()]; validate_typeid = set_typeid; factor = 1; break; case HeatingCircuit::Mode::DESIGN: set_typeid = summer_typeids[hc->hc()]; validate_typeid = set_typeid; if (hc->heatingtype == 3) { offset = 5; } else { offset = 4; } factor = 1; break; case HeatingCircuit::Mode::MINFLOW: set_typeid = summer_typeids[hc->hc()]; validate_typeid = set_typeid; offset = 8; factor = 1; break; case HeatingCircuit::Mode::MAXFLOW: set_typeid = curve_typeids[hc->hc()]; validate_typeid = set_typeid; if (hc->heatingtype == 3) { offset = 7; } else { offset = 8; } factor = 1; break; case HeatingCircuit::Mode::NOFROST: set_typeid = curve_typeids[hc->hc()]; validate_typeid = set_typeid; offset = 6; factor = 1; break; case HeatingCircuit::Mode::ROOMINFLUENCE: set_typeid = summer_typeids[hc->hc()]; validate_typeid = set_typeid; offset = 0; factor = 1; break; case HeatingCircuit::Mode::NOREDUCE: offset = 12; factor = 1; break; case HeatingCircuit::Mode::REDUCE: offset = 9; factor = 1; break; default: // HeatingCircuit::Mode::AUTO: uint8_t mode_ = hc->get_mode(); if (mode_ == HeatingCircuit::Mode::MANUAL) { offset = 0x0A; // manual offset } else { offset = 0x08; // auto offset // special case to reactivate auto temperature, see #737, #746 if (temperature == -1) { factor = 1; } } validate_typeid = monitor_typeids[hc->hc()]; // get setpoint roomtemp back break; } } else if ((model == EMS_DEVICE_FLAG_RC20_N) || (model == EMS_DEVICE_FLAG_RC25)) { switch (mode) { case HeatingCircuit::Mode::MINFLOW: offset = 15; factor = 1; break; case HeatingCircuit::Mode::MAXFLOW: offset = 16; factor = 1; break; case HeatingCircuit::Mode::SUMMER: offset = 17; factor = 1; break; case HeatingCircuit::Mode::TEMPAUTO: offset = 13; break; case HeatingCircuit::Mode::NIGHT: // change the night temp offset = EMS_OFFSET_RC20_2_Set_temp_night; break; case HeatingCircuit::Mode::DAY: // change the day temp offset = EMS_OFFSET_RC20_2_Set_temp_day; break; default: // automatic selection, if no type is defined, we use the standard code uint8_t mode_ = hc->get_mode(); if (mode_ == HeatingCircuit::Mode::NIGHT) { offset = EMS_OFFSET_RC20_2_Set_temp_night; } else if (mode_ == HeatingCircuit::Mode::DAY) { offset = EMS_OFFSET_RC20_2_Set_temp_day; } else { offset = 13; // tempautotemp } break; } } else if ((model == EMS_DEVICE_FLAG_RC35) || (model == EMS_DEVICE_FLAG_RC30_N)) { validate_typeid = set_typeids[hc->hc()]; switch (mode) { case HeatingCircuit::Mode::NIGHT: // change the night temp offset = EMS_OFFSET_RC35Set_temp_night; break; case HeatingCircuit::Mode::DAY: // change the day temp offset = EMS_OFFSET_RC35Set_temp_day; break; case HeatingCircuit::Mode::HOLIDAY: // change the holiday temp offset = EMS_OFFSET_RC35Set_temp_holiday; break; case HeatingCircuit::Mode::OFFSET: // change the offset temp offset = EMS_OFFSET_RC35Set_temp_offset; break; case HeatingCircuit::Mode::FLOWOFFSET: // change the offset of flowtemp offset = EMS_OFFSET_RC35Set_temp_flowoffset; factor = 1; break; case HeatingCircuit::Mode::DESIGN: if (hc->heatingtype == 3 && model == EMS_DEVICE_FLAG_RC35) { offset = EMS_OFFSET_RC35Set_temp_design_floor; } else { offset = EMS_OFFSET_RC35Set_temp_design; } factor = 1; break; case HeatingCircuit::Mode::SUMMER: offset = EMS_OFFSET_RC35Set_temp_summer; factor = 1; break; case HeatingCircuit::Mode::NOFROST: offset = EMS_OFFSET_RC35Set_temp_nofrost; factor = 1; break; case HeatingCircuit::Mode::ROOMINFLUENCE: offset = 4; factor = 1; break; case HeatingCircuit::Mode::NOREDUCE: offset = EMS_OFFSET_RC35Set_noreducetemp; factor = 1; break; case HeatingCircuit::Mode::REDUCE: offset = EMS_OFFSET_RC35Set_reducetemp; factor = 1; break; case HeatingCircuit::Mode::VACREDUCE: offset = EMS_OFFSET_RC35Set_vacreducetemp; factor = 1; break; case HeatingCircuit::Mode::TEMPAUTO: offset = EMS_OFFSET_RC35Set_seltemp; break; case HeatingCircuit::Mode::MINFLOW: offset = 16; factor = 1; break; case HeatingCircuit::Mode::MAXFLOW: if (hc->heatingtype == 3 && model == EMS_DEVICE_FLAG_RC35) { offset = 35; } else { offset = 15; } factor = 1; break; default: // automatic selection, if no type is defined, we use the standard code validate_typeid = monitor_typeids[hc->hc()]; //get setpoint roomtemp back uint8_t mode_ = hc->get_mode(); if (mode_ == HeatingCircuit::Mode::NIGHT) { offset = EMS_OFFSET_RC35Set_temp_night; } else if (mode_ == HeatingCircuit::Mode::DAY) { offset = EMS_OFFSET_RC35Set_temp_day; } else if (model == EMS_DEVICE_FLAG_RC35) { offset = EMS_OFFSET_RC35Set_seltemp; // https://github.com/emsesp/EMS-ESP/issues/310 } else { // RC30_N missing temporary auto temperature https://github.com/emsesp/EMS-ESP32/issues/395 uint8_t modetype = hc->get_mode_type(); offset = (modetype == HeatingCircuit::Mode::NIGHT) ? EMS_OFFSET_RC35Set_temp_night : EMS_OFFSET_RC35Set_temp_day; } break; } } else if (model == EMS_DEVICE_FLAG_JUNKERS) { // figure out if we have older or new thermostats, Heating Circuits on 0x65 or 0x79 // see https://github.com/emsesp/EMS-ESP/issues/335#issuecomment-593324716) bool old_junkers = (has_flags(EMS_DEVICE_FLAG_JUNKERS_OLD)); if (!old_junkers) { switch (mode) { case HeatingCircuit::Mode::NOFROST: offset = EMS_OFFSET_JunkersSetMessage_no_frost_temp; break; case HeatingCircuit::Mode::NIGHT: case HeatingCircuit::Mode::ECO: offset = EMS_OFFSET_JunkersSetMessage_night_temp; break; case HeatingCircuit::Mode::HEAT: case HeatingCircuit::Mode::DAY: offset = EMS_OFFSET_JunkersSetMessage_day_temp; break; default: // automatic selection, if no type is defined, we check mode and modetype uint8_t mode_ = hc->get_mode(); if (mode_ == HeatingCircuit::Mode::NIGHT || mode_ == HeatingCircuit::Mode::ECO) { offset = EMS_OFFSET_JunkersSetMessage_night_temp; } else if (mode_ == HeatingCircuit::Mode::DAY || mode_ == HeatingCircuit::Mode::HEAT) { offset = EMS_OFFSET_JunkersSetMessage_day_temp; } else if (mode_ == HeatingCircuit::Mode::NOFROST) { offset = EMS_OFFSET_JunkersSetMessage_no_frost_temp; } else { // auto mode, missing temporary parameter, use modetype https://github.com/emsesp/EMS-ESP32/issues/400 uint8_t modetype = hc->get_mode_type(); if (modetype == HeatingCircuit::Mode::NIGHT || modetype == HeatingCircuit::Mode::ECO) { offset = EMS_OFFSET_JunkersSetMessage_night_temp; } else if (modetype == HeatingCircuit::Mode::DAY || modetype == HeatingCircuit::Mode::HEAT) { offset = EMS_OFFSET_JunkersSetMessage_day_temp; } else { offset = EMS_OFFSET_JunkersSetMessage_no_frost_temp; } } break; } } else { // older, like the FR100 switch (mode) { case HeatingCircuit::Mode::NOFROST: offset = EMS_OFFSET_JunkersSetMessage2_no_frost_temp; break; case HeatingCircuit::Mode::ECO: case HeatingCircuit::Mode::NIGHT: offset = EMS_OFFSET_JunkersSetMessage2_eco_temp; break; case HeatingCircuit::Mode::HEAT: case HeatingCircuit::Mode::DAY: offset = EMS_OFFSET_JunkersSetMessage2_heat_temp; break; default: // automatic selection, if no type is defined, we check mode and modetype uint8_t mode_ = hc->get_mode(); if (mode_ == HeatingCircuit::Mode::NIGHT || mode_ == HeatingCircuit::Mode::ECO) { offset = EMS_OFFSET_JunkersSetMessage2_eco_temp; } else if (mode_ == HeatingCircuit::Mode::DAY || mode_ == HeatingCircuit::Mode::HEAT) { offset = EMS_OFFSET_JunkersSetMessage2_heat_temp; } else if (mode_ == HeatingCircuit::Mode::NOFROST) { offset = EMS_OFFSET_JunkersSetMessage2_no_frost_temp; } else { // auto mode, missing temporary parameter, use modetype https://github.com/emsesp/EMS-ESP32/issues/400 uint8_t modetype = hc->get_mode_type(); if (modetype == HeatingCircuit::Mode::NIGHT || modetype == HeatingCircuit::Mode::ECO) { offset = EMS_OFFSET_JunkersSetMessage2_eco_temp; } else if (modetype == HeatingCircuit::Mode::DAY || modetype == HeatingCircuit::Mode::HEAT) { offset = EMS_OFFSET_JunkersSetMessage2_heat_temp; } else { offset = EMS_OFFSET_JunkersSetMessage2_no_frost_temp; } } break; } } } // if we know what to send and to where, go and do it if (offset != -1) { // add the write command to the Tx queue. value is *2 // post validate is the corresponding monitor or set type IDs as they can differ per model write_command(set_typeid, offset, (uint8_t)(temperature * (float)factor), validate_typeid); return true; } return false; } bool Thermostat::set_temperature_value(const char * value, const int8_t id, const uint8_t mode, bool relative) { float f = 0; uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; if (Helpers::value2temperature(value, f, relative)) { return set_temperature(f, mode, hc_num); } else { return false; } } bool Thermostat::set_temp(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::AUTO); } bool Thermostat::set_nighttemp(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::NIGHT); } bool Thermostat::set_daytemp(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::DAY); } bool Thermostat::set_daylowtemp(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::DAYLOW); } bool Thermostat::set_daymidtemp(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::DAYMID); } bool Thermostat::set_comforttemp(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::COMFORT); } bool Thermostat::set_nofrosttemp(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::NOFROST); } bool Thermostat::set_ecotemp(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::ECO); } bool Thermostat::set_heattemp(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::HEAT); } bool Thermostat::set_summertemp(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::SUMMER); } bool Thermostat::set_designtemp(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::DESIGN); } bool Thermostat::set_offsettemp(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::OFFSET); } bool Thermostat::set_holidaytemp(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::HOLIDAY); } bool Thermostat::set_manualtemp(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::MANUAL); } bool Thermostat::set_tempautotemp(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::TEMPAUTO); } bool Thermostat::set_noreducetemp(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::NOREDUCE); } bool Thermostat::set_reducetemp(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::REDUCE); } bool Thermostat::set_vacreducetemp(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::VACREDUCE); } bool Thermostat::set_flowtempoffset(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::FLOWOFFSET, true); } bool Thermostat::set_maxflowtemp(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::MAXFLOW); } bool Thermostat::set_minflowtemp(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::MINFLOW); } bool Thermostat::set_roominfluence(const char * value, const int8_t id) { return set_temperature_value(value, id, HeatingCircuit::Mode::ROOMINFLUENCE, true); } // register main device values, top level for all thermostats (excluding heating circuits) // as these are done in void Thermostat::register_device_values_hc() void Thermostat::register_device_values() { // Common for all thermostats register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &errorCode_, DeviceValueType::STRING, nullptr, FL_(errorCode), DeviceValueUOM::NONE); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &lastCode_, DeviceValueType::STRING, nullptr, FL_(lastCode), DeviceValueUOM::NONE); switch (this->model()) { case EMS_DEVICE_FLAG_RC100: case EMS_DEVICE_FLAG_RC300: register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::STRING, FL_(tpl_datetime), FL_(dateTime), DeviceValueUOM::NONE, MAKE_CF_CB(set_datetime)); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &floordrystatus_, DeviceValueType::ENUM, FL_(enum_floordrystatus), FL_(floordrystatus), DeviceValueUOM::NONE); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &dampedoutdoortemp2_, DeviceValueType::SHORT, FL_(div10), FL_(dampedoutdoortemp), DeviceValueUOM::DEGREES); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &floordrytemp_, DeviceValueType::UINT, nullptr, FL_(floordrytemp), DeviceValueUOM::DEGREES); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &ibaBuildingType_, DeviceValueType::ENUM, FL_(enum_ibaBuildingType), FL_(ibaBuildingType), DeviceValueUOM::NONE, MAKE_CF_CB(set_building)); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &ibaMinExtTemperature_, DeviceValueType::INT, nullptr, FL_(ibaMinExtTemperature), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_minexttemp)); register_device_value( DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwSetTemp_, DeviceValueType::UINT, nullptr, FL_(wwSetTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwtemp)); register_device_value( DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwSetTempLow_, DeviceValueType::UINT, nullptr, FL_(wwSetTempLow), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwtemplow)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode), FL_(wwCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwcircmode)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwChargeDuration_, DeviceValueType::UINT, FL_(mul15), FL_(wwChargeDuration), DeviceValueUOM::MINUTES, MAKE_CF_CB(set_wwchargeduration)); register_device_value( DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwCharge_, DeviceValueType::BOOL, nullptr, FL_(wwCharge), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwcharge)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwExtra1_, DeviceValueType::UINT, nullptr, FL_(wwExtra1), DeviceValueUOM::DEGREES); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwExtra2_, DeviceValueType::UINT, nullptr, FL_(wwExtra2), DeviceValueUOM::DEGREES); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwDisinfecting_, DeviceValueType::BOOL, nullptr, FL_(wwDisinfecting), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwDisinfect)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwDisinfectDay_, DeviceValueType::ENUM, FL_(enum_dayOfWeek), FL_(wwDisinfectDay), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwDisinfectDay)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwDisinfectHour_, DeviceValueType::UINT, FL_(mul15), FL_(wwDisinfectTime), DeviceValueUOM::MINUTES, MAKE_CF_CB(set_wwDisinfectHour), 0, 1431); break; case EMS_DEVICE_FLAG_RC10: register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &ibaCalIntTemperature_, DeviceValueType::INT, FL_(div10), FL_(ibaCalIntTemperature), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_calinttemp)); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &heatingpid_, DeviceValueType::ENUM, FL_(enum_PID), FL_(heatingPID), DeviceValueUOM::NONE, MAKE_CF_CB(set_heatingpid)); register_device_value( DeviceValueTAG::TAG_THERMOSTAT_DATA, &backlight_, DeviceValueType::BOOL, nullptr, FL_(backlight), DeviceValueUOM::NONE, MAKE_CF_CB(set_backlight)); register_device_value( DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode3), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode)); break; case EMS_DEVICE_FLAG_RC20_N: case EMS_DEVICE_FLAG_RC25: register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::STRING, nullptr, FL_(dateTime), DeviceValueUOM::NONE); // can't set datetime register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &ibaMinExtTemperature_, DeviceValueType::INT, nullptr, FL_(ibaMinExtTemperature), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_minexttemp)); break; case EMS_DEVICE_FLAG_RC20: register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::STRING, nullptr, FL_(dateTime), DeviceValueUOM::NONE); // can't set datetime break; case EMS_DEVICE_FLAG_RC30: register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::STRING, nullptr, FL_(dateTime), DeviceValueUOM::NONE); // can't set datetime register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &ibaClockOffset_, DeviceValueType::INT, nullptr, FL_(ibaClockOffset), DeviceValueUOM::SECONDS, MAKE_CF_CB(set_clockoffset)); // offset (in sec) to clock, 0xff=-1s, 0x02=2s register_device_value( DeviceValueTAG::TAG_THERMOSTAT_DATA, &autodst_, DeviceValueType::BOOL, nullptr, FL_(autodst), DeviceValueUOM::NONE, MAKE_CF_CB(set_autodst)); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &ibaLanguage_, DeviceValueType::ENUM, FL_(enum_ibaLanguage_RC30), FL_(ibaLanguage), DeviceValueUOM::NONE, MAKE_CF_CB(set_language)); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &ibaMainDisplay_, DeviceValueType::ENUM, FL_(enum_ibaMainDisplay), FL_(ibaMainDisplay), DeviceValueUOM::NONE); register_device_value( DeviceValueTAG::TAG_THERMOSTAT_DATA, &backlight_, DeviceValueType::BOOL, nullptr, FL_(backlight), DeviceValueUOM::NONE, MAKE_CF_CB(set_backlight)); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &brightness_, DeviceValueType::INT, nullptr, FL_(brightness), DeviceValueUOM::NONE, MAKE_CF_CB(set_brightness), -15, 15); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &mixingvalves_, DeviceValueType::UINT, nullptr, FL_(mixingvalves), DeviceValueUOM::NONE, MAKE_CF_CB(set_mixingvalves), 0, 2); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &ibaBuildingType_, DeviceValueType::ENUM, FL_(enum_ibaBuildingType), FL_(ibaBuildingType), DeviceValueUOM::NONE, MAKE_CF_CB(set_building)); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &heatingpid_, DeviceValueType::ENUM, FL_(enum_PID), FL_(heatingPID), DeviceValueUOM::NONE, MAKE_CF_CB(set_heatingpid)); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &preheating_, DeviceValueType::BOOL, nullptr, FL_(preheating), DeviceValueUOM::NONE, MAKE_CF_CB(set_preheating)); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &ibaCalIntTemperature_, DeviceValueType::INT, FL_(div10), FL_(ibaCalIntTemperature), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_calinttemp)); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &offtemp_, DeviceValueType::UINT, FL_(div2), FL_(offtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_offtemp), 5, 30); register_device_value( DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode3), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwWhenModeOff_, DeviceValueType::BOOL, nullptr, FL_(wwWhenModeOff), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwwhenmodeoff)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwDisinfecting_, DeviceValueType::BOOL, nullptr, FL_(wwDisinfecting), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwDisinfect)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwDisinfectDay_, DeviceValueType::ENUM, FL_(enum_dayOfWeek), FL_(wwDisinfectDay), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwDisinfectDay)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwDisinfectHour_, DeviceValueType::UINT, nullptr, FL_(wwDisinfectHour), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwDisinfectHour), 0, 23); break; case EMS_DEVICE_FLAG_RC30_N: register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::STRING, nullptr, FL_(dateTime), DeviceValueUOM::NONE); // can't set datetime register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &ibaMainDisplay_, DeviceValueType::ENUM, FL_(enum_ibaMainDisplay), FL_(ibaMainDisplay), DeviceValueUOM::NONE); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &ibaLanguage_, DeviceValueType::ENUM, FL_(enum_ibaLanguage), FL_(ibaLanguage), DeviceValueUOM::NONE); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &ibaClockOffset_, DeviceValueType::INT, nullptr, FL_(ibaClockOffset), DeviceValueUOM::SECONDS); // offset (in sec) to clock, 0xff=-1s, 0x02=2s register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &ibaCalIntTemperature_, DeviceValueType::INT, FL_(div10), FL_(ibaCalIntTemperature), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_calinttemp)); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &ibaMinExtTemperature_, DeviceValueType::INT, nullptr, FL_(ibaMinExtTemperature), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_minexttemp)); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &dampedoutdoortemp_, DeviceValueType::INT, nullptr, FL_(dampedoutdoortemp), DeviceValueUOM::DEGREES); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &ibaBuildingType_, DeviceValueType::ENUM, FL_(enum_ibaBuildingType), FL_(ibaBuildingType), DeviceValueUOM::NONE, MAKE_CF_CB(set_building)); register_device_value( DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode2), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwMode2), FL_(wwCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwcircmode)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwProgMode_, DeviceValueType::ENUM, FL_(enum_wwProgMode), FL_(wwProgMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwProgMode)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwCircProg_, DeviceValueType::ENUM, FL_(enum_wwProgMode), FL_(wwCircProg), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwCircProg)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwDisinfecting_, DeviceValueType::BOOL, nullptr, FL_(wwDisinfecting), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwDisinfect)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwDisinfectDay_, DeviceValueType::ENUM, FL_(enum_dayOfWeek), FL_(wwDisinfectDay), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwDisinfectDay)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwDisinfectHour_, DeviceValueType::UINT, nullptr, FL_(wwDisinfectHour), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwDisinfectHour), 0, 23); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwMaxTemp_, DeviceValueType::UINT, nullptr, FL_(wwMaxTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwMaxTemp)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwOneTimeKey_, DeviceValueType::BOOL, nullptr, FL_(wwOneTimeKey), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwOneTimeKey)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwSwitchTime_, DeviceValueType::STRING, FL_(tpl_switchtime), FL_(wwswitchtime), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwSwitchTime)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwCircSwitchTime_, DeviceValueType::STRING, FL_(tpl_switchtime), FL_(wwcircswitchtime), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwCircSwitchTime)); break; case EMS_DEVICE_FLAG_RC35: register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::STRING, FL_(tpl_datetime), FL_(dateTime), DeviceValueUOM::NONE, MAKE_CF_CB(set_datetime)); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &ibaCalIntTemperature_, DeviceValueType::INT, FL_(div10), FL_(ibaCalIntTemperature), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_calinttemp), -5, 5); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &ibaMinExtTemperature_, DeviceValueType::INT, nullptr, FL_(ibaMinExtTemperature), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_minexttemp)); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &tempsensor1_, DeviceValueType::USHORT, FL_(div10), FL_(tempsensor1), DeviceValueUOM::DEGREES); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &tempsensor2_, DeviceValueType::USHORT, FL_(div10), FL_(tempsensor2), DeviceValueUOM::DEGREES); register_device_value( DeviceValueTAG::TAG_THERMOSTAT_DATA, &ibaDamping_, DeviceValueType::BOOL, nullptr, FL_(damping), DeviceValueUOM::NONE, MAKE_CF_CB(set_damping)); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &dampedoutdoortemp_, DeviceValueType::INT, nullptr, FL_(dampedoutdoortemp), DeviceValueUOM::DEGREES); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &ibaBuildingType_, DeviceValueType::ENUM, FL_(enum_ibaBuildingType), FL_(ibaBuildingType), DeviceValueUOM::NONE, MAKE_CF_CB(set_building)); register_device_value( DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode2), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwMode2), FL_(wwCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwcircmode)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwProgMode_, DeviceValueType::ENUM, FL_(enum_wwProgMode), FL_(wwProgMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwProgMode)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwCircProg_, DeviceValueType::ENUM, FL_(enum_wwProgMode), FL_(wwCircProg), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwCircProg)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwDisinfecting_, DeviceValueType::BOOL, nullptr, FL_(wwDisinfecting), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwDisinfect)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwDisinfectDay_, DeviceValueType::ENUM, FL_(enum_dayOfWeek), FL_(wwDisinfectDay), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwDisinfectDay)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwDisinfectHour_, DeviceValueType::UINT, nullptr, FL_(wwDisinfectHour), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwDisinfectHour), 0, 23); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwMaxTemp_, DeviceValueType::UINT, nullptr, FL_(wwMaxTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwMaxTemp)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwOneTimeKey_, DeviceValueType::BOOL, nullptr, FL_(wwOneTimeKey), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwOneTimeKey)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwSwitchTime_, DeviceValueType::STRING, FL_(tpl_switchtime), FL_(wwswitchtime), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwSwitchTime)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwCircSwitchTime_, DeviceValueType::STRING, FL_(tpl_switchtime), FL_(wwcircswitchtime), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwCircSwitchTime)); break; case EMS_DEVICE_FLAG_JUNKERS: register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::STRING, FL_(tpl_datetime), FL_(dateTime), DeviceValueUOM::NONE, MAKE_CF_CB(set_datetime)); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &hybridStrategy_, DeviceValueType::ENUM, FL_(enum_hybridStrategy), FL_(hybridStrategy), DeviceValueUOM::NONE, MAKE_CF_CB(set_hybridStrategy)); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &switchOverTemp_, DeviceValueType::INT, nullptr, FL_(switchOverTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_switchOverTemp), -20, 20); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &energyCostRatio_, DeviceValueType::UINT, FL_(div10), FL_(energyCostRatio), DeviceValueUOM::NONE, MAKE_CF_CB(set_energyCostRatio), 0, 20); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &fossileFactor_, DeviceValueType::UINT, FL_(div10), FL_(fossileFactor), DeviceValueUOM::NONE, MAKE_CF_CB(set_fossileFactor), 0, 5); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &electricFactor_, DeviceValueType::UINT, FL_(div10), FL_(electricFactor), DeviceValueUOM::NONE, MAKE_CF_CB(set_electricFactor), 0, 5); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &delayBoiler_, DeviceValueType::UINT, nullptr, FL_(delayBoiler), DeviceValueUOM::MINUTES, MAKE_CF_CB(set_delayBoiler), 5, 120); register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &tempDiffBoiler_, DeviceValueType::UINT, nullptr, FL_(tempDiffBoiler), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_tempDiffBoiler), 1, 99); break; case EMS_DEVICE_FLAG_EASY: // Easy TC100 have no date/time, see issue #100, not sure about CT200, so leave it. register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::STRING, nullptr, FL_(dateTime), DeviceValueUOM::NONE); // can't set datetime break; case EMS_DEVICE_FLAG_CRF: default: register_device_value(DeviceValueTAG::TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::STRING, nullptr, FL_(dateTime), DeviceValueUOM::NONE); // can't set datetime break; } } // registers the values for a heating circuit void Thermostat::register_device_values_hc(std::shared_ptr hc) { uint8_t model = hc->get_model(); // heating circuit uint8_t tag = DeviceValueTAG::TAG_HC1 + hc->hc(); // different logic on how temperature values are stored, depending on model const __FlashStringHelper * const * seltemp_divider; const __FlashStringHelper * const * roomtemp_divider; if (model == EMS_DEVICE_FLAG_EASY) { seltemp_divider = FL_(div100); roomtemp_divider = FL_(div100); } else if (model == EMS_DEVICE_FLAG_JUNKERS) { seltemp_divider = FL_(div10); roomtemp_divider = FL_(div10); } else { seltemp_divider = FL_(div2); roomtemp_divider = FL_(div10); } if (has_flags(EMS_DEVICE_FLAG_NO_WRITE) || device_id() != EMSESP::actual_master_thermostat()) { register_device_value(tag, &hc->selTemp, DeviceValueType::SHORT, seltemp_divider, FL_(selRoomTemp), DeviceValueUOM::DEGREES); } else { register_device_value(tag, &hc->selTemp, DeviceValueType::SHORT, seltemp_divider, FL_(selRoomTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_temp), 0, 29); } register_device_value(tag, &hc->roomTemp, DeviceValueType::SHORT, roomtemp_divider, FL_(roomTemp), DeviceValueUOM::DEGREES); if (device_id() != EMSESP::actual_master_thermostat()) { return; } register_device_value(tag, &hc->climate, DeviceValueType::ENUM, FL_(enum_climate), FL_(climate), DeviceValueUOM::NONE); switch (model) { case EMS_DEVICE_FLAG_RC10: register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode6), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode)); register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), FL_(daytemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daytemp)); register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), FL_(nighttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nighttemp)); register_device_value(tag, &hc->reducehours, DeviceValueType::UINT, nullptr, FL_(reducehours), DeviceValueUOM::HOURS, MAKE_CF_CB(set_reducehours)); register_device_value(tag, &hc->reduceminutes, DeviceValueType::USHORT, nullptr, FL_(reduceminutes), DeviceValueUOM::MINUTES); break; case EMS_DEVICE_FLAG_RC100: case EMS_DEVICE_FLAG_RC300: register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode)); register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype), FL_(modetype), DeviceValueUOM::NONE); register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), FL_(ecotemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_ecotemp)); register_device_value(tag, &hc->manualtemp, DeviceValueType::UINT, FL_(div2), FL_(manualtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_manualtemp)); register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), FL_(comforttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_comforttemp)); register_device_value(tag, &hc->summertemp, DeviceValueType::UINT, nullptr, FL_(summertemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_summertemp)); register_device_value(tag, &hc->designtemp, DeviceValueType::UINT, nullptr, FL_(designtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_designtemp)); register_device_value(tag, &hc->offsettemp, DeviceValueType::INT, nullptr, FL_(offsettemp), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_offsettemp)); register_device_value(tag, &hc->minflowtemp, DeviceValueType::UINT, nullptr, FL_(minflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_minflowtemp)); register_device_value(tag, &hc->maxflowtemp, DeviceValueType::UINT, nullptr, FL_(maxflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_maxflowtemp)); register_device_value(tag, &hc->roominfluence, DeviceValueType::UINT, nullptr, FL_(roominfluence), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_roominfluence)); register_device_value( tag, &hc->roominfl_factor, DeviceValueType::UINT, FL_(div10), FL_(roominfl_factor), DeviceValueUOM::NONE, MAKE_CF_CB(set_roominfl_factor)); register_device_value(tag, &hc->curroominfl, DeviceValueType::SHORT, FL_(div10), FL_(curroominfl), DeviceValueUOM::DEGREES_R); register_device_value(tag, &hc->nofrosttemp, DeviceValueType::INT, nullptr, FL_(nofrosttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nofrosttemp)); register_device_value(tag, &hc->targetflowtemp, DeviceValueType::UINT, nullptr, FL_(targetflowtemp), DeviceValueUOM::DEGREES); register_device_value( tag, &hc->heatingtype, DeviceValueType::ENUM, FL_(enum_heatingtype), FL_(heatingtype), DeviceValueUOM::NONE, MAKE_CF_CB(set_heatingtype)); register_device_value( tag, &hc->summersetmode, DeviceValueType::ENUM, FL_(enum_summermode), FL_(summersetmode), DeviceValueUOM::NONE, MAKE_CF_CB(set_summermode)); register_device_value(tag, &hc->hpoperatingmode, DeviceValueType::ENUM, FL_(enum_hpoperatingmode), FL_(hpoperatingmode), DeviceValueUOM::NONE, MAKE_CF_CB(set_summermode)); register_device_value(tag, &hc->summermode, DeviceValueType::ENUM, FL_(enum_summer), FL_(summermode), DeviceValueUOM::NONE); register_device_value( tag, &hc->controlmode, DeviceValueType::ENUM, FL_(enum_controlmode), FL_(controlmode), DeviceValueUOM::NONE, MAKE_CF_CB(set_controlmode)); register_device_value(tag, &hc->program, DeviceValueType::ENUM, FL_(enum_progMode), FL_(program), DeviceValueUOM::NONE, MAKE_CF_CB(set_program)); register_device_value( tag, &hc->tempautotemp, DeviceValueType::INT, FL_(div2), FL_(tempautotemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_tempautotemp), -1, 30); register_device_value(tag, &hc->fastHeatup, DeviceValueType::UINT, nullptr, FL_(fastheatup), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_fastheatup)); register_device_value(tag, &hc->reducemode1, DeviceValueType::ENUM, FL_(enum_reducemode1), FL_(reducemode1), DeviceValueUOM::NONE, MAKE_CF_CB(set_reducemode1)); register_device_value(tag, &hc->noreducetemp, DeviceValueType::INT, nullptr, FL_(noreducetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_noreducetemp)); register_device_value(tag, &hc->reducetemp, DeviceValueType::INT, nullptr, FL_(reducetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_reducetemp)); break; case EMS_DEVICE_FLAG_CRF: register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode5), FL_(mode), DeviceValueUOM::NONE); register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype5), FL_(modetype), DeviceValueUOM::NONE); register_device_value(tag, &hc->targetflowtemp, DeviceValueType::UINT, nullptr, FL_(targetflowtemp), DeviceValueUOM::DEGREES); break; case EMS_DEVICE_FLAG_RC20: register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode2), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode)); register_device_value(tag, &hc->manualtemp, DeviceValueType::UINT, FL_(div2), FL_(manualtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_manualtemp)); register_device_value(tag, &hc->daylowtemp, DeviceValueType::UINT, FL_(div2), FL_(daylowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daylowtemp)); register_device_value(tag, &hc->daymidtemp, DeviceValueType::UINT, FL_(div2), FL_(daymidtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daymidtemp)); register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), FL_(dayhightemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daytemp)); register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), FL_(nighttemp2), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nighttemp)); register_device_value( tag, &hc->switchtime1, DeviceValueType::STRING, FL_(tpl_switchtime1), FL_(switchtime), DeviceValueUOM::NONE, MAKE_CF_CB(set_switchtime1)); break; case EMS_DEVICE_FLAG_RC20_N: register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode2), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode)); register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype3), FL_(modetype), DeviceValueUOM::NONE); register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), FL_(daytemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daytemp)); register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), FL_(nighttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nighttemp)); register_device_value(tag, &hc->program, DeviceValueType::ENUM, FL_(enum_progMode3), FL_(program), DeviceValueUOM::NONE, MAKE_CF_CB(set_program)); register_device_value(tag, &hc->minflowtemp, DeviceValueType::UINT, nullptr, FL_(minflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_minflowtemp)); register_device_value(tag, &hc->maxflowtemp, DeviceValueType::UINT, nullptr, FL_(maxflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_maxflowtemp)); register_device_value(tag, &hc->tempautotemp, DeviceValueType::UINT, FL_(div2), FL_(tempautotemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_tempautotemp)); register_device_value( tag, &hc->heatingtype, DeviceValueType::ENUM, FL_(enum_heatingtype), FL_(heatingtype), DeviceValueUOM::NONE, MAKE_CF_CB(set_heatingtype)); register_device_value(tag, &hc->summertemp, DeviceValueType::UINT, nullptr, FL_(summertemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_summertemp)); register_device_value(tag, &hc->summermode, DeviceValueType::ENUM, FL_(enum_summer), FL_(summermode), DeviceValueUOM::NONE); register_device_value(tag, &hc->remotetemp, DeviceValueType::SHORT, FL_(div10), FL_(remotetemp), DeviceValueUOM::DEGREES); break; case EMS_DEVICE_FLAG_RC25: register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode3), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode)); register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype3), FL_(modetype), DeviceValueUOM::NONE); register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), FL_(daytemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daytemp)); register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), FL_(nighttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nighttemp)); register_device_value(tag, &hc->program, DeviceValueType::ENUM, FL_(enum_progMode3), FL_(program), DeviceValueUOM::NONE, MAKE_CF_CB(set_program)); register_device_value(tag, &hc->minflowtemp, DeviceValueType::UINT, nullptr, FL_(minflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_minflowtemp)); register_device_value(tag, &hc->maxflowtemp, DeviceValueType::UINT, nullptr, FL_(maxflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_maxflowtemp)); register_device_value(tag, &hc->tempautotemp, DeviceValueType::UINT, FL_(div2), FL_(tempautotemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_tempautotemp)); register_device_value( tag, &hc->heatingtype, DeviceValueType::ENUM, FL_(enum_heatingtype), FL_(heatingtype), DeviceValueUOM::NONE, MAKE_CF_CB(set_heatingtype)); register_device_value(tag, &hc->summertemp, DeviceValueType::UINT, nullptr, FL_(summertemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_summertemp)); register_device_value(tag, &hc->summermode, DeviceValueType::ENUM, FL_(enum_summer), FL_(summermode), DeviceValueUOM::NONE); break; case EMS_DEVICE_FLAG_RC30: register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode3), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode)); register_device_value(tag, &hc->holiday, DeviceValueType::STRING, FL_(tpl_holidays), FL_(holidays), DeviceValueUOM::NONE, MAKE_CF_CB(set_holiday)); register_device_value(tag, &hc->vacation, DeviceValueType::STRING, FL_(tpl_holidays), FL_(vacations), DeviceValueUOM::NONE, MAKE_CF_CB(set_vacation)); register_device_value(tag, &hc->program, DeviceValueType::ENUM, FL_(enum_progMode2), FL_(program), DeviceValueUOM::NONE, MAKE_CF_CB(set_program)); register_device_value(tag, &hc->pause, DeviceValueType::UINT, nullptr, FL_(pause), DeviceValueUOM::HOURS, MAKE_CF_CB(set_pause)); register_device_value(tag, &hc->party, DeviceValueType::UINT, nullptr, FL_(party), DeviceValueUOM::HOURS, MAKE_CF_CB(set_party)); register_device_value( tag, &hc->switchtime1, DeviceValueType::STRING, FL_(tpl_switchtime1), FL_(switchtime1), DeviceValueUOM::NONE, MAKE_CF_CB(set_switchtime1)); register_device_value( tag, &hc->heatingtype, DeviceValueType::ENUM, FL_(enum_heatingtype), FL_(heatingtype), DeviceValueUOM::NONE, MAKE_CF_CB(set_heatingtype)); register_device_value( tag, &hc->controlmode, DeviceValueType::ENUM, FL_(enum_controlmode2), FL_(controlmode), DeviceValueUOM::NONE, MAKE_CF_CB(set_controlmode)); register_device_value(tag, &hc->holidaytemp, DeviceValueType::UINT, FL_(div2), FL_(holidaytemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_holidaytemp)); register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), FL_(nighttemp2), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nighttemp)); register_device_value(tag, &hc->daylowtemp, DeviceValueType::UINT, FL_(div2), FL_(daylowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daylowtemp)); register_device_value(tag, &hc->daymidtemp, DeviceValueType::UINT, FL_(div2), FL_(daymidtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daymidtemp)); register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), FL_(dayhightemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daytemp)); break; case EMS_DEVICE_FLAG_RC30_N: case EMS_DEVICE_FLAG_RC35: register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode3), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode)); register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype3), FL_(modetype), DeviceValueUOM::NONE); register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), FL_(daytemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daytemp)); register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), FL_(nighttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nighttemp)); register_device_value(tag, &hc->designtemp, DeviceValueType::UINT, nullptr, FL_(designtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_designtemp)); register_device_value(tag, &hc->offsettemp, DeviceValueType::INT, FL_(div2), FL_(offsettemp), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_offsettemp)); register_device_value(tag, &hc->holidaytemp, DeviceValueType::UINT, FL_(div2), FL_(holidaytemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_holidaytemp)); register_device_value(tag, &hc->targetflowtemp, DeviceValueType::UINT, nullptr, FL_(targetflowtemp), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->summertemp, DeviceValueType::UINT, nullptr, FL_(summertemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_summertemp)); register_device_value(tag, &hc->summermode, DeviceValueType::ENUM, FL_(enum_summer), FL_(summermode), DeviceValueUOM::NONE); register_device_value(tag, &hc->holidaymode, DeviceValueType::BOOL, nullptr, FL_(holidaymode), DeviceValueUOM::NONE); register_device_value(tag, &hc->nofrosttemp, DeviceValueType::INT, nullptr, FL_(nofrosttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nofrosttemp)); register_device_value( tag, &hc->nofrostmode, DeviceValueType::ENUM, FL_(enum_nofrostmode), FL_(nofrostmode), DeviceValueUOM::NONE, MAKE_CF_CB(set_nofrostmode)); register_device_value(tag, &hc->roominfluence, DeviceValueType::UINT, nullptr, FL_(roominfluence), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_roominfluence)); register_device_value(tag, &hc->minflowtemp, DeviceValueType::UINT, nullptr, FL_(minflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_minflowtemp)); register_device_value(tag, &hc->maxflowtemp, DeviceValueType::UINT, nullptr, FL_(maxflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_maxflowtemp)); register_device_value( tag, &hc->flowtempoffset, DeviceValueType::UINT, nullptr, FL_(flowtempoffset), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_flowtempoffset)); register_device_value( tag, &hc->heatingtype, DeviceValueType::ENUM, FL_(enum_heatingtype), FL_(heatingtype), DeviceValueUOM::NONE, MAKE_CF_CB(set_heatingtype)); register_device_value(tag, &hc->reducemode, DeviceValueType::ENUM, FL_(enum_reducemode), FL_(reducemode), DeviceValueUOM::NONE, MAKE_CF_CB(set_reducemode)); register_device_value( tag, &hc->controlmode, DeviceValueType::ENUM, FL_(enum_controlmode2), FL_(controlmode), DeviceValueUOM::NONE, MAKE_CF_CB(set_controlmode)); register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_control), FL_(control), DeviceValueUOM::NONE, MAKE_CF_CB(set_control)); register_device_value(tag, &hc->holiday, DeviceValueType::STRING, FL_(tpl_holidays), FL_(holidays), DeviceValueUOM::NONE, MAKE_CF_CB(set_holiday)); register_device_value(tag, &hc->vacation, DeviceValueType::STRING, FL_(tpl_holidays), FL_(vacations), DeviceValueUOM::NONE, MAKE_CF_CB(set_vacation)); register_device_value(tag, &hc->program, DeviceValueType::ENUM, FL_(enum_progMode2), FL_(program), DeviceValueUOM::NONE, MAKE_CF_CB(set_program)); register_device_value(tag, &hc->pause, DeviceValueType::UINT, nullptr, FL_(pause), DeviceValueUOM::HOURS, MAKE_CF_CB(set_pause)); register_device_value(tag, &hc->party, DeviceValueType::UINT, nullptr, FL_(party), DeviceValueUOM::HOURS, MAKE_CF_CB(set_party)); register_device_value(tag, &hc->tempautotemp, DeviceValueType::UINT, FL_(div2), FL_(tempautotemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_tempautotemp)); register_device_value(tag, &hc->noreducetemp, DeviceValueType::INT, nullptr, FL_(noreducetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_noreducetemp)); register_device_value(tag, &hc->reducetemp, DeviceValueType::INT, nullptr, FL_(reducetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_reducetemp)); register_device_value(tag, &hc->vacreducetemp, DeviceValueType::INT, nullptr, FL_(vacreducetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_vacreducetemp)); register_device_value( tag, &hc->vacreducemode, DeviceValueType::ENUM, FL_(enum_reducemode), FL_(vacreducemode), DeviceValueUOM::NONE, MAKE_CF_CB(set_vacreducemode)); register_device_value(tag, &hc->remotetemp, DeviceValueType::SHORT, FL_(div10), FL_(remotetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_remotetemp)); register_device_value(tag, &hc->wwprio, DeviceValueType::BOOL, nullptr, FL_(wwprio), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwprio)); register_device_value( tag, &hc->switchtime1, DeviceValueType::STRING, FL_(tpl_switchtime), FL_(switchtime1), DeviceValueUOM::NONE, MAKE_CF_CB(set_switchtime1)); register_device_value( tag, &hc->switchtime2, DeviceValueType::STRING, FL_(tpl_switchtime), FL_(switchtime2), DeviceValueUOM::NONE, MAKE_CF_CB(set_switchtime2)); break; case EMS_DEVICE_FLAG_JUNKERS: register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode4), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode)); register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype4), FL_(modetype), DeviceValueUOM::NONE); register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), FL_(heattemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_heattemp)); register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), FL_(ecotemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_ecotemp)); register_device_value(tag, &hc->nofrosttemp, DeviceValueType::INT, FL_(div2), FL_(nofrosttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nofrosttemp)); register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_j_control), FL_(control), DeviceValueUOM::NONE, MAKE_CF_CB(set_control)); register_device_value(tag, &hc->program, DeviceValueType::ENUM, FL_(enum_progMode4), FL_(program), DeviceValueUOM::NONE, MAKE_CF_CB(set_program)); register_device_value(tag, &hc->remotetemp, DeviceValueType::SHORT, FL_(div10), FL_(remotetemp), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->targetflowtemp, DeviceValueType::UINT, nullptr, FL_(targetflowtemp), DeviceValueUOM::DEGREES); break; default: break; } } } // namespace emsesp