Merge branch 'dev_' into dev_no_master_thermostat

This commit is contained in:
MichaelDvP
2022-04-14 07:27:07 +02:00
55 changed files with 3607 additions and 2542 deletions

View File

@@ -281,7 +281,7 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char *
return_code = ((cf->cmdfunction_json_)(value, id, output)) ? CommandRet::OK : CommandRet::ERROR;
}
if (cf->cmdfunction_) {
if (cf->cmdfunction_ && !EMSESP::cmd_is_readonly(device_type, cmd, id)) {
return_code = ((cf->cmdfunction_)(value, id)) ? CommandRet::OK : CommandRet::ERROR;
}

View File

@@ -139,13 +139,10 @@ class Command {
}
};
using KeyValueMap_t = std::unordered_map<std::string, std::string>;
using Folder_t = std::vector<std::string>;
class SUrlParser {
private:
KeyValueMap_t m_keysvalues;
Folder_t m_folders;
std::unordered_map<std::string, std::string> m_keysvalues;
std::vector<std::string> m_folders;
public:
SUrlParser() = default;
@@ -153,11 +150,11 @@ class SUrlParser {
bool parse(const char * url);
Folder_t & paths() {
std::vector<std::string> & paths() {
return m_folders;
};
KeyValueMap_t & params() {
std::unordered_map<std::string, std::string> & params() {
return m_keysvalues;
};

View File

@@ -36,6 +36,7 @@
{132, DeviceType::BOILER, F("GC7000F"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{133, DeviceType::BOILER, F("Logano GB125/KB195i/Logamatic MC110"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{167, DeviceType::BOILER, F("Cerapur Aero"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{168, DeviceType::BOILER, F("Hybrid Heatpump"), DeviceFlags::EMS_DEVICE_FLAG_HYBRID},
{170, DeviceType::BOILER, F("Logano GB212"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{172, DeviceType::BOILER, F("Enviline/Compress 6000AW/Hybrid 7000iAW/SupraEco/Geo 5xx"), DeviceFlags::EMS_DEVICE_FLAG_HEATPUMP},
{173, DeviceType::BOILER, F("Geo 5xx"), DeviceFlags::EMS_DEVICE_FLAG_HEATPUMP},
@@ -56,6 +57,7 @@
{114, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{125, DeviceType::CONTROLLER, F("BC25"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{152, DeviceType::CONTROLLER, F("Controller"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{168, DeviceType::CONTROLLER, F("Hybrid Heatpump"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{169, DeviceType::CONTROLLER, F("BC40"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{190, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{194, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
@@ -90,6 +92,7 @@
{165, DeviceType::THERMOSTAT, F("RC100/Moduline 1000/1010"), DeviceFlags::EMS_DEVICE_FLAG_RC100}, // 0x18, 0x38
{172, DeviceType::THERMOSTAT, F("Rego 2000/3000"), DeviceFlags::EMS_DEVICE_FLAG_RC300}, // 0x10
{216, DeviceType::THERMOSTAT, F("CRF200S"), DeviceFlags::EMS_DEVICE_FLAG_CRF | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18
{246, DeviceType::THERMOSTAT, F("Comfort+2RF"), DeviceFlags::EMS_DEVICE_FLAG_CRF | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18
// Thermostat - Sieger - 0x10 / 0x17
{ 66, DeviceType::THERMOSTAT, F("ES72/RC20"), DeviceFlags::EMS_DEVICE_FLAG_RC20_N}, // 0x17 or remote
@@ -108,15 +111,17 @@
{191, DeviceType::THERMOSTAT, F("FR120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_OLD}, // older model
{192, DeviceType::THERMOSTAT, F("FW120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},
// Solar Modules - 0x30, 0x2A (for ww)
// Solar Modules - 0x30 (for solar), 0x2A, 0x41 (for ww)
{ 73, DeviceType::SOLAR, F("SM10"), DeviceFlags::EMS_DEVICE_FLAG_SM10},
{101, DeviceType::SOLAR, F("ISM1"), DeviceFlags::EMS_DEVICE_FLAG_ISM},
{103, DeviceType::SOLAR, F("ISM2"), DeviceFlags::EMS_DEVICE_FLAG_ISM},
{162, DeviceType::SOLAR, F("SM50"), DeviceFlags::EMS_DEVICE_FLAG_SM100},
{163, DeviceType::SOLAR, F("SM100/MS100"), DeviceFlags::EMS_DEVICE_FLAG_SM100},
{164, DeviceType::SOLAR, F("SM200/MS200"), DeviceFlags::EMS_DEVICE_FLAG_SM100},
// Mixer Modules - 0x20-0x27 for HC, 0x28-0x29 for WWC and 0x11 for the MP100
{ 69, DeviceType::MIXER, F("MM10"), DeviceFlags::EMS_DEVICE_FLAG_MM10},
{100, DeviceType::MIXER, F("IPM"), DeviceFlags::EMS_DEVICE_FLAG_IPM},
{102, DeviceType::MIXER, F("IPM"), DeviceFlags::EMS_DEVICE_FLAG_IPM},
{159, DeviceType::MIXER, F("MM50"), DeviceFlags::EMS_DEVICE_FLAG_MMPLUS},
{160, DeviceType::MIXER, F("MM100"), DeviceFlags::EMS_DEVICE_FLAG_MMPLUS},
@@ -134,6 +139,7 @@
// Wireless sensor base - 0x50
{236, DeviceType::CONNECT, F("Wireless sensor base"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{238, DeviceType::CONNECT, F("Wireless sensor base"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
// Switches - 0x11
{ 71, DeviceType::SWITCH, F("WM10"), DeviceFlags::EMS_DEVICE_FLAG_NONE},

View File

@@ -48,6 +48,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
// the telegram handlers...
// common for all boilers
register_telegram_type(0xBF, F("ErrorMessage"), false, MAKE_PF_CB(process_ErrorMessage));
register_telegram_type(0x10, F("UBAErrorMessage1"), false, MAKE_PF_CB(process_UBAErrorMessage));
register_telegram_type(0x11, F("UBAErrorMessage2"), false, MAKE_PF_CB(process_UBAErrorMessage));
register_telegram_type(0xC2, F("UBAErrorMessage3"), false, MAKE_PF_CB(process_UBAErrorMessage2));
@@ -70,7 +71,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
}
// only EMS+
if (model() != EMSdevice::EMS_DEVICE_FLAG_EMS && model() != EMSdevice::EMS_DEVICE_FLAG_HT3) {
if (model() != EMSdevice::EMS_DEVICE_FLAG_EMS && model() != EMSdevice::EMS_DEVICE_FLAG_HT3 && model() != EMSdevice::EMS_DEVICE_FLAG_HYBRID) {
register_telegram_type(0xD1, F("UBAOutdoorTemp"), false, MAKE_PF_CB(process_UBAOutdoorTemp));
register_telegram_type(0xE3, F("UBAMonitorSlowPlus2"), false, MAKE_PF_CB(process_UBAMonitorSlowPlus2));
register_telegram_type(0xE4, F("UBAMonitorFastPlus"), false, MAKE_PF_CB(process_UBAMonitorFastPlus));
@@ -88,6 +89,11 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_telegram_type(0x48A, F("HpPool"), true, MAKE_PF_CB(process_HpPool));
}
/*
if (model() == EMSdevice::EMS_DEVICE_FLAG_HYBRID) {
register_telegram_type(0xBB, F("HybridHp"), true, MAKE_PF_CB(process_HybridHp));
}
*/
// reset is a command uses a dummy variable which is always zero, shown as blank, but provides command enum options
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &reset_, DeviceValueType::CMD, FL_(enum_reset), FL_(reset), DeviceValueUOM::NONE, MAKE_CF_CB(set_reset));
has_update(reset_, 0);
@@ -196,6 +202,72 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
DeviceValueUOM::NONE,
MAKE_CF_CB(set_maintenancedate));
/*
// Hybrid Heatpump
if (model() == EMSdevice::EMS_DEVICE_FLAG_HYBRID) {
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&hybridStrategy_,
DeviceValueType::ENUM,
FL_(enum_hybridStrategy),
FL_(hybridStrategy),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_hybridStrategy));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&switchOverTemp_,
DeviceValueType::INT,
nullptr,
FL_(switchOverTemp),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_switchOverTemp),
-20,
20);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&energyCostRatio_,
DeviceValueType::UINT,
FL_(div10),
FL_(energyCostRatio),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_energyCostRatio),
0,
19.9);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&fossileFactor_,
DeviceValueType::UINT,
FL_(div10),
FL_(fossileFactor),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_fossileFactor),
0,
5);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&electricFactor_,
DeviceValueType::UINT,
FL_(div10),
FL_(electricFactor),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_electricFactor),
0,
5);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&delayBoiler_,
DeviceValueType::UINT,
nullptr,
FL_(delayBoiler),
DeviceValueUOM::MINUTES,
MAKE_CF_CB(set_delayBoiler),
5,
120);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&tempDiffBoiler_,
DeviceValueType::UINT,
nullptr,
FL_(tempDiffBoiler),
DeviceValueUOM::DEGREES_R,
MAKE_CF_CB(set_tempDiffBoiler),
1,
99);
}
*/
// heatpump info
if (model() == EMS_DEVICE_FLAG_HEATPUMP) {
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &upTimeControl_, DeviceValueType::TIME, FL_(div60), FL_(upTimeControl), DeviceValueUOM::MINUTES);
@@ -629,9 +701,10 @@ void Boiler::process_UBAMonitorSlow(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, exhaustTemp_, 4);
has_update(telegram, switchTemp_, 25); // only if there is a mixer module present
has_update(telegram, heatingPumpMod_, 9);
has_update(telegram, burnStarts_, 10, 3); // force to 3 bytes
has_update(telegram, burnWorkMin_, 13, 3); // force to 3 bytes
has_update(telegram, heatWorkMin_, 19, 3); // force to 3 bytes
has_update(telegram, burnStarts_, 10, 3); // force to 3 bytes
has_update(telegram, burnWorkMin_, 13, 3); // force to 3 bytes
has_update(telegram, burn2WorkMin_, 16, 3); // force to 3 bytes
has_update(telegram, heatWorkMin_, 19, 3); // force to 3 bytes
}
/*
@@ -653,9 +726,10 @@ void Boiler::process_UBAMonitorSlowPlus(std::shared_ptr<const Telegram> telegram
has_bitupdate(telegram, heatingPump_, 2, 5);
has_bitupdate(telegram, wwCirc_, 2, 7);
has_update(telegram, exhaustTemp_, 6);
has_update(telegram, burnStarts_, 10, 3); // force to 3 bytes
has_update(telegram, burnWorkMin_, 13, 3); // force to 3 bytes
has_update(telegram, heatWorkMin_, 19, 3); // force to 3 bytes
has_update(telegram, burnStarts_, 10, 3); // force to 3 bytes
has_update(telegram, burnWorkMin_, 13, 3); // force to 3 bytes
has_update(telegram, burn2WorkMin_, 16, 3); // force to 3 bytes
has_update(telegram, heatWorkMin_, 19, 3); // force to 3 bytes
has_update(telegram, heatingPumpMod_, 25);
// temperature measurements at 4, see #620
}
@@ -869,6 +943,11 @@ void Boiler::process_UBAMaintenanceStatus(std::shared_ptr<const Telegram> telegr
}
}
// 0xBF
void Boiler::process_ErrorMessage(std::shared_ptr<const Telegram> telegram) {
EMSESP::send_read_request(0xC2, device_id()); // read last errorcode
}
// 0x10, 0x11
void Boiler::process_UBAErrorMessage(std::shared_ptr<const Telegram> telegram) {
if (telegram->offset > 0 || telegram->message_length < 11) {
@@ -984,6 +1063,86 @@ void Boiler::process_UBAMaintenanceData(std::shared_ptr<const Telegram> telegram
has_update(maintenanceDate_, date, sizeof(maintenanceDate_));
}
}
/*
// 0xBB Heatpump optimization
// Boiler(0x08) -> Me(0x0B), ?(0xBB), data: 00 00 00 00 00 00 00 00 00 00 00 FF 02 0F 1E 0B 1A 00 14 03
void Boiler::process_HybridHp(std::shared_ptr<const Telegram> 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
}
*/
/*
* Settings
*/
/*
bool Boiler::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 Boiler::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 Boiler::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 Boiler::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 Boiler::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 Boiler::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 Boiler::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;
}
*/
// Set the dhw temperature 0x33/0x35 or 0xEA
bool Boiler::set_ww_temp(const char * value, const int8_t id) {
@@ -1475,12 +1634,12 @@ bool Boiler::set_reset(const char * value, const int8_t id) {
if (num == 1) {
// LOG_INFO(F("Reset boiler maintenance message"));
write_command(0x05, 0x08, 0xFF, 0x1C);
has_update(reset_);
has_update(&reset_);
return true;
} else if (num == 2) {
// LOG_INFO(F("Reset boiler error message"));
write_command(0x05, 0x00, 0x5A); // error reset
has_update(reset_);
has_update(&reset_);
return true;
}
return false;

View File

@@ -125,9 +125,10 @@ class Boiler : public EMSdevice {
uint8_t setBurnPow_; // max output power in %
uint32_t burnStarts_; // burner restarts
uint32_t burnWorkMin_; // Total burner operating time
uint32_t burn2WorkMin_; // burner stage 2 operating time
uint32_t heatWorkMin_; // Total heat operating time
uint32_t UBAuptime_; // Total UBA working hours
char lastCode_[75]; // last error code
char lastCode_[50]; // last error code
char serviceCode_[4]; // 3 character status/service code
uint16_t serviceCodeNumber_; // error/service code
@@ -192,6 +193,17 @@ class Boiler : public EMSdevice {
// Pool unit
int8_t poolSetTemp_;
/*
// HybridHP
uint8_t hybridStrategy_; // cost = 2, temperature = 3, mix = 4
int8_t switchOverTemp_; // degrees
uint8_t energyCostRatio_; // is *10
uint8_t fossileFactor_; // is * 10
uint8_t electricFactor_; // is * 10
uint8_t delayBoiler_; // minutes
uint8_t tempDiffBoiler_; // relative temperature degrees
*/
void process_UBAParameterWW(std::shared_ptr<const Telegram> telegram);
void process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram);
void process_UBATotalUptime(std::shared_ptr<const Telegram> telegram);
@@ -209,6 +221,7 @@ class Boiler : public EMSdevice {
void process_MC110Status(std::shared_ptr<const Telegram> telegram);
void process_UBAMaintenanceStatus(std::shared_ptr<const Telegram> telegram);
void process_UBAMaintenanceData(std::shared_ptr<const Telegram> telegram);
void process_ErrorMessage(std::shared_ptr<const Telegram> telegram);
void process_UBAErrorMessage(std::shared_ptr<const Telegram> telegram);
void process_UBAErrorMessage2(std::shared_ptr<const Telegram> telegram);
void process_UBAMonitorWWPlus(std::shared_ptr<const Telegram> telegram);
@@ -219,6 +232,7 @@ class Boiler : public EMSdevice {
void process_HpPower(std::shared_ptr<const Telegram> telegram);
void process_HpOutdoor(std::shared_ptr<const Telegram> telegram);
void process_HpPool(std::shared_ptr<const Telegram> telegram);
void process_HybridHp(std::shared_ptr<const Telegram> telegram);
// commands - none of these use the additional id parameter
bool set_ww_mode(const char * value, const int8_t id);
@@ -254,6 +268,15 @@ class Boiler : public EMSdevice {
bool set_ww_hyst_on(const char * value, const int8_t id);
bool set_ww_hyst_off(const char * value, const int8_t id);
bool set_pool_temp(const char * value, const int8_t id);
/*
bool set_hybridStrategy(const char * value, const int8_t id);
bool set_switchOverTemp(const char * value, const int8_t id);
bool set_energyCostRatio(const char * value, const int8_t id);
bool set_fossileFactor(const char * value, const int8_t id);
bool set_electricFactor(const char * value, const int8_t id);
bool set_delayBoiler(const char * value, const int8_t id);
bool set_tempDiffBoiler(const char * value, const int8_t id);
*/
};
} // namespace emsesp

View File

@@ -24,6 +24,28 @@ REGISTER_FACTORY(Controller, EMSdevice::DeviceType::CONTROLLER);
Controller::Controller(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) {
// IVT broadcasts Thermostat time from controller (0x09) if display is off.
register_telegram_type(0x06, F("RCTime"), false, MAKE_PF_CB(process_dateTime));
register_device_value(DeviceValueTAG::TAG_NONE, &dateTime_, DeviceValueType::STRING, nullptr, FL_(dateTime), DeviceValueUOM::NONE);
}
// process_dateTime - type 0x06 - date and time from a thermostat - 14 bytes long, IVT only
void Controller::process_dateTime(std::shared_ptr<const Telegram> telegram) {
if (telegram->offset > 0 || telegram->message_length < 5) {
return;
}
char newdatetime[sizeof(dateTime_)];
// publich as dd.mm.yyyy hh:mmF
snprintf(newdatetime,
sizeof(dateTime_),
"%02d.%02d.%04d %02d:%02d",
telegram->message_data[3],
telegram->message_data[1] - 1,
(telegram->message_data[0] & 0x7F) + 2000,
telegram->message_data[2],
telegram->message_data[4]);
has_update(dateTime_, newdatetime, sizeof(dateTime_));
}
} // namespace emsesp

View File

@@ -26,6 +26,10 @@ namespace emsesp {
class Controller : public EMSdevice {
public:
Controller(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);
void process_dateTime(std::shared_ptr<const Telegram> telegram);
char dateTime_[25];
};
} // namespace emsesp

View File

@@ -99,17 +99,51 @@ Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
// HT3
if (flags == EMSdevice::EMS_DEVICE_FLAG_IPM) {
register_telegram_type(0x010C, F("IPMStatusMessage"), false, MAKE_PF_CB(process_IPMStatusMessage));
register_telegram_type(0x011E, F("IPMTempMessage"), false, MAKE_PF_CB(process_IPMTempMessage));
// register_telegram_type(0x0123, F("IPMSetMessage"), false, MAKE_PF_CB(process_IPMSetMessage));
type_ = Type::HC;
hc_ = device_id - 0x20 + 1;
uint8_t tag = DeviceValueTAG::TAG_HC1 + hc_ - 1;
register_device_value(tag, &flowTempHc_, DeviceValueType::USHORT, FL_(div10), FL_(flowTempHc), DeviceValueUOM::DEGREES);
register_device_value(tag, &status_, DeviceValueType::INT, nullptr, FL_(mixerStatus), DeviceValueUOM::PERCENT);
register_device_value(tag, &flowSetTemp_, DeviceValueType::UINT, nullptr, FL_(flowSetTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_flowSetTemp));
register_device_value(tag, &pumpStatus_, DeviceValueType::BOOL, nullptr, FL_(pumpStatus), DeviceValueUOM::NONE, MAKE_CF_CB(set_pump));
register_device_value(tag, &flowTempVf_, DeviceValueType::USHORT, FL_(div10), FL_(flowTempVf), DeviceValueUOM::DEGREES);
if (device_id >= 0x40) { // special DHW pos 10
register_telegram_type(0x34, F("UBAMonitorWW"), false, MAKE_PF_CB(process_IPMMonitorWW));
register_telegram_type(0x1E, F("HydrTemp"), false, MAKE_PF_CB(process_IPMHydrTemp));
register_telegram_type(0x33, F("UBAParameterWW"), true, MAKE_PF_CB(process_IPMParameterWW));
// register_telegram_type(0x10D, F("wwNTCStatus"), false, MAKE_PF_CB(process_wwNTCStatus));
type_ = Type::WWC;
hc_ = device_id - 0x40 + 1;
uint8_t tag = DeviceValueTAG::TAG_WWC9 + hc_ - 1;
register_device_value(tag, &wwSelTemp_, DeviceValueType::UINT, nullptr, FL_(wwSelTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwSelTemp));
register_device_value(tag, &wwCurTemp_1_, DeviceValueType::USHORT, FL_(div10), FL_(wwCurTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &wwCurTemp_2_, DeviceValueType::USHORT, FL_(div10), FL_(wwCurTemp2), DeviceValueUOM::DEGREES);
register_device_value(tag, &HydrTemp_, DeviceValueType::USHORT, FL_(div10), FL_(hydrTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &pumpStatus_, DeviceValueType::BOOL, nullptr, FL_(pumpStatus), DeviceValueUOM::NONE);
register_device_value(
tag, &wwFlowTempOffset_, DeviceValueType::UINT, nullptr, FL_(wwFlowTempOffset), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_wwFlowTempOffset));
register_device_value(tag, &wwHystOn_, DeviceValueType::INT, nullptr, FL_(wwHystOn), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_wwHystOn));
register_device_value(tag, &wwHystOff_, DeviceValueType::INT, nullptr, FL_(wwHystOff), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_wwHystOff));
register_device_value(tag,
&wwDisinfectionTemp_,
DeviceValueType::UINT,
nullptr,
FL_(wwDisinfectionTemp),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_wwDisinfectionTemp));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW,
&wwCircPump_,
DeviceValueType::BOOL,
nullptr,
FL_(wwCircPump),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_wwCircPump));
register_device_value(tag, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode), FL_(wwCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwCircMode));
} else {
register_telegram_type(0x010C, F("IPMStatusMessage"), false, MAKE_PF_CB(process_IPMStatusMessage));
register_telegram_type(0x011E, F("IPMTempMessage"), false, MAKE_PF_CB(process_IPMTempMessage));
// register_telegram_type(0x0123, F("IPMSetMessage"), false, MAKE_PF_CB(process_IPMSetMessage));
type_ = Type::HC;
hc_ = device_id - 0x20 + 1;
uint8_t tag = DeviceValueTAG::TAG_HC1 + hc_ - 1;
register_device_value(tag, &flowTempHc_, DeviceValueType::USHORT, FL_(div10), FL_(flowTempHc), DeviceValueUOM::DEGREES);
register_device_value(tag, &status_, DeviceValueType::INT, nullptr, FL_(mixerStatus), DeviceValueUOM::PERCENT);
register_device_value(tag, &flowSetTemp_, DeviceValueType::UINT, nullptr, FL_(flowSetTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_flowSetTemp));
register_device_value(tag, &pumpStatus_, DeviceValueType::BOOL, nullptr, FL_(pumpStatus), DeviceValueUOM::NONE, MAKE_CF_CB(set_pump));
register_device_value(tag, &flowTempVf_, DeviceValueType::USHORT, FL_(div10), FL_(flowTempVf), DeviceValueUOM::DEGREES);
}
}
}
@@ -203,6 +237,35 @@ void Mixer::process_MMPLUSConfigMessage_WWC(std::shared_ptr<const Telegram> tele
has_update(telegram, wwMaxTemp_, 10);
}
// 0x34 only 8 bytes long
// Mixer(0x41) -> All(0x00), UBAMonitorWW(0x34), data: 37 02 1E 02 1E 00 00 00 00
void Mixer::process_IPMMonitorWW(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, wwSelTemp_, 0);
has_update(telegram, wwCurTemp_1_, 1);
has_update(telegram, wwCurTemp_2_, 3);
has_bitupdate(telegram, pumpStatus_, 5, 3);
}
// Mixer(0x41) -> Me(0x0B), UBAParameterWW(0x33), data: 08 FF 46 FB FF 28 FF 07 46 00 FF 00
void Mixer::process_IPMParameterWW(std::shared_ptr<const Telegram> telegram) {
// has_update(telegram, wwActivated_, 1); // 0xFF means on
// has_update(telegram, wwSelTemp_, 2);
has_update(telegram, wwHystOn_, 3); // Hyst on (default -5)
has_update(telegram, wwHystOff_, 4); // Hyst off (default -1)
has_update(telegram, wwFlowTempOffset_, 5); // default 40
has_update(telegram, wwCircPump_, 6); // 0xFF means on
has_update(telegram, wwCircMode_, 7); // 1=1x3min 6=6x3min 7=continuous
has_update(telegram, wwDisinfectionTemp_, 8);
// has_bitupdate(telegram, wwChargeType_, 10, 0); // 0 = charge pump, 0xff = 3-way valve
}
// 0x1E, only16 bit temperature
// Mixer(0x41) -> Boiler(0x08), HydrTemp(0x1E), data: 01 D8
void Mixer::process_IPMHydrTemp(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, HydrTemp_, 0);
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
@@ -241,6 +304,15 @@ void Mixer::process_IPMSetMessage(std::shared_ptr<const Telegram> telegram) {
#pragma GCC diagnostic pop
bool Mixer::set_wwSelTemp(const char * value, const int8_t id) {
int temperature;
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
write_command(0x35, 3, (uint8_t)temperature, 0x34);
return true;
}
bool Mixer::set_flowSetTemp(const char * value, const int8_t id) {
int v;
if (!Helpers::value2number(value, v)) {
@@ -349,34 +421,72 @@ bool Mixer::set_wwRequiredTemp(const char * value, const int8_t id) {
}
bool Mixer::set_wwDisinfectionTemp(const char * value, const int8_t id) {
uint8_t wwc = device_id() - 0x28;
float v = 0;
float v = 0;
if (!Helpers::value2temperature(value, v)) {
return false;
}
write_command(0x313 + wwc, 9, (uint8_t)v, 0x313 + wwc);
if (flags() == EMSdevice::EMS_DEVICE_FLAG_IPM) {
write_command(0x33, 8, (uint8_t)v, 0x33);
} else {
uint8_t wwc = device_id() - 0x28;
write_command(0x313 + wwc, 9, (uint8_t)v, 0x313 + wwc);
}
return true;
}
bool Mixer::set_wwCircPump(const char * value, const int8_t id) {
uint8_t wwc = device_id() - 0x28;
bool v = false;
bool v = false;
if (!Helpers::value2bool(value, v)) {
return false;
}
write_command(0x33B + wwc, 0, v ? 0x01 : 0x00, 0x33B + wwc);
if (flags() == EMSdevice::EMS_DEVICE_FLAG_IPM) {
write_command(0x33, 6, v ? 0xFF : 0x00, 0x33);
} else {
uint8_t wwc = device_id() - 0x28;
write_command(0x33B + wwc, 0, v ? 0x01 : 0x00, 0x33B + wwc);
}
return true;
}
bool Mixer::set_wwCircMode(const char * value, const int8_t id) {
uint8_t wwc = device_id() - 0x28;
uint8_t n;
if (!Helpers::value2enum(value, n, FL_(enum_wwCircMode))) {
return false;
}
write_command(0x313 + wwc, 0, n, 0x313 + wwc);
if (flags() == EMSdevice::EMS_DEVICE_FLAG_IPM) {
write_command(0x33, 7, n, 0x33);
} else {
uint8_t wwc = device_id() - 0x28;
write_command(0x313 + wwc, 0, n, 0x313 + wwc);
}
return true;
}
bool Mixer::set_wwFlowTempOffset(const char * value, const int8_t id) {
int n;
if (!Helpers::value2number(value, n)) {
return false;
}
write_command(0x33, 5, n, 0x33);
return true;
}
bool Mixer::set_wwHystOn(const char * value, const int8_t id) {
int n;
if (!Helpers::value2number(value, n)) {
return false;
}
write_command(0x33, 3, n, 0x33);
return true;
}
bool Mixer::set_wwHystOff(const char * value, const int8_t id) {
int n;
if (!Helpers::value2number(value, n)) {
return false;
}
write_command(0x33, 4, n, 0x33);
return true;
}
} // namespace emsesp

View File

@@ -43,6 +43,10 @@ class Mixer : public EMSdevice {
void process_MMSetMessage(std::shared_ptr<const Telegram> telegram);
void process_HpPoolStatus(std::shared_ptr<const Telegram> telegram);
void process_IPMMonitorWW(std::shared_ptr<const Telegram> telegram);
void process_IPMHydrTemp(std::shared_ptr<const Telegram> telegram);
void process_IPMParameterWW(std::shared_ptr<const Telegram> telegram);
bool set_flowSetTemp(const char * value, const int8_t id);
bool set_pump(const char * value, const int8_t id);
bool set_activated(const char * value, const int8_t id);
@@ -56,6 +60,10 @@ class Mixer : public EMSdevice {
bool set_wwCircPump(const char * value, const int8_t id);
bool set_wwCircMode(const char * value, const int8_t id);
bool set_wwSelTemp(const char * value, const int8_t id);
bool set_wwFlowTempOffset(const char * value, const int8_t id);
bool set_wwHystOn(const char * value, const int8_t id);
bool set_wwHystOff(const char * value, const int8_t id);
enum class Type {
NONE,
@@ -90,6 +98,14 @@ class Mixer : public EMSdevice {
Type type_ = Type::NONE;
uint16_t hc_ = EMS_VALUE_USHORT_NOTSET;
uint8_t poolShuntStatus__ = EMS_VALUE_UINT_NOTSET; // temp value
uint8_t wwSelTemp_;
uint16_t wwCurTemp_1_;
uint16_t wwCurTemp_2_;
uint16_t HydrTemp_;
int8_t wwHystOn_; // Hyst on (default -5)
int8_t wwHystOff_; // Hyst off (default -1)
uint8_t wwFlowTempOffset_; // default 40
};
} // namespace emsesp

View File

@@ -34,7 +34,7 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
}
if (flags == EMSdevice::EMS_DEVICE_FLAG_SM100) {
if (device_id == 0x2A) {
if (device_id == 0x2A) { // SM100 DHW
register_telegram_type(0x07D6, F("SM100wwTemperature"), false, MAKE_PF_CB(process_SM100wwTemperature));
register_telegram_type(0x07AA, F("SM100wwStatus"), false, MAKE_PF_CB(process_SM100wwStatus));
register_telegram_type(0x07AB, F("SM100wwCommand"), false, MAKE_PF_CB(process_SM100wwCommand));
@@ -65,11 +65,11 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
if (flags == EMSdevice::EMS_DEVICE_FLAG_ISM) {
register_telegram_type(0x0103, F("ISM1StatusMessage"), true, MAKE_PF_CB(process_ISM1StatusMessage));
register_telegram_type(0x0101, F("ISM1Set"), true, MAKE_PF_CB(process_ISM1Set));
register_telegram_type(0x0104, F("ISM2StatusMessage"), false, MAKE_PF_CB(process_ISM2StatusMessage));
}
// device values...
// special case for a device_id with 0x2A where it's not actual a solar module
// special case for a SM100 DHW device_id with 0x2A where it's not actual a solar module
if (device_id == 0x2A) {
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwTemp_1_, DeviceValueType::USHORT, FL_(div10), FL_(wwTemp1), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwTemp_3_, DeviceValueType::USHORT, FL_(div10), FL_(wwTemp3), DeviceValueUOM::DEGREES);
@@ -84,8 +84,13 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
FL_(wwMaxTemp),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_wwMaxTemp));
register_device_value(
DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwTemp_, DeviceValueType::UINT, nullptr, FL_(wwTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwTemp));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW,
&wwSelTemp_,
DeviceValueType::UINT,
nullptr,
FL_(wwSelTemp),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_wwSelTemp));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW,
&wwRedTemp_,
DeviceValueType::UINT,
@@ -128,6 +133,7 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
return;
}
// common solar values for all modules (except dhw)
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &collectorTemp_, DeviceValueType::SHORT, FL_(div10), FL_(collectorTemp), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &cylBottomTemp_, DeviceValueType::SHORT, FL_(div10), FL_(cylBottomTemp), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &solarPump_, DeviceValueType::BOOL, nullptr, FL_(solarPump), DeviceValueUOM::NONE);
@@ -137,6 +143,7 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &collectorShutdown_, DeviceValueType::BOOL, nullptr, FL_(collectorShutdown), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &cylHeated_, DeviceValueType::BOOL, nullptr, FL_(cylHeated), DeviceValueUOM::NONE);
// values per device flag
if (flags == EMSdevice::EMS_DEVICE_FLAG_SM10) {
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &solarPumpMod_, DeviceValueType::UINT, nullptr, FL_(solarPumpMod), DeviceValueUOM::PERCENT);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
@@ -191,6 +198,9 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
*/
}
if (flags == EMSdevice::EMS_DEVICE_FLAG_ISM) {
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &cylMiddleTemp_, DeviceValueType::SHORT, FL_(div10), FL_(cylMiddleTemp), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &retHeatAssist_, DeviceValueType::SHORT, FL_(div10), FL_(retHeatAssist), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &m1Valve_, DeviceValueType::BOOL, nullptr, FL_(m1Valve), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &energyLastHour_, DeviceValueType::ULONG, FL_(div10), FL_(energyLastHour), DeviceValueUOM::WH);
}
@@ -608,7 +618,7 @@ void Solar::process_SM100wwStatus(std::shared_ptr<const Telegram> telegram) {
// data: FF 05 0F 5F 00 01 3C 3C 3C 3C 28 12 46 01 3C 1E 03 07 3C 00 0F 00 05
void Solar::process_SM100wwParam(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, wwMaxTemp_, 8);
has_update(telegram, wwTemp_, 9);
has_update(telegram, wwSelTemp_, 9);
has_update(telegram, wwRedTemp_, 10);
has_update(telegram, wwDailyTemp_, 6);
has_update(telegram, wwDisinfectionTemp_, 12);
@@ -788,6 +798,18 @@ void Solar::process_ISM1StatusMessage(std::shared_ptr<const Telegram> telegram)
has_bitupdate(telegram, cylHeated_, 9, 2); // cyl full
}
/*
* Junkers ISM12 Solar Module - type 0x0104 EMS+ for heat assist
* ?(0x103), data: 00 00 00 00 00 7A 01 15 00 00 05 37 F0
* ?(0x104), data: 01 A9 01 22 27 0F 27 0F 27 0F 27 0F 27 0F 27 0F
* ?(0x104), data: 01 01 00 00 00 00 00 27 0F 27 0F (offset 16)
*/
void Solar::process_ISM2StatusMessage(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, cylMiddleTemp_, 0); // Temperature Middle of Solar Boiler cyl
has_update(telegram, retHeatAssist_, 2); // return temperature from heating T4
has_bitupdate(telegram, m1Valve_, 17, 0); // return valve DUW1 (also 16,0)
}
/*
* Junkers ISM1 Solar Module - type 0x0101 EMS+ for setting values
*/
@@ -943,54 +965,54 @@ bool Solar::set_SM10MaxFlow(const char * value, const int8_t id) {
// switch heat transfer system on/off
bool Solar::set_heatTransferSystem(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x358, 5, v ? 0x01 : 0x00, 0x358);
write_command(0x358, 5, b ? 0x01 : 0x00, 0x358);
return true;
}
// switch external cylinder on/off
bool Solar::set_externalCyl(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x358, 9, v ? 0x01 : 0x00, 0x358);
write_command(0x358, 9, b ? 0x01 : 0x00, 0x358);
return true;
}
// switch thermal disinfection on/off
bool Solar::set_thermalDisinfect(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x358, 10, v ? 0x01 : 0x00, 0x358);
write_command(0x358, 10, b ? 0x01 : 0x00, 0x358);
return true;
}
// switch heat metering on/off
bool Solar::set_heatMetering(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x358, 14, v ? 0x01 : 0x00, 0x358);
write_command(0x358, 14, b ? 0x01 : 0x00, 0x358);
return true;
}
// switch solar system on/off
bool Solar::set_solarEnabled(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_SM10) {
write_command(0x96, 0, v ? 0xFF : 0x00, 0x96);
write_command(0x96, 0, b ? 0xFF : 0x00, 0x96);
} else {
write_command(0x358, 19, v ? 0x01 : 0x00, 0x358);
write_command(0x358, 19, b ? 0x01 : 0x00, 0x358);
}
return true;
}
@@ -1018,71 +1040,71 @@ bool Solar::set_solarMode2(const char * value, const int8_t id) {
// switch pumpkick on/off
bool Solar::set_solarPumpKick(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x35A, 9, v ? 0x01 : 0x00, 0x35A);
write_command(0x35A, 9, b ? 0x01 : 0x00, 0x35A);
return true;
}
// switch pump2kick on/off
bool Solar::set_solarPump2Kick(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x35D, 0, v ? 0x01 : 0x00, 0x35D);
write_command(0x35D, 0, b ? 0x01 : 0x00, 0x35D);
return true;
}
// switch plain water mode on/off
bool Solar::set_plainWaterMode(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x35A, 10, v ? 0x01 : 0x00, 0x35A);
write_command(0x35A, 10, b ? 0x01 : 0x00, 0x35A);
return true;
}
// switch double match flow on/off
bool Solar::set_doubleMatchFlow(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x35A, 11, v ? 0x01 : 0x00, 0x35A);
write_command(0x35A, 11, b ? 0x01 : 0x00, 0x35A);
return true;
}
// set climate zone number
bool Solar::set_climateZone(const char * value, const int8_t id) {
int v = 0;
if (!Helpers::value2number(value, v)) {
int zone;
if (!Helpers::value2number(value, zone)) {
return false;
}
write_command(0x380, 0, v, 0x380);
write_command(0x380, 0, zone, 0x380);
return true;
}
// collector area in squaremeters
bool Solar::set_collector1Area(const char * value, const int8_t id) {
float v = 0;
if (!Helpers::value2float(value, v)) {
float area;
if (!Helpers::value2float(value, area)) {
return false;
}
write_command(0x380, 3, (uint16_t)(v * 10), 0x380);
write_command(0x380, 3, (uint16_t)(area * 10), 0x380);
return true;
}
// collector area in squaremeters
bool Solar::set_collector2Area(const char * value, const int8_t id) {
float v = 0;
if (!Helpers::value2float(value, v)) {
float area;
if (!Helpers::value2float(value, area)) {
return false;
}
write_command(0x380, 6, (uint16_t)(v * 10), 0x380);
write_command(0x380, 6, (uint16_t)(area * 10), 0x380);
return true;
}
@@ -1108,101 +1130,101 @@ bool Solar::set_collector2Type(const char * value, const int8_t id) {
// priority of cylinders if there are 2
bool Solar::set_cylPriority(const char * value, const int8_t id) {
uint8_t n;
if (!Helpers::value2enum(value, n, FL_(enum_cylprio))) {
uint8_t num;
if (!Helpers::value2enum(value, num, FL_(enum_cylprio))) {
return false;
}
write_command(0x35F, 3, n, 0x35F);
write_command(0x35F, 3, num, 0x35F);
return true;
}
bool Solar::set_heatAssist(const char * value, const int8_t id) {
float v = 0;
if (!Helpers::value2temperature(value, v)) {
float temperature;
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
write_command(0x35C, 0, (uint8_t)(v * 10), 0x35C);
write_command(0x35C, 0, (uint8_t)(temperature * 10), 0x35C);
return true;
}
bool Solar::set_diffControl(const char * value, const int8_t id) {
float v = 0;
if (!Helpers::value2temperature(value, v)) {
float temperature;
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
write_command(0x361, 4, (uint8_t)(v * 10), 0x361);
write_command(0x361, 4, (uint8_t)(temperature * 10), 0x361);
return true;
}
bool Solar::set_wwTemp(const char * value, const int8_t id) {
float v = 0;
if (!Helpers::value2temperature(value, v)) {
bool Solar::set_wwSelTemp(const char * value, const int8_t id) {
int temperature;
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
write_command(0x7A6, 9, (uint8_t)v, 0x7A6);
write_command(0x7A6, 9, (uint8_t)temperature, 0x7A6);
return true;
}
bool Solar::set_wwMaxTemp(const char * value, const int8_t id) {
float v = 0;
if (!Helpers::value2temperature(value, v)) {
int temperature;
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
write_command(0x7A6, 8, (uint8_t)v, 0x7A6);
write_command(0x7A6, 8, (uint8_t)temperature, 0x7A6);
return true;
}
bool Solar::set_wwRedTemp(const char * value, const int8_t id) {
float v = 0;
if (!Helpers::value2temperature(value, v)) {
int temperature;
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
write_command(0x7A6, 10, (uint8_t)v, 0x7A6);
write_command(0x7A6, 10, (uint8_t)temperature, 0x7A6);
return true;
}
bool Solar::set_wwDailyTemp(const char * value, const int8_t id) {
float v = 0;
if (!Helpers::value2temperature(value, v)) {
int temperature;
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
write_command(0x7A6, 6, (uint8_t)v, 0x7A6);
write_command(0x7A6, 6, (uint8_t)temperature, 0x7A6);
return true;
}
bool Solar::set_wwDisinfectionTemp(const char * value, const int8_t id) {
float v = 0;
if (!Helpers::value2temperature(value, v)) {
int temperature;
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
write_command(0x7A6, 12, (uint8_t)v, 0x7A6);
write_command(0x7A6, 12, (uint8_t)temperature, 0x7A6);
return true;
}
bool Solar::set_wwCirc(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x7A5, 0, v ? 0xFF : 0x00, 0x7A5);
write_command(0x7A5, 0, b ? 0xFF : 0x00, 0x7A5);
return true;
}
bool Solar::set_wwCircMode(const char * value, const int8_t id) {
uint8_t n;
if (!Helpers::value2enum(value, n, FL_(enum_wwCircMode))) {
uint8_t num;
if (!Helpers::value2enum(value, num, FL_(enum_wwCircMode))) {
return false;
}
write_command(0x7A5, 3, n, 0x7A5);
write_command(0x7A5, 3, num, 0x7A5);
return true;
}
bool Solar::set_wwKeepWarm(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x7AE, 0, v ? 0xFF : 0x00, 0x7AE);
write_command(0x7AE, 0, b ? 0xFF : 0x00, 0x7AE);
return true;
}

View File

@@ -125,7 +125,7 @@ class Solar : public EMSdevice {
// SM100wwParam - 0x07A6
uint8_t wwMaxTemp_;
uint8_t wwTemp_;
uint8_t wwSelTemp_;
uint8_t wwRedTemp_;
uint8_t wwDailyTemp_;
uint8_t wwDisinfectionTemp_;
@@ -187,8 +187,9 @@ class Solar : public EMSdevice {
void process_ISM1StatusMessage(std::shared_ptr<const Telegram> telegram);
void process_ISM1Set(std::shared_ptr<const Telegram> telegram);
void process_ISM2StatusMessage(std::shared_ptr<const Telegram> telegram);
// settings
bool set_CollectorMaxTemp(const char * value, const int8_t id);
bool set_CollectorMinTemp(const char * value, const int8_t id);
bool set_cylMaxTemp(const char * value, const int8_t id);
@@ -224,7 +225,7 @@ class Solar : public EMSdevice {
bool set_heatAssist(const char * value, const int8_t id);
bool set_diffControl(const char * value, const int8_t id);
bool set_wwTemp(const char * value, const int8_t id);
bool set_wwSelTemp(const char * value, const int8_t id);
bool set_wwMaxTemp(const char * value, const int8_t id);
bool set_wwRedTemp(const char * value, const int8_t id);
bool set_wwCirc(const char * value, const int8_t id);

View File

@@ -146,6 +146,8 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_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));
}
@@ -270,6 +272,11 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
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;
@@ -342,8 +349,8 @@ void Thermostat::add_ha_climate(std::shared_ptr<HeatingCircuit> hc) const {
return;
}
if (Helpers::hasValue(hc->selTemp) && is_visible(&hc->selTemp)) {
if (Helpers::hasValue(hc->roomTemp) && is_visible(&hc->roomTemp)) {
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;
@@ -587,7 +594,7 @@ void Thermostat::process_RC20Timer(std::shared_ptr<const Telegram> telegram) {
char data[sizeof(hc->switchtime1)];
uint8_t no = telegram->offset / 2;
uint8_t day = telegram->message_data[0] >> 5;
uint8_t temp = telegram->message_data[0] & 1;
uint8_t temp = telegram->message_data[0] & 7;
uint8_t time = telegram->message_data[1];
std::string sday = read_flash_string(FL_(enum_dayOfWeek)[day]);
@@ -805,6 +812,27 @@ void Thermostat::process_JunkersMonitor(std::shared_ptr<const Telegram> telegram
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<const Telegram> 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<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> 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<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
@@ -1009,7 +1037,18 @@ void Thermostat::process_RC30Set(std::shared_ptr<const Telegram> 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 0x3E (HC1), 0x48 (HC2), 0x52 (HC3), 0x5C (HC4) - data from the RC35 thermostat (0x10) - 16 bytes
@@ -1065,12 +1104,16 @@ void Thermostat::process_RC35Set(std::shared_ptr<const Telegram> telegram) {
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->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);
if (hc->heatingtype == 3) { // floor heating
@@ -1164,32 +1207,49 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
return;
}
if (telegram->message_data[7] & 0x0C) { // date and time not valid
set_datetime("ntp", -1); // set from NTP
if ((telegram->message_data[7] & 0x0C) && has_command(&dateTime_)) { // date and time not valid
set_datetime("ntp", -1); // set from NTP
return;
}
// render date to HH:MM:SS DD/MM/YYYY
// had to create separate buffers because of how printf works
char date[25];
char buf1[6];
char buf2[6];
char buf3[6];
char buf4[6];
char buf5[6];
char buf6[6];
snprintf(date,
sizeof(date),
"%s:%s:%s %s.%s.%s",
Helpers::smallitoa(buf1, telegram->message_data[2]), // hour
Helpers::smallitoa(buf2, telegram->message_data[4]), // minute
Helpers::smallitoa(buf3, telegram->message_data[5]), // second
Helpers::smallitoa(buf4, telegram->message_data[3]), // day
Helpers::smallitoa(buf5, telegram->message_data[1]), // month
// IVT reports Year with high bit set.?
Helpers::itoa((telegram->message_data[0] & 0x7F) + 2000, buf6) // year
);
has_update(dateTime_, date, sizeof(dateTime_));
// 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
@@ -1237,6 +1297,76 @@ void Thermostat::process_RCErrorMessage(std::shared_ptr<const Telegram> telegram
}
}
/*
*
* *** 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;
@@ -1255,19 +1385,23 @@ bool Thermostat::set_minexttemp(const char * value, const int8_t id) {
return true;
}
// 0xA5 - Clock offset
// 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;
}
write_command(EMS_TYPE_IBASettings, 12, co, EMS_TYPE_IBASettings);
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 - Calibrate internal temperature
// 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)) {
@@ -1279,6 +1413,8 @@ bool Thermostat::set_calinttemp(const char * value, const int8_t id) {
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);
}
@@ -1298,6 +1434,18 @@ bool Thermostat::set_display(const char * value, const int8_t id) {
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)) {
@@ -1321,7 +1469,7 @@ bool Thermostat::set_remotetemp(const char * value, const int8_t id) {
return true;
}
// 0xA5 - Set the building settings
// 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))) {
@@ -1330,6 +1478,8 @@ bool Thermostat::set_building(const char * value, const int8_t id) {
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);
}
@@ -1337,7 +1487,7 @@ bool Thermostat::set_building(const char * value, const int8_t id) {
return true;
}
// 0xB0 - Set RC10 heating pid
// 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))) {
@@ -1346,12 +1496,14 @@ bool Thermostat::set_heatingpid(const char * value, const int8_t id) {
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 building settings
// 0xA5 - Set the damping settings
bool Thermostat::set_damping(const char * value, const int8_t id) {
bool dmp;
if (Helpers::value2bool(value, dmp)) {
@@ -1362,14 +1514,21 @@ bool Thermostat::set_damping(const char * value, const int8_t id) {
return false;
}
// 0xA5 Set the language settings
// 0xA5/0xA7 Set the language settings
bool Thermostat::set_language(const char * value, const int8_t id) {
uint8_t lg = 0;
if (!Helpers::value2enum(value, lg, FL_(enum_ibaLanguage))) {
return false;
}
write_command(EMS_TYPE_IBASettings, 1, lg, EMS_TYPE_IBASettings);
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;
}
@@ -1389,7 +1548,7 @@ bool Thermostat::set_control(const char * value, const int8_t id) {
return true;
}
} else if (Helpers::value2enum(value, ctrl, FL_(enum_control))) {
write_command(set_typeids[hc->hc()], 26, ctrl);
write_command(set_typeids[hc->hc()], EMS_OFFSET_RC35Set_control, ctrl);
return true;
}
@@ -1602,14 +1761,63 @@ bool Thermostat::set_wwOneTimeKey(const char * value, const int8_t id) {
return true;
}
// only RC10, 0xB0
// 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;
}
write_command(0xB0, 1, b ? 0xFF : 0x00, 0xB0);
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;
}
@@ -1715,7 +1923,7 @@ bool Thermostat::set_party(const char * value, const int8_t id) {
return true;
}
// set date&time as string hh:mm:ss-dd.mm.yyyy-dw-dst or "NTP" for setting to internet-time
// 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) {
@@ -1728,9 +1936,12 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) {
if (dt == "ntp") {
time_t now = time(nullptr);
tm * tm_ = localtime(&now);
if (tm_->tm_year < 110) { // no NTP time
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;
@@ -1740,25 +1951,20 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) {
data[5] = tm_->tm_sec;
data[6] = (tm_->tm_wday + 6) % 7; // Bosch counts from Mo, time from Su
data[7] = tm_->tm_isdst + 2; // set DST and flag for ext. clock
// char time_string[25];
// strftime(time_string, 25, "%FT%T%z", tm_);
// LOG_INFO(F("Date and time: %s"), time_string);
} else if (dt.length() == 23) {
data[0] = (dt[16] - '0') * 100 + (dt[17] - '0') * 10 + (dt[18] - '0'); // year
data[1] = (dt[12] - '0') * 10 + (dt[13] - '0'); // month
data[2] = (dt[0] - '0') * 10 + (dt[1] - '0'); // hour
data[3] = (dt[9] - '0') * 10 + (dt[10] - '0'); // day
data[4] = (dt[3] - '0') * 10 + (dt[4] - '0'); // min
data[5] = (dt[6] - '0') * 10 + (dt[7] - '0'); // sec
data[6] = (dt[20] - '0'); // day of week, Mo:0
data[7] = (dt[22] - '0') + 2; // DST and flag
// LOG_INFO(F("Date and time: %02d.%02d.2%03d-%02d:%02d:%02d"), data[3], data[1], data[0], data[2], data[4], data[5]);
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("Set date: invalid data"));
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;
}
@@ -1896,7 +2102,8 @@ bool Thermostat::set_mode_n(const uint8_t mode, const uint8_t hc_num) {
break;
}
switch (model()) {
uint8_t model_ = model();
switch (model_) {
case EMSdevice::EMS_DEVICE_FLAG_RC10:
offset = 0;
validate_typeid = 0xB1;
@@ -1959,6 +2166,18 @@ bool Thermostat::set_mode_n(const uint8_t mode, const uint8_t hc_num) {
// 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;
}
@@ -2015,7 +2234,41 @@ bool Thermostat::set_reducemode(const char * value, const int8_t id) {
return false;
}
write_command(set_typeids[hc->hc()], 25, set, set_typeids[hc->hc()]);
write_command(set_typeids[hc->hc()], EMS_OFFSET_RC35Set_reducemode, set, set_typeids[hc->hc()]);
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<Thermostat::HeatingCircuit> 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<Thermostat::HeatingCircuit> 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;
}
@@ -2159,6 +2412,8 @@ bool Thermostat::set_switchtime(const char * value, const uint16_t type_id, char
}
if (strlen(value) > 13 && value[12] == 'o') {
on = value[13] == 'n' ? 1 : 0;
} else if (strlen(value) > 13 && value[12] == 'T') {
on = value[13] - '0';
} else if (strlen(value) == 13) {
on = value[12] - '0';
}
@@ -2175,8 +2430,8 @@ bool Thermostat::set_switchtime(const char * value, const uint16_t type_id, char
data[1] = time;
}
uint8_t max_on = 3;
if ((model() == EMS_DEVICE_FLAG_RC35) || (model() == EMS_DEVICE_FLAG_RC30_N)) {
uint8_t max_on = 4;
if (model() == EMS_DEVICE_FLAG_RC35 || model() == EMS_DEVICE_FLAG_RC30_N) {
max_on = 1;
}
if (no > 41 || time > 0x90 || (on > max_on && on != 7)) {
@@ -2186,7 +2441,7 @@ bool Thermostat::set_switchtime(const char * value, const uint16_t type_id, char
}
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)) {
if (model() == EMS_DEVICE_FLAG_RC35 || model() == EMS_DEVICE_FLAG_RC30_N) {
snprintf(out, len, "%02d %s %02d:%02d %s", no, sday.c_str(), time / 6, 10 * (time % 6), on ? "on" : "off");
} else if (model() == EMS_DEVICE_FLAG_RC20) {
snprintf(out, len, "%02d %s %02d:%02d T%d", no, sday.c_str(), time / 6, 10 * (time % 6), on);
@@ -2540,6 +2795,14 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
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;
@@ -2558,16 +2821,15 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
default:
// automatic selection, if no type is defined, we use the standard code
validate_typeid = monitor_typeids[hc->hc()]; //get setpoint roomtemp back
if (model == EMS_DEVICE_FLAG_RC35) {
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 {
offset = EMS_OFFSET_RC35Set_seltemp; // https://github.com/emsesp/EMS-ESP/issues/310
}
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;
}
@@ -2592,14 +2854,24 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
offset = EMS_OFFSET_JunkersSetMessage_day_temp;
break;
default:
// automatic selection, if no type is defined, we use the standard code
uint8_t modetype = hc->get_mode_type();
if (modetype == HeatingCircuit::Mode::NIGHT || modetype == HeatingCircuit::Mode::ECO) {
// 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 (modetype == HeatingCircuit::Mode::DAY || modetype == HeatingCircuit::Mode::HEAT) {
} else if (mode_ == HeatingCircuit::Mode::DAY || mode_ == HeatingCircuit::Mode::HEAT) {
offset = EMS_OFFSET_JunkersSetMessage_day_temp;
} else {
} 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;
}
@@ -2619,14 +2891,24 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
offset = EMS_OFFSET_JunkersSetMessage2_heat_temp;
break;
default:
// automatic selection, if no type is defined, we use the standard code
uint8_t modetype = hc->get_mode_type();
if (modetype == HeatingCircuit::Mode::NIGHT || modetype == HeatingCircuit::Mode::ECO) {
// 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 (modetype == HeatingCircuit::Mode::DAY || modetype == HeatingCircuit::Mode::HEAT) {
} else if (mode_ == HeatingCircuit::Mode::DAY || mode_ == HeatingCircuit::Mode::HEAT) {
offset = EMS_OFFSET_JunkersSetMessage2_heat_temp;
} else {
} 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;
}
@@ -2718,6 +3000,14 @@ 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);
}
@@ -2867,19 +3157,72 @@ void Thermostat::register_device_values() {
break;
case EMS_DEVICE_FLAG_RC30:
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &dateTime_, DeviceValueType::STRING, nullptr, FL_(dateTime), DeviceValueUOM::NONE); // can't set datetime
register_device_value(DeviceValueTAG::TAG_DEVICE_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_DEVICE_DATA, &autodst_, DeviceValueType::BOOL, nullptr, FL_(autodst), DeviceValueUOM::NONE, MAKE_CF_CB(set_autodst));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&ibaLanguage_,
DeviceValueType::ENUM,
FL_(enum_ibaLanguage_RC30),
FL_(ibaLanguage),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_language));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&ibaMainDisplay_,
DeviceValueType::ENUM,
FL_(enum_ibaMainDisplay),
FL_(ibaMainDisplay),
DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &ibaLanguage_, DeviceValueType::ENUM, FL_(enum_ibaLanguage), FL_(ibaLanguage), DeviceValueUOM::NONE);
register_device_value(
DeviceValueTAG::TAG_DEVICE_DATA, &backlight_, DeviceValueType::BOOL, nullptr, FL_(backlight), DeviceValueUOM::NONE, MAKE_CF_CB(set_backlight));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&ibaClockOffset_,
&brightness_,
DeviceValueType::INT,
nullptr,
FL_(ibaClockOffset),
DeviceValueUOM::SECONDS); // offset (in sec) to clock, 0xff=-1s, 0x02=2s
FL_(brightness),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_brightness),
-15,
15);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&mixingvalves_,
DeviceValueType::UINT,
nullptr,
FL_(mixingvalves),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_mixingvalves),
0,
2);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&ibaBuildingType_,
DeviceValueType::ENUM,
FL_(enum_ibaBuildingType),
FL_(ibaBuildingType),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_building));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&heatingpid_,
DeviceValueType::ENUM,
FL_(enum_PID),
FL_(heatingPID),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_heatingpid));
register_device_value(
DeviceValueTAG::TAG_DEVICE_DATA, &preheating_, DeviceValueType::BOOL, nullptr, FL_(preheating), DeviceValueUOM::NONE, MAKE_CF_CB(set_preheating));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&ibaCalIntTemperature_,
DeviceValueType::INT,
FL_(div10),
FL_(ibaCalIntTemperature),
DeviceValueUOM::DEGREES_R,
MAKE_CF_CB(set_calinttemp));
register_device_value(
DeviceValueTAG::TAG_DEVICE_DATA, &offtemp_, DeviceValueType::UINT, FL_(div2), FL_(offtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_offtemp), 5, 30);
break;
case EMS_DEVICE_FLAG_RC30_N:
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &dateTime_, DeviceValueType::STRING, nullptr, FL_(dateTime), DeviceValueUOM::NONE); // can't set datetime
@@ -3112,6 +3455,67 @@ void Thermostat::register_device_values() {
FL_(dateTime),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_datetime));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&hybridStrategy_,
DeviceValueType::ENUM,
FL_(enum_hybridStrategy),
FL_(hybridStrategy),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_hybridStrategy));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&switchOverTemp_,
DeviceValueType::INT,
nullptr,
FL_(switchOverTemp),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_switchOverTemp),
-20,
20);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&energyCostRatio_,
DeviceValueType::UINT,
FL_(div10),
FL_(energyCostRatio),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_energyCostRatio),
0,
19.9);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&fossileFactor_,
DeviceValueType::UINT,
FL_(div10),
FL_(fossileFactor),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_fossileFactor),
0,
5);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&electricFactor_,
DeviceValueType::UINT,
FL_(div10),
FL_(electricFactor),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_electricFactor),
0,
5);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&delayBoiler_,
DeviceValueType::UINT,
nullptr,
FL_(delayBoiler),
DeviceValueUOM::MINUTES,
MAKE_CF_CB(set_delayBoiler),
5,
120);
register_device_value(DeviceValueTAG::TAG_DEVICE_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.
@@ -3183,7 +3587,7 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
tag, &hc->heatingtype, DeviceValueType::ENUM, FL_(enum_heatingtype), FL_(heatingtype), DeviceValueUOM::NONE, MAKE_CF_CB(set_heatingtype));
register_device_value(
tag, &hc->summer_setmode, DeviceValueType::ENUM, FL_(enum_summermode), FL_(summersetmode), DeviceValueUOM::NONE, MAKE_CF_CB(set_summermode));
register_device_value(tag, &hc->summermode, DeviceValueType::BOOL, nullptr, FL_(summermode), DeviceValueUOM::NONE);
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));
@@ -3251,6 +3655,8 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
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));
@@ -3269,6 +3675,10 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
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(
@@ -3285,6 +3695,7 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
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;

View File

@@ -62,12 +62,16 @@ class Thermostat : public EMSdevice {
uint8_t minflowtemp;
uint8_t maxflowtemp;
uint8_t reducemode;
uint8_t nofrostmode;
uint8_t program;
uint8_t controlmode;
uint8_t control;
uint8_t pause;
uint8_t party;
int8_t noreducetemp; // signed -20°C to +10°C
int8_t reducetemp;
int8_t vacreducetemp;
uint8_t vacreducemode;
uint8_t wwprio;
uint8_t fastHeatup;
char holiday[26];
@@ -120,6 +124,8 @@ class Thermostat : public EMSdevice {
ROOMINFLUENCE,
TEMPAUTO,
NOREDUCE,
REDUCE,
VACREDUCE,
ON,
DAYLOW,
DAYMID,
@@ -176,6 +182,11 @@ class Thermostat : public EMSdevice {
uint8_t ibaDamping_; // damping 0-off, 0xff-on
uint8_t backlight_;
uint8_t heatingpid_;
int8_t brightness_; // Screen brightness 0F=dark F1=light
uint8_t preheating_; // Preheating in the clock program: (0x00 = off, 0xFF = on)
uint8_t autodst_; // Automatic change Daylight Saving time: (0x00 = off, 0xFF = on)
uint8_t offtemp_; // Set Temperature when mode is Off / 10 (e.g.: 0x0F = 7.5 degrees Celsius)
uint8_t mixingvalves_; // Number of Mixing Valves: (0x00=0, 0x01=1, 0x02=2)
int8_t dampedoutdoortemp_;
uint16_t tempsensor1_;
@@ -205,6 +216,15 @@ class Thermostat : public EMSdevice {
uint8_t wwDailyHeating_;
uint8_t wwDailyHeatTime_;
// HybridHP
uint8_t hybridStrategy_; // co2 = 1, cost = 2, temperature = 3, mix = 4
int8_t switchOverTemp_; // degrees
uint8_t energyCostRatio_; // is *10
uint8_t fossileFactor_; // is * 10
uint8_t electricFactor_; // is * 10
uint8_t delayBoiler_; // minutes
uint8_t tempDiffBoiler_; // relative temperature degrees
std::vector<std::shared_ptr<HeatingCircuit>> heating_circuits_; // each thermostat can have multiple heating circuits
uint8_t zero_value_ = 0; // for fixing current room temperature to 0 for HA
@@ -245,6 +265,12 @@ class Thermostat : public EMSdevice {
static constexpr uint8_t EMS_OFFSET_RC35Set_targetflowtemp = 14; // target flow temperature
static constexpr uint8_t EMS_OFFSET_RC35Set_seltemp = 37; // selected temp
static constexpr uint8_t EMS_OFFSET_RC35Set_noreducetemp = 38; // temp to stop reducing
static constexpr uint8_t EMS_OFFSET_RC35Set_reducetemp = 39; // temp reducing/hold
static constexpr uint8_t EMS_OFFSET_RC35Set_vacreducetemp = 40; // temp reducing/hold in vacations
static constexpr uint8_t EMS_OFFSET_RC35Set_vacreducemode = 41; // reduce mode in vacations
static constexpr uint8_t EMS_OFFSET_RC35Set_reducemode = 25; // reduce mode in normal operation
static constexpr uint8_t EMS_OFFSET_RC35Set_nofrostmode = 28; // 0-off, 1-room, 2-outdoor
static constexpr uint8_t EMS_OFFSET_RC35Set_control = 26; // 0-off, 1-RC20, 2-RC3x
static constexpr uint8_t EMS_OFFSET_RC35Set_temp_offset = 6;
static constexpr uint8_t EMS_OFFSET_RC35Set_temp_flowoffset = 24;
static constexpr uint8_t EMS_OFFSET_RC35Set_temp_design = 17;
@@ -283,9 +309,10 @@ class Thermostat : public EMSdevice {
static constexpr uint8_t AUTO_HEATING_CIRCUIT = 0;
// Installation settings
static constexpr uint8_t EMS_TYPE_IBASettings = 0xA5; // installation settings
static constexpr uint8_t EMS_TYPE_wwSettings = 0x37; // ww settings
static constexpr uint8_t EMS_TYPE_time = 0x06; // time
static constexpr uint8_t EMS_TYPE_IBASettings = 0xA5; // installation settings
static constexpr uint8_t EMS_TYPE_RC30Settings = 0xA7; // RC30 settings
static constexpr uint8_t EMS_TYPE_wwSettings = 0x37; // ww settings
static constexpr uint8_t EMS_TYPE_time = 0x06; // time
std::shared_ptr<Thermostat::HeatingCircuit> heating_circuit(std::shared_ptr<const Telegram> telegram);
std::shared_ptr<Thermostat::HeatingCircuit> heating_circuit(const uint8_t hc_num);
@@ -333,6 +360,8 @@ class Thermostat : public EMSdevice {
void process_JunkersSet2(std::shared_ptr<const Telegram> telegram);
void process_EasyMonitor(std::shared_ptr<const Telegram> telegram);
void process_JunkersRemoteMonitor(std::shared_ptr<const Telegram> telegram);
void process_JunkersHybridSettings(std::shared_ptr<const Telegram> telegram);
void process_JunkersSetMixer(std::shared_ptr<const Telegram> telegram);
// internal helper functions
bool set_mode_n(const uint8_t mode, const uint8_t hc_num);
@@ -368,6 +397,11 @@ class Thermostat : public EMSdevice {
bool set_manualtemp(const char * value, const int8_t id);
bool set_tempautotemp(const char * value, const int8_t id);
bool set_noreducetemp(const char * value, const int8_t id);
bool set_reducetemp(const char * value, const int8_t id);
bool set_vacreducetemp(const char * value, const int8_t id);
bool set_vacreducemode(const char * value, const int8_t id);
bool set_nofrostmode(const char * value, const int8_t id);
bool set_remotetemp(const char * value, const int8_t id);
bool set_roominfluence(const char * value, const int8_t id);
bool set_roominfl_factor(const char * value, const int8_t id);
@@ -413,6 +447,19 @@ class Thermostat : public EMSdevice {
bool set_reducehours(const char * value, const int8_t id);
bool set_backlight(const char * value, const int8_t id);
bool set_heatingpid(const char * value, const int8_t id);
bool set_brightness(const char * value, const int8_t id);
bool set_autodst(const char * value, const int8_t id);
bool set_preheating(const char * value, const int8_t id);
bool set_mixingvalves(const char * value, const int8_t id);
bool set_offtemp(const char * value, const int8_t id);
bool set_hybridStrategy(const char * value, const int8_t id);
bool set_switchOverTemp(const char * value, const int8_t id);
bool set_energyCostRatio(const char * value, const int8_t id);
bool set_fossileFactor(const char * value, const int8_t id);
bool set_electricFactor(const char * value, const int8_t id);
bool set_delayBoiler(const char * value, const int8_t id);
bool set_tempDiffBoiler(const char * value, const int8_t id);
};
} // namespace emsesp

View File

@@ -26,7 +26,7 @@ namespace emsesp {
uint8_t EMSdevice::count_entities() {
uint8_t count = 0;
for (auto & dv : devicevalues_) {
if (dv.has_state(DeviceValueState::DV_VISIBLE) && dv.hasValue()) {
if (!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && dv.hasValue()) {
count++;
}
}
@@ -258,7 +258,7 @@ bool EMSdevice::has_tag(const uint8_t tag) {
// called from the command 'entities'
void EMSdevice::list_device_entries(JsonObject & output) const {
for (const auto & dv : devicevalues_) {
if (dv.has_state(DeviceValueState::DV_VISIBLE) && dv.type != DeviceValueType::CMD && dv.full_name) {
if (!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && dv.type != DeviceValueType::CMD && dv.full_name) {
// if we have a tag prefix it
char key[50];
if (!EMSdevice::tag_to_mqtt(dv.tag).empty()) {
@@ -321,6 +321,11 @@ void EMSdevice::show_telegram_handlers(uuid::console::Shell & shell) const {
}
}
shell.println();
shell.printf(F(" Ignored telegram type IDs: "));
for (auto handlers : handlers_ignored_) {
shell.printf(F("0x%02X "), handlers);
}
shell.println();
}
// list all the telegram type IDs for this device, outputting to a string (max size 200)
@@ -343,10 +348,27 @@ char * EMSdevice::show_telegram_handlers(char * result, const size_t len, const
strlcat(result, Helpers::hextoa(tf.telegram_type_id_, true).c_str(), len);
}
}
if (handlers == Handlers::ALL || handlers == Handlers::IGNORED) {
i = 0;
for (auto handlers : handlers_ignored_) {
if (i++ > 0) {
strlcat(result, " ", len);
}
strlcat(result, Helpers::hextoa(handlers).c_str(), len);
}
}
return result;
}
void EMSdevice::add_handlers_ignored(const uint16_t handler) {
for (auto handlers : handlers_ignored_) {
if (handler == handlers) {
return;
}
}
handlers_ignored_.push_back(handler);
}
// list all the mqtt handlers for this device
void EMSdevice::show_mqtt_handlers(uuid::console::Shell & shell) const {
Mqtt::show_topic_handlers(shell, device_type_);
@@ -405,19 +427,18 @@ void EMSdevice::register_device_value(uint8_t tag,
}
}
// this is the unique id set for the device entity. it's a simple sequence number
uint8_t dv_id = get_next_dv_id();
// determine state
uint8_t state = DeviceValueState::DV_VISIBLE; // default to visible
uint8_t state = DeviceValueState::DV_DEFAULT;
// scan through customizations to see if it's on the exclusion list by matching the productID and deviceID
EMSESP::webCustomizationService.read([&](WebCustomization & settings) {
for (EntityCustomization entityCustomization : settings.entityCustomizations) {
if ((entityCustomization.product_id == product_id()) && (entityCustomization.device_id == device_id())) {
for (uint8_t entity_id : entityCustomization.entity_ids) {
if (entity_id == dv_id) {
state = DeviceValueState::DV_DEFAULT; // it's on the exclude list, turn off active and visible flags
std::string entity = tag < DeviceValueTAG::TAG_HC1 ? read_flash_string(short_name) : tag_to_string(tag) + "/" + read_flash_string(short_name);
for (std::string entity_id : entityCustomization.entity_ids) {
if (entity_id.substr(2) == entity) {
uint8_t mask = Helpers::hextoint(entity_id.substr(0, 2).c_str());
state = mask << 4; // set state high bits to flag, turn off active and ha flags
break;
}
}
@@ -426,7 +447,7 @@ void EMSdevice::register_device_value(uint8_t tag,
});
// add the device
devicevalues_.emplace_back(device_type_, tag, value_p, type, options, options_size, short_name, full_name, uom, 0, has_cmd, min, max, state, dv_id);
devicevalues_.emplace_back(device_type_, tag, value_p, type, options, options_size, short_name, full_name, uom, 0, has_cmd, min, max, state);
}
// function with min and max values
@@ -454,7 +475,7 @@ void EMSdevice::register_device_value(uint8_t tag,
if (tag >= DeviceValueTAG::TAG_HC1 && tag <= DeviceValueTAG::TAG_HC8) {
flags |= CommandFlag::MQTT_SUB_FLAG_HC;
} else if (tag >= DeviceValueTAG::TAG_WWC1 && tag <= DeviceValueTAG::TAG_WWC4) {
} else if (tag >= DeviceValueTAG::TAG_WWC1 && tag <= DeviceValueTAG::TAG_WWC10) {
flags |= CommandFlag::MQTT_SUB_FLAG_WWC;
} else if (tag == DeviceValueTAG::TAG_DEVICE_DATA_WW || tag == DeviceValueTAG::TAG_BOILER_DATA_WW) {
flags |= CommandFlag::MQTT_SUB_FLAG_WW;
@@ -486,11 +507,33 @@ void EMSdevice::register_device_value(uint8_t tag,
register_device_value(tag, value_p, type, options, name, uom, nullptr, 0, 0);
}
// check if value is visible
bool EMSdevice::is_visible(const void * value_p) const {
// check if value is readable via mqtt/api
bool EMSdevice::is_readable(const void * value_p) const {
for (const auto & dv : devicevalues_) {
if (dv.value_p == value_p) {
return dv.has_state(DeviceValueState::DV_VISIBLE);
return !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE);
}
}
return false;
}
// check if value/command is readonly
bool EMSdevice::is_readonly(const std::string & cmd, const int8_t id) const {
uint8_t tag = id > 0 ? DeviceValueTAG::TAG_HC1 + id - 1 : DeviceValueTAG::TAG_NONE;
for (const auto & dv : devicevalues_) {
// check command name and tag, id -1 is default hc and only checks name
if (dv.has_cmd && read_flash_string(dv.short_name) == cmd && (dv.tag < DeviceValueTAG::TAG_HC1 || dv.tag == tag || id == -1)) {
return dv.has_state(DeviceValueState::DV_READONLY);
}
}
return true; // not found, no write
}
// check if value has a registered command
bool EMSdevice::has_command(const void * value_p) const {
for (const auto & dv : devicevalues_) {
if (dv.value_p == value_p) {
return dv.has_cmd && !dv.has_state(DeviceValueState::DV_READONLY);
}
}
return false;
@@ -503,7 +546,7 @@ void EMSdevice::publish_value(void * value_p) const {
}
for (const auto & dv : devicevalues_) {
if (dv.value_p == value_p && dv.has_state(DeviceValueState::DV_VISIBLE)) {
if (dv.value_p == value_p && !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE)) {
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
if (Mqtt::publish_single2cmd()) {
if (dv.tag >= DeviceValueTAG::TAG_HC1) {
@@ -528,7 +571,7 @@ void EMSdevice::publish_value(void * value_p) const {
}
int8_t divider = (dv.options_size == 1) ? Helpers::atoint(read_flash_string(dv.options[0]).c_str()) : 0;
char payload[30] = {'\0'};
char payload[50] = {'\0'};
uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (dv.uom == DeviceValueUOM::DEGREES) ? 2 : (dv.uom == DeviceValueUOM::DEGREES_R) ? 1 : 0;
switch (dv.type) {
@@ -561,7 +604,7 @@ void EMSdevice::publish_value(void * value_p) const {
Helpers::render_value(payload, *(uint32_t *)(value_p), divider, fahrenheit);
break;
case DeviceValueType::BOOL: {
Helpers::render_boolean(payload, (bool)(*(uint8_t *)(value_p)));
Helpers::render_boolean(payload, (bool)*(uint8_t *)(value_p));
break;
}
case DeviceValueType::TIME:
@@ -603,7 +646,7 @@ std::string EMSdevice::get_value_uom(const char * key) const {
// look up key in our device value list
for (const auto & dv : devicevalues_) {
if ((dv.has_state(DeviceValueState::DV_VISIBLE) && dv.full_name) && (read_flash_string(dv.full_name) == key_p)) {
if ((!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && dv.full_name) && (read_flash_string(dv.full_name) == key_p)) {
// ignore TIME since "minutes" is already added to the string value
if ((dv.uom == DeviceValueUOM::NONE) || (dv.uom == DeviceValueUOM::MINUTES)) {
break;
@@ -618,114 +661,124 @@ std::string EMSdevice::get_value_uom(const char * key) const {
// prepare array of device values used for the WebUI
// this is loosely based of the function generate_values used for the MQTT and Console
// except additional data is stored in the JSON document needed for the Web UI like the UOM and command
// v = value, u=uom, n=name, c=cmd
// v=value, u=uom, n=name, c=cmd, h=help string, s=step, m=min, x=max
void EMSdevice::generate_values_web(JsonObject & output) {
output["label"] = to_string_short();
JsonArray data = output.createNestedArray("data");
for (auto & dv : devicevalues_) {
// check conditions:
// 1. full_name cannot be empty
// 2. it must have a valid value, if it is not a command like 'reset'
// do two passes. First for all entities marked as favorites, then for all others. This sorts the list.
for (int8_t fav = 1; fav >= 0; fav--) {
for (auto & dv : devicevalues_) {
// check conditions:
// 1. full_name cannot be empty
// 2. it must have a valid value, if it is not a command like 'reset'
// 3. show favorites first
bool show = (fav && dv.has_state(DeviceValueState::DV_FAVORITE)) || (!fav && !dv.has_state(DeviceValueState::DV_FAVORITE));
if (show && !dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && dv.full_name && (dv.hasValue() || (dv.type == DeviceValueType::CMD))) {
JsonObject obj = data.createNestedObject(); // create the object, we know there is a value
uint8_t fahrenheit = 0;
if (dv.has_state(DeviceValueState::DV_VISIBLE) && dv.full_name && (dv.hasValue() || (dv.type == DeviceValueType::CMD))) {
JsonObject obj = data.createNestedObject(); // create the object, we know there is a value
uint8_t fahrenheit = 0;
// handle Booleans (true, false), use strings, no native true/false)
if (dv.type == DeviceValueType::BOOL) {
bool value_b = (bool)*(uint8_t *)(dv.value_p);
if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
obj["v"] = value_b ? 1 : 0;
} else {
char s[7];
obj["v"] = Helpers::render_boolean(s, value_b);
}
}
// handle Booleans (true, false)
if (dv.type == DeviceValueType::BOOL) {
bool value_b = *(bool *)(dv.value_p);
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
obj["v"] = value_b ? "true" : "false";
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
obj["v"] = value_b ? 1 : 0;
// handle TEXT strings
else if (dv.type == DeviceValueType::STRING) {
obj["v"] = (char *)(dv.value_p);
}
// handle ENUMs
else if ((dv.type == DeviceValueType::ENUM) && (*(uint8_t *)(dv.value_p) < dv.options_size)) {
obj["v"] = dv.options[*(uint8_t *)(dv.value_p)];
}
// handle numbers
else {
// If a divider is specified, do the division to 2 decimals places and send back as double/float
// otherwise force as an integer whole
// the nested if's is necessary due to the way the ArduinoJson templates are pre-processed by the compiler
int8_t divider = (dv.options_size == 1) ? Helpers::atoint(read_flash_string(dv.options[0]).c_str()) : 0;
fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (dv.uom == DeviceValueUOM::DEGREES) ? 2 : (dv.uom == DeviceValueUOM::DEGREES_R) ? 1 : 0;
if ((dv.type == DeviceValueType::INT) && Helpers::hasValue(*(int8_t *)(dv.value_p))) {
obj["v"] = Helpers::round2(*(int8_t *)(dv.value_p), divider, fahrenheit);
} else if ((dv.type == DeviceValueType::UINT) && Helpers::hasValue(*(uint8_t *)(dv.value_p))) {
obj["v"] = Helpers::round2(*(uint8_t *)(dv.value_p), divider, fahrenheit);
} else if ((dv.type == DeviceValueType::SHORT) && Helpers::hasValue(*(int16_t *)(dv.value_p))) {
obj["v"] = Helpers::round2(*(int16_t *)(dv.value_p), divider, fahrenheit);
} else if ((dv.type == DeviceValueType::USHORT) && Helpers::hasValue(*(uint16_t *)(dv.value_p))) {
obj["v"] = Helpers::round2(*(uint16_t *)(dv.value_p), divider, fahrenheit);
} else if ((dv.type == DeviceValueType::ULONG) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
obj["v"] = Helpers::round2(*(uint32_t *)(dv.value_p), divider, fahrenheit);
} else if ((dv.type == DeviceValueType::TIME) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
uint32_t time_value = *(uint32_t *)(dv.value_p);
obj["v"] = (divider > 0) ? time_value / divider : time_value; // sometimes we need to divide by 60
}
}
// add the unit of measure (uom)
obj["u"] = fahrenheit ? (uint8_t)DeviceValueUOM::FAHRENHEIT : dv.uom;
auto mask = Helpers::hextoa((uint8_t)(dv.state >> 4), false); // create mask to a 2-char string
// add name, prefixing the tag if it exists
if ((dv.tag == DeviceValueTAG::TAG_NONE) || tag_to_string(dv.tag).empty()) {
obj["n"] = mask + read_flash_string(dv.full_name);
} else if (dv.tag < DeviceValueTAG::TAG_HC1) {
obj["n"] = mask + tag_to_string(dv.tag) + " " + read_flash_string(dv.full_name);
} else {
char s[7];
obj["v"] = Helpers::render_boolean(s, value_b);
obj["n"] = mask + tag_to_string(dv.tag) + " " + read_flash_string(dv.full_name);
}
}
// handle TEXT strings
else if (dv.type == DeviceValueType::STRING) {
obj["v"] = (char *)(dv.value_p);
}
// handle ENUMs
else if ((dv.type == DeviceValueType::ENUM) && (*(uint8_t *)(dv.value_p) < dv.options_size)) {
obj["v"] = dv.options[*(uint8_t *)(dv.value_p)];
}
// handle numbers
else {
// If a divider is specified, do the division to 2 decimals places and send back as double/float
// otherwise force as an integer whole
// the nested if's is necessary due to the way the ArduinoJson templates are pre-processed by the compiler
int8_t divider = (dv.options_size == 1) ? Helpers::atoint(read_flash_string(dv.options[0]).c_str()) : 0;
fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (dv.uom == DeviceValueUOM::DEGREES) ? 2 : (dv.uom == DeviceValueUOM::DEGREES_R) ? 1 : 0;
if ((dv.type == DeviceValueType::INT) && Helpers::hasValue(*(int8_t *)(dv.value_p))) {
obj["v"] = Helpers::round2(*(int8_t *)(dv.value_p), divider, fahrenheit);
} else if ((dv.type == DeviceValueType::UINT) && Helpers::hasValue(*(uint8_t *)(dv.value_p))) {
obj["v"] = Helpers::round2(*(uint8_t *)(dv.value_p), divider, fahrenheit);
} else if ((dv.type == DeviceValueType::SHORT) && Helpers::hasValue(*(int16_t *)(dv.value_p))) {
obj["v"] = Helpers::round2(*(int16_t *)(dv.value_p), divider, fahrenheit);
} else if ((dv.type == DeviceValueType::USHORT) && Helpers::hasValue(*(uint16_t *)(dv.value_p))) {
obj["v"] = Helpers::round2(*(uint16_t *)(dv.value_p), divider, fahrenheit);
} else if ((dv.type == DeviceValueType::ULONG) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
obj["v"] = Helpers::round2(*(uint32_t *)(dv.value_p), divider, fahrenheit);
} else if ((dv.type == DeviceValueType::TIME) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
uint32_t time_value = *(uint32_t *)(dv.value_p);
obj["v"] = (divider > 0) ? time_value / divider : time_value; // sometimes we need to divide by 60
}
}
// add the unit of measure (uom)
obj["u"] = fahrenheit ? (uint8_t)DeviceValueUOM::FAHRENHEIT : dv.uom;
// add name, prefixing the tag if it exists
if ((dv.tag == DeviceValueTAG::TAG_NONE) || tag_to_string(dv.tag).empty()) {
obj["n"] = dv.full_name;
} else if (dv.tag < DeviceValueTAG::TAG_HC1) {
obj["n"] = tag_to_string(dv.tag) + " " + read_flash_string(dv.full_name);
} else {
obj["n"] = tag_to_string(dv.tag) + " " + read_flash_string(dv.full_name);
}
// add commands and options
if (dv.has_cmd) {
// add the name of the Command function
if (dv.tag >= DeviceValueTAG::TAG_HC1) {
obj["c"] = tag_to_mqtt(dv.tag) + "/" + read_flash_string(dv.short_name);
} else {
obj["c"] = dv.short_name;
}
// add the Command options
if (dv.type == DeviceValueType::ENUM || (dv.type == DeviceValueType::CMD && dv.options_size > 1)) {
JsonArray l = obj.createNestedArray("l");
for (uint8_t i = 0; i < dv.options_size; i++) {
if (!read_flash_string(dv.options[i]).empty()) {
l.add(read_flash_string(dv.options[i]));
// add commands and options
if (dv.has_cmd && !dv.has_state(DeviceValueState::DV_READONLY)) {
// add the name of the Command function
if (dv.tag >= DeviceValueTAG::TAG_HC1) {
obj["c"] = tag_to_mqtt(dv.tag) + "/" + read_flash_string(dv.short_name);
} else {
obj["c"] = dv.short_name;
}
// add the Command options
if (dv.type == DeviceValueType::ENUM || (dv.type == DeviceValueType::CMD && dv.options_size > 1)) {
JsonArray l = obj.createNestedArray("l");
for (uint8_t i = 0; i < dv.options_size; i++) {
if (!read_flash_string(dv.options[i]).empty()) {
l.add(read_flash_string(dv.options[i]));
}
}
} else if (dv.type == DeviceValueType::BOOL) {
JsonArray l = obj.createNestedArray("l");
char result[10];
l.add(Helpers::render_boolean(result, false));
l.add(Helpers::render_boolean(result, true));
}
// add command help template
else if (dv.type == DeviceValueType::STRING || dv.type == DeviceValueType::CMD) {
if (dv.options_size == 1) {
obj["h"] = dv.options[0];
}
}
} else if (dv.type == DeviceValueType::BOOL) {
JsonArray l = obj.createNestedArray("l");
l.add("off");
l.add("on");
}
// add command help template
else if (dv.type == DeviceValueType::STRING || dv.type == DeviceValueType::CMD) {
if (dv.options_size == 1) {
obj["h"] = dv.options[0];
}
}
// add steps to numeric values with divider/multiplier
else {
int8_t divider = (dv.options_size == 1) ? Helpers::atoint(read_flash_string(dv.options[0]).c_str()) : 0;
char s[10];
if (divider > 0) {
obj["s"] = Helpers::render_value(s, (float)1 / divider, 1);
} else if (divider < 0) {
obj["s"] = Helpers::render_value(s, (-1) * divider, 0);
// add steps to numeric values with divider/multiplier
else {
int8_t divider = (dv.options_size == 1) ? Helpers::atoint(read_flash_string(dv.options[0]).c_str()) : 0;
char s[10];
if (divider > 0) {
obj["s"] = Helpers::render_value(s, (float)1 / divider, 1);
} else if (divider < 0) {
obj["s"] = Helpers::render_value(s, (-1) * divider, 0);
}
int16_t dv_set_min, dv_set_max;
if (dv.get_min_max(dv_set_min, dv_set_max)) {
obj["m"] = Helpers::render_value(s, dv_set_min, 0);
obj["x"] = Helpers::render_value(s, dv_set_max, 0);
}
}
}
}
@@ -733,42 +786,19 @@ void EMSdevice::generate_values_web(JsonObject & output) {
}
}
// reset all entities to being visible
// this is called before loading in the exclude entities list from the customization service
void EMSdevice::reset_exclude_entities() {
for (auto & dv : devicevalues_) {
dv.add_state(DeviceValueState::DV_VISIBLE);
}
}
// disable/exclude a device entity based on its unique id
void EMSdevice::exclude_entity(uint8_t id) {
for (auto & dv : devicevalues_) {
if (dv.id == id) {
#if defined(EMSESP_USE_SERIAL)
Serial.print("exclude_entity() Removing Visible for device value: ");
Serial.println(read_flash_string(dv.full_name).c_str());
#endif
dv.remove_state(DeviceValueState::DV_VISIBLE); // this will remove from MQTT payloads and showing in web & console
return;
}
}
}
// as generate_values_web() but stripped down to only show all entities and their state
// this is used only for WebCustomizationService::device_entities()
void EMSdevice::generate_values_web_all(JsonArray & output) {
for (auto & dv : devicevalues_) {
for (const auto & dv : devicevalues_) {
// also show commands and entities that have an empty full name
JsonObject obj = output.createNestedObject();
// create the value
if (dv.hasValue()) {
// handle Booleans (true, false)
// handle Booleans (true, false), use strings, no native true/false)
if (dv.type == DeviceValueType::BOOL) {
bool value_b = *(bool *)(dv.value_p);
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
obj["v"] = value_b;
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
bool value_b = (bool)*(uint8_t *)(dv.value_p);
if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
obj["v"] = value_b ? 1 : 0;
} else {
char s[7];
@@ -830,8 +860,9 @@ void EMSdevice::generate_values_web_all(JsonArray & output) {
obj["n"] = name;
}
} else {
obj["n"] = "(hidden)";
obj["n"] = "";
}
// shortname
if (dv.tag >= DeviceValueTAG::TAG_HC1) {
obj["s"] = tag_to_string(dv.tag) + "/" + read_flash_string(dv.short_name);
@@ -839,14 +870,45 @@ void EMSdevice::generate_values_web_all(JsonArray & output) {
obj["s"] = dv.short_name;
}
// is it marked as excluded?
obj["x"] = !dv.has_state(DeviceValueState::DV_VISIBLE);
// add the unique ID
obj["i"] = dv.id;
obj["m"] = dv.state >> 4; // send back the mask state. We're only interested in the high nibble
obj["w"] = dv.has_cmd; // if writable
}
}
// set mask per device entity based on the id which is prefixed with the 2 char hex mask value
// returns true if the entity has a mask set (not 0 the default)
void EMSdevice::mask_entity(const std::string & entity_id) {
for (auto & dv : devicevalues_) {
std::string entity_name =
dv.tag < DeviceValueTAG::TAG_HC1 ? read_flash_string(dv.short_name) : tag_to_string(dv.tag) + "/" + read_flash_string(dv.short_name);
if (entity_name == entity_id.substr(2)) {
// this entity has a new mask set
uint8_t current_mask = dv.state >> 4;
uint8_t new_mask = Helpers::hextoint(entity_id.substr(0, 2).c_str()); // first character contains mask flags
if (Mqtt::ha_enabled() && ((current_mask ^ new_mask) & (DeviceValueState::DV_READONLY >> 4))) {
// remove ha config on change of dv_readonly flag
dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED);
Mqtt::publish_ha_sensor_config(dv, "", "", true); // delete topic (remove = true)
}
dv.state = ((dv.state & 0x0F) | (new_mask << 4)); // set state high bits to flag
return;
}
}
}
// populate a string vector with entities that have masks set
void EMSdevice::getMaskedEntities(std::vector<std::string> & entity_ids) {
for (auto & dv : devicevalues_) {
std::string entity_name =
dv.tag < DeviceValueTAG::TAG_HC1 ? read_flash_string(dv.short_name) : tag_to_string(dv.tag) + "/" + read_flash_string(dv.short_name);
uint8_t mask = dv.state >> 4;
if (mask) {
entity_ids.push_back(Helpers::hextoa(mask, false) + entity_name);
}
}
}
// builds json for a specific device value / entity
// cmd is the endpoint or name of the device entity
// returns false if failed, otherwise true
@@ -855,7 +917,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
int8_t tag = id;
// check if we have hc or wwc or hs
if (id >= 1 && id <= 29) {
if (id >= 1 && id <= 34) {
tag = DeviceValueTAG::TAG_HC1 + id - 1;
} else if (id != -1) {
return false; // error
@@ -939,7 +1001,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
case DeviceValueType::BOOL:
if (Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) {
auto value_b = (bool)(*(uint8_t *)(dv.value_p));
bool value_b = (bool)*(uint8_t *)(dv.value_p);
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
json[value] = value_b;
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
@@ -993,8 +1055,9 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
json["uom"] = uom_to_string(dv.uom);
}
json["writeable"] = dv.has_cmd;
json["visible"] = dv.has_state(DeviceValueState::DV_VISIBLE);
json["readable"] = !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE);
json["writeable"] = dv.has_cmd && !dv.has_state(DeviceValueState::DV_READONLY);
json["visible"] = !dv.has_state(DeviceValueState::DV_WEB_EXCLUDE);
// if there is no value, mention it
if (!json.containsKey(value)) {
@@ -1038,14 +1101,11 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
// check conditions:
// 1. it must have a valid value (state is active)
// 2. it must have a visible, unless the output_target is MQTT
// 2. it must have a visible flag
// 3. it must match the given tag filter or have an empty tag
bool conditions = ((tag_filter == DeviceValueTAG::TAG_NONE) || (tag_filter == dv.tag)) && dv.has_state(DeviceValueState::DV_ACTIVE);
// 4. for MQTT we want to always show the special HA entities (they have an empty fullname)
bool visible = ((dv.has_state(DeviceValueState::DV_VISIBLE)) || ((output_target == OUTPUT_TARGET::MQTT) && (!dv.full_name)));
conditions &= visible;
if (conditions) {
// 4. it must not have the exclude flag set or outputs to console
if (dv.has_state(DeviceValueState::DV_ACTIVE) && dv.full_name && (tag_filter == DeviceValueTAG::TAG_NONE || tag_filter == dv.tag)
&& (output_target == OUTPUT_TARGET::CONSOLE || !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE))) {
has_values = true; // flagged if we actually have data
// we have a tag if it matches the filter given, and that the tag name is not empty/""
@@ -1053,7 +1113,7 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
// create the name for the JSON key
char name[80];
if (output_target == OUTPUT_TARGET::API_VERBOSE) {
if (output_target == OUTPUT_TARGET::API_VERBOSE || output_target == OUTPUT_TARGET::CONSOLE) {
if (have_tag) {
snprintf(name, 80, "%s %s", tag_to_string(dv.tag).c_str(), read_flash_string(dv.full_name).c_str()); // prefix the tag
} else {
@@ -1074,7 +1134,7 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
// handle Booleans
if (dv.type == DeviceValueType::BOOL && Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) {
// see how to render the value depending on the setting
auto value_b = (bool)*(uint8_t *)(dv.value_p);
bool value_b = (bool)*(uint8_t *)(dv.value_p);
if (Mqtt::ha_enabled() && (output_target == OUTPUT_TARGET::MQTT)) {
char s[7];
json[name] = Helpers::render_boolean(s, value_b); // for HA always render as string
@@ -1166,7 +1226,7 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
} else if ((dv.type == DeviceValueType::TIME) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
uint32_t time_value = *(uint32_t *)(dv.value_p);
time_value = Helpers::round2(time_value, divider); // sometimes we need to divide by 60
if (output_target == EMSdevice::OUTPUT_TARGET::API_VERBOSE) {
if (output_target == OUTPUT_TARGET::API_VERBOSE || output_target == OUTPUT_TARGET::CONSOLE) {
char time_s[40];
snprintf(time_s,
sizeof(time_s),
@@ -1193,12 +1253,12 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
return has_values;
}
// remove the Home Assistant configs for each device value/entity if its not visible or active
// remove the Home Assistant configs for each device value/entity if its not visible or active or marked as read-only
// this is called when an MQTT publish is done via an EMS Device in emsesp.cpp::publish_device_values()
void EMSdevice::mqtt_ha_entity_config_remove() {
for (auto & dv : devicevalues_) {
if (dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED)
&& ((!dv.has_state(DeviceValueState::DV_VISIBLE)) || (!dv.has_state(DeviceValueState::DV_ACTIVE)))) {
&& ((dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE)) || (!dv.has_state(DeviceValueState::DV_ACTIVE)))) {
dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED);
if (dv.short_name == FL_(climate)[0]) {
Mqtt::publish_ha_climate_config(dv.tag, false, true); // delete topic (remove = true)
@@ -1218,7 +1278,7 @@ void EMSdevice::mqtt_ha_entity_config_create() {
// create climate if roomtemp is visible
// create the discovery topic if if hasn't already been created, not a command (like reset) and is active and visible
for (auto & dv : devicevalues_) {
if ((dv.short_name == FL_(climate)[0]) && dv.has_state(DeviceValueState::DV_VISIBLE) && dv.has_state(DeviceValueState::DV_ACTIVE)) {
if ((dv.short_name == FL_(climate)[0]) && !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE) && dv.has_state(DeviceValueState::DV_ACTIVE)) {
if (*(int8_t *)(dv.value_p) == 1 && (!dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED) || dv.has_state(DeviceValueState::DV_HA_CLIMATE_NO_RT))) {
dv.remove_state(DeviceValueState::DV_HA_CLIMATE_NO_RT);
dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED);
@@ -1231,7 +1291,7 @@ void EMSdevice::mqtt_ha_entity_config_create() {
}
}
if (!dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED) && (dv.type != DeviceValueType::CMD) && dv.has_state(DeviceValueState::DV_ACTIVE)
&& dv.has_state(DeviceValueState::DV_VISIBLE)) {
&& !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE)) {
// create_device_config is only done once for the EMS device. It can added to any entity, so we take the first
Mqtt::publish_ha_sensor_config(dv, name(), brand_to_string(), false, create_device_config);
dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED);

View File

@@ -179,14 +179,16 @@ class EMSdevice {
std::string to_string() const;
std::string to_string_short() const;
enum Handlers : uint8_t { ALL, RECEIVED, FETCHED, PENDING };
enum Handlers : uint8_t { ALL, RECEIVED, FETCHED, PENDING, IGNORED };
void show_telegram_handlers(uuid::console::Shell & shell) const;
char * show_telegram_handlers(char * result, const size_t len, const uint8_t handlers);
void show_mqtt_handlers(uuid::console::Shell & shell) const;
void list_device_entries(JsonObject & output) const;
void exclude_entity(uint8_t entity_id);
void reset_exclude_entities();
void add_handlers_ignored(const uint16_t handler);
void mask_entity(const std::string & entity_id);
void getMaskedEntities(std::vector<std::string> & entity_ids);
using process_function_p = std::function<void(std::shared_ptr<const Telegram>)>;
@@ -197,7 +199,7 @@ class EMSdevice {
bool get_value_info(JsonObject & root, const char * cmd, const int8_t id);
void get_dv_info(JsonObject & json);
enum OUTPUT_TARGET : uint8_t { API_VERBOSE, API_SHORTNAMES, MQTT };
enum OUTPUT_TARGET : uint8_t { API_VERBOSE, API_SHORTNAMES, MQTT, CONSOLE };
bool generate_values(JsonObject & output, const uint8_t tag_filter, const bool nested, const uint8_t output_target);
void generate_values_web(JsonObject & output);
void generate_values_web_all(JsonArray & output);
@@ -244,7 +246,9 @@ class EMSdevice {
void read_command(const uint16_t type_id, uint8_t offset = 0, uint8_t length = 0) const;
bool is_visible(const void * value_p) const;
bool is_readable(const void * value_p) const;
bool is_readonly(const std::string & cmd, const int8_t id) const;
bool has_command(const void * value_p) const;
void publish_value(void * value_p) const;
void publish_all_values();
@@ -318,6 +322,7 @@ class EMSdevice {
static constexpr uint8_t EMS_DEVICE_FLAG_EMSPLUS = 2;
static constexpr uint8_t EMS_DEVICE_FLAG_HT3 = 3;
static constexpr uint8_t EMS_DEVICE_FLAG_HEATPUMP = 4;
static constexpr uint8_t EMS_DEVICE_FLAG_HYBRID = 5;
// Solar Module
static constexpr uint8_t EMS_DEVICE_FLAG_SM10 = 1;
@@ -384,10 +389,7 @@ class EMSdevice {
// device values
std::vector<DeviceValue> devicevalues_;
uint8_t dv_index_ = 0; // unique counter for each added device value
uint8_t get_next_dv_id() {
return (dv_index_++);
}
std::vector<uint16_t> handlers_ignored_;
};
} // namespace emsesp

View File

@@ -70,6 +70,12 @@ const __FlashStringHelper * const DeviceValue::DeviceValueTAG_s[] PROGMEM = {
F_(tag_wwc2), // "Wwc2"
F_(tag_wwc3), // "wwc3"
F_(tag_wwc4), // "wwc4"
F_(tag_wwc5), // "wwc5"
F_(tag_wwc6), // "wwc6"
F_(tag_wwc7), // "wwc7"
F_(tag_wwc8), // "wwc8"
F_(tag_wwc9), // "wwc9"
F_(tag_wwc10), // "wwc10"
F_(tag_hs1), // "hs1"
F_(tag_hs2), // "hs2"
F_(tag_hs3), // "hs3"
@@ -109,6 +115,12 @@ const __FlashStringHelper * const DeviceValue::DeviceValueTAG_mqtt[] PROGMEM = {
F_(tag_wwc2), // "Wwc2"
F_(tag_wwc3), // "wwc3"
F_(tag_wwc4), // "wwc4"
F_(tag_wwc5), // "wwc5"
F_(tag_wwc6), // "wwc6"
F_(tag_wwc7), // "wwc7"
F_(tag_wwc8), // "wwc8"
F_(tag_wwc9), // "wwc9"
F_(tag_wwc10), // "wwc10"
F_(tag_hs1), // "hs1"
F_(tag_hs2), // "hs2"
F_(tag_hs3), // "hs3"
@@ -134,7 +146,7 @@ size_t DeviceValue::tag_count = sizeof(DeviceValue::DeviceValueTAG_s) / sizeof(_
// checks whether the device value has an actual value
// returns true if its valid
// state is stored in the dv object
bool DeviceValue::hasValue() {
bool DeviceValue::hasValue() const {
bool has_value = false;
switch (type) {
case DeviceValueType::BOOL:

View File

@@ -90,6 +90,12 @@ class DeviceValue {
TAG_WWC2,
TAG_WWC3,
TAG_WWC4,
TAG_WWC5,
TAG_WWC6,
TAG_WWC7,
TAG_WWC8,
TAG_WWC9,
TAG_WWC10,
TAG_HS1,
TAG_HS2,
TAG_HS3,
@@ -110,11 +116,17 @@ class DeviceValue {
// states of a device value
enum DeviceValueState : uint8_t {
// low nibble active state of the device value
DV_DEFAULT = 0, // 0 - does not yet have a value
DV_ACTIVE = (1 << 0), // 1 - has a validated real value
DV_VISIBLE = (1 << 1), // 2 - shown on web, console and on MQTT payload. Otherwise hidden
DV_HA_CONFIG_CREATED = (1 << 2), // 4 - set if the HA config topic has been created
DV_HA_CLIMATE_NO_RT = (1 << 3) // 8 - climate created without roomTemp
DV_HA_CONFIG_CREATED = (1 << 1), // 2 - set if the HA config topic has been created
DV_HA_CLIMATE_NO_RT = (1 << 2), // 4 - climate created without roomTemp
// high nibble as mask for exclusions & special functions
DV_WEB_EXCLUDE = (1 << 4), // 16 - not shown on web
DV_API_MQTT_EXCLUDE = (1 << 5), // 32 - not shown on mqtt, API
DV_READONLY = (1 << 6), // 64 - read only
DV_FAVORITE = (1 << 7) // 128 - sort to front
};
uint8_t device_type; // EMSdevice::DeviceType
@@ -131,7 +143,6 @@ class DeviceValue {
int16_t min; // min range
uint16_t max; // max range
uint8_t state; // DeviceValueState::*
uint8_t id; // internal unique counter
DeviceValue(uint8_t device_type,
uint8_t tag,
@@ -146,8 +157,7 @@ class DeviceValue {
bool has_cmd,
int16_t min,
uint16_t max,
uint8_t state,
uint8_t id)
uint8_t state)
: device_type(device_type)
, tag(tag)
, value_p(value_p)
@@ -161,24 +171,23 @@ class DeviceValue {
, has_cmd(has_cmd)
, min(min)
, max(max)
, state(state)
, id(id) {
, state(state) {
}
bool hasValue();
bool hasValue() const;
bool get_min_max(int16_t & dv_set_min, int16_t & dv_set_max);
// state flags
inline void add_state(uint8_t s) {
void add_state(uint8_t s) {
state |= s;
}
inline bool has_state(uint8_t s) const {
bool has_state(uint8_t s) const {
return (state & s) == s;
}
inline void remove_state(uint8_t s) {
void remove_state(uint8_t s) {
state &= ~s;
}
inline uint8_t get_state() const {
uint8_t get_state() const {
return state;
}

View File

@@ -105,6 +105,15 @@ void EMSESP::fetch_device_values_type(const uint8_t device_type) {
}
}
bool EMSESP::cmd_is_readonly(const uint8_t device_type, const char * cmd, const int8_t id) {
for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_type)) {
return emsdevice->is_readonly(cmd, id);
}
}
return false;
}
// clears list of recognized devices
void EMSESP::clear_all_devices() {
// temporarily removed: clearing the list causes a crash, the associated commands and mqtt should also be removed.
@@ -308,7 +317,7 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) {
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XXLARGE_DYN); // use max size
JsonObject json = doc.to<JsonObject>();
emsdevice->generate_values(json, DeviceValueTAG::TAG_NONE, true, EMSdevice::OUTPUT_TARGET::API_VERBOSE); // verbose mode and nested
emsdevice->generate_values(json, DeviceValueTAG::TAG_NONE, true, EMSdevice::OUTPUT_TARGET::CONSOLE); // verbose mode and nested
// print line
uint8_t id = 0;
@@ -845,6 +854,9 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
if (wait_validate_ == telegram->type_id) {
wait_validate_ = 0;
}
if (!found && emsdevice->is_device_id(telegram->src) && telegram->message_length > 0) {
emsdevice->add_handlers_ignored(telegram->type_id);
}
break;
}
}
@@ -1090,7 +1102,7 @@ bool EMSESP::command_commands(uint8_t device_type, JsonObject & output, const in
bool EMSESP::command_info(uint8_t device_type, JsonObject & output, const int8_t id, const uint8_t output_target) {
bool has_value = false;
uint8_t tag;
if (id >= 1 && id <= 29) {
if (id >= 1 && id <= 34) {
tag = DeviceValueTAG::TAG_HC1 + id - 1; // this sets also WWC and HS
} else if (id == -1 || id == 0) {
tag = DeviceValueTAG::TAG_NONE;

View File

@@ -128,6 +128,7 @@ class EMSESP {
static void send_raw_telegram(const char * data);
static bool device_exists(const uint8_t device_id);
static bool cmd_is_readonly(const uint8_t device_type, const char * cmd, const int8_t id);
static uint8_t count_devices(const uint8_t device_type);
static uint8_t count_devices();

View File

@@ -189,12 +189,6 @@ char * Helpers::render_boolean(char * result, bool value) {
return result;
}
// // render for native char strings
// char * Helpers::render_value(char * result, const char * value, const int8_t format __attribute__((unused))) {
// strcpy(result, value); // un-safe but we don't care
// return result;
// }
// convert unsigned int (single byte) to text value and returns it
// format: 255(0xFF)=boolean, 0=no formatting, otherwise divide by format
char * Helpers::render_value(char * result, uint8_t value, int8_t format, const uint8_t fahrenheit) {

View File

@@ -37,7 +37,6 @@ class Helpers {
static char * render_value(char * result, const uint32_t value, const int8_t format, const uint8_t fahrenheit = 0);
static char * render_value(char * result, const int16_t value, const int8_t format, const uint8_t fahrenheit = 0);
static char * render_value(char * result, const int32_t value, const int8_t format, const uint8_t fahrenheit = 0);
// static char * render_value(char * result, const char * value, const int8_t format);
static char * render_boolean(char * result, bool value);
static char * hextoa(char * result, const uint8_t value);

View File

@@ -218,6 +218,12 @@ MAKE_PSTR(tag_wwc1, "wwc1")
MAKE_PSTR(tag_wwc2, "wwc2")
MAKE_PSTR(tag_wwc3, "wwc3")
MAKE_PSTR(tag_wwc4, "wwc4")
MAKE_PSTR(tag_wwc5, "wwc5")
MAKE_PSTR(tag_wwc6, "wwc6")
MAKE_PSTR(tag_wwc7, "wwc7")
MAKE_PSTR(tag_wwc8, "wwc8")
MAKE_PSTR(tag_wwc9, "wwc9")
MAKE_PSTR(tag_wwc10, "wwc10")
MAKE_PSTR(tag_hs1, "hs1")
MAKE_PSTR(tag_hs2, "hs2")
MAKE_PSTR(tag_hs3, "hs3")
@@ -330,7 +336,7 @@ MAKE_PSTR(functioning_mode, "functioning mode")
MAKE_PSTR(smoke_temperature, "Abgastemperatur")
// thermostat lists
MAKE_PSTR_LIST(tpl_datetime, F("Format: < hh:mm:ss dd/mm/yyyy-dw-dst | NTP >"))
MAKE_PSTR_LIST(tpl_datetime, F("Format: < NTP | dd.mm.yyyy-hh:mm:ss-dw-dst >"))
MAKE_PSTR_LIST(tpl_switchtime, F("Format: < nn.d.o.hh:mm >"))
MAKE_PSTR_LIST(tpl_holidays, F("Format: < dd.mm.yyyy-dd.mm.yyyy >"))
MAKE_PSTR_LIST(enum_ibaMainDisplay,
@@ -344,6 +350,7 @@ MAKE_PSTR_LIST(enum_ibaMainDisplay,
F_(date),
F_(smoke_temperature))
MAKE_PSTR_LIST(enum_ibaLanguage, F_(german), F_(dutch), F_(french), F_(italian))
MAKE_PSTR_LIST(enum_ibaLanguage_RC30, F_(german), F_(dutch))
MAKE_PSTR_LIST(enum_floordrystatus, F_(off), F_(start), F_(heat), F_(hold), F_(cool), F_(end))
MAKE_PSTR_LIST(enum_ibaBuildingType, F_(light), F_(medium), F_(heavy)) // RC300
MAKE_PSTR_LIST(enum_PID, F("fast"), F_(medium), F("slow"))
@@ -369,6 +376,7 @@ MAKE_PSTR_LIST(enum_modetype4, F_(nofrost), F_(eco), F_(heat))
MAKE_PSTR_LIST(enum_modetype5, F_(off), F_(on))
MAKE_PSTR_LIST(enum_reducemode, F_(nofrost), F_(reduce), F_(room), F_(outdoor))
MAKE_PSTR_LIST(enum_nofrostmode, F_(off), F_(room), F_(outdoor))
MAKE_PSTR_LIST(enum_controlmode, F_(off), F_(optimized), F_(simple), F_(mpc), F_(room), F_(power), F_(constant))
MAKE_PSTR_LIST(enum_controlmode2, F_(outdoor), F_(room))
@@ -451,6 +459,7 @@ MAKE_PSTR_LIST(setBurnPow, F("setburnpow"), F("Sollwert Brennerleistung"))
MAKE_PSTR_LIST(curBurnPow, F("curburnpow"), F("Brennerleistung"))
MAKE_PSTR_LIST(burnStarts, F("burnstarts"), F("Brenner # starts"))
MAKE_PSTR_LIST(burnWorkMin, F("burnworkmin"), F("Brenner Laufzeit"))
MAKE_PSTR_LIST(burn2WorkMin, F("burn2workmin"), F("Brenner Stufe 2 Laufzeit"))
MAKE_PSTR_LIST(heatWorkMin, F("heatworkmin"), F("Heizung Laufzeit"))
MAKE_PSTR_LIST(UBAuptime, F("ubauptime"), F("gesamte Laufzeit"))
MAKE_PSTR_LIST(lastCode, F("lastcode"), F("Fehlerspeicher"))
@@ -514,6 +523,16 @@ MAKE_PSTR_LIST(hpTl2, F("hptl2"), F("Außenlufttemperaturfühler (TL2)"))
MAKE_PSTR_LIST(hpPl1, F("hppl1"), F("Niedrigdruckfühler (PL1)"))
MAKE_PSTR_LIST(hpPh1, F("hpph1"), F("Hochdruckfühler (PH1)"))
// hybrid heatpump
MAKE_PSTR_LIST(enum_hybridStrategy, F("co2-optimized"), F("cost-optimized"), F("outside-temp-switched"), F("co2-cost-mix"))
MAKE_PSTR_LIST(hybridStrategy, F("hybridstrategy"), F("hybrid control strategy"))
MAKE_PSTR_LIST(switchOverTemp, F("switchovertemp"), F("outside switchover temperature"))
MAKE_PSTR_LIST(energyCostRatio, F("energycostratio"), F("energy cost ratio"))
MAKE_PSTR_LIST(fossileFactor, F("fossilefactor"), F("fossile energy factor"))
MAKE_PSTR_LIST(electricFactor, F("electricfactor"), F("electric energy factor"))
MAKE_PSTR_LIST(delayBoiler, F("delayboiler"), F("delay boiler support"))
MAKE_PSTR_LIST(tempDiffBoiler, F("tempdiffboiler"), F("tempediff boiler support"))
// the following are dhw for the boiler and automatically tagged with 'ww'
MAKE_PSTR_LIST(wWSelTemp, F("wwseltemp"), F("gewählte Temperatur"))
MAKE_PSTR_LIST(wwSelTempLow, F("wwseltemplow"), F("selected lower temperature"))
@@ -556,6 +575,7 @@ MAKE_PSTR_LIST(wwMaxTemp, F("wwmaxtemp"), F("Maximale Temperatur"))
MAKE_PSTR_LIST(wwOneTimeKey, F("wwonetimekey"), F("Einmalladungstaste"))
// mqtt values / commands
MAKE_PSTR_LIST(switchtime, F("switchtime"), F("program switchtime"))
MAKE_PSTR_LIST(switchtime1, F("switchtime1"), F("own1 program switchtime"))
MAKE_PSTR_LIST(switchtime2, F("switchtime2"), F("own2 program switchtime"))
MAKE_PSTR_LIST(wwswitchtime, F("wwswitchtime"), F("program switchtime"))
@@ -576,6 +596,11 @@ MAKE_PSTR_LIST(tempsensor2, F("inttemp2"), F("Temperatursensor 2"))
MAKE_PSTR_LIST(dampedoutdoortemp, F("dampedoutdoortemp"), F("gedämpfte Aussentemperatur"))
MAKE_PSTR_LIST(floordrystatus, F("floordry"), F("Estrichtrocknung"))
MAKE_PSTR_LIST(floordrytemp, F("floordrytemp"), F("Estrichtrocknungs Temperatur"))
MAKE_PSTR_LIST(brightness, F("brightness"), F("bildschirmhelligkeit"))
MAKE_PSTR_LIST(autodst, F("autodst"), F("automatische sommerzeit umstellung"))
MAKE_PSTR_LIST(preheating, F("preheating"), F("vorheizen im uhrenprogramm"))
MAKE_PSTR_LIST(offtemp, F("offtemp"), F("temperatur bei ausgeschaltetem modus"))
MAKE_PSTR_LIST(mixingvalves, F("mixingvalves"), F("mischventile"))
// thermostat ww
MAKE_PSTR_LIST(wwMode, F("wwmode"), F("modus"))
MAKE_PSTR_LIST(wwSetTempLow, F("wwsettemplow"), F("untere Solltemperatur"))
@@ -632,6 +657,10 @@ MAKE_PSTR_LIST(holidaymode, F("holidaymode"), F("Urlaubsbetrieb"))
MAKE_PSTR_LIST(flowtempoffset, F("flowtempoffset"), F("Flusstemperaturanhebung"))
MAKE_PSTR_LIST(reducemode, F("reducemode"), F("Absenkmodus"))
MAKE_PSTR_LIST(noreducetemp, F("noreducetemp"), F("Absenkung unterbrechen unter Temperatur"))
MAKE_PSTR_LIST(reducetemp, F("reducetemp"), F("Absenkmodus unter Temperatur"))
MAKE_PSTR_LIST(vacreducetemp, F("vacreducetemp"), F("Urlaub Absenkmodus unter Temperatur"))
MAKE_PSTR_LIST(vacreducemode, F("vacreducemode"), F("Urlaub Absenkmodus"))
MAKE_PSTR_LIST(nofrostmode, F("nofrostmode"), F("Frostschutz Modus"))
MAKE_PSTR_LIST(remotetemp, F("remotetemp"), F("Raumtemperatur der Fernsteuerung"))
MAKE_PSTR_LIST(reducehours, F("reducehours"), F("duration for nighttemp"))
MAKE_PSTR_LIST(reduceminutes, F("reduceminutes"), F("remaining time for nightmode"))
@@ -656,6 +685,7 @@ MAKE_PSTR_LIST(poolSetTemp, F("poolsettemp"), F("pool set temperature"))
MAKE_PSTR_LIST(poolTemp, F("pooltemp"), F("pool temperature"))
MAKE_PSTR_LIST(poolShuntStatus, F("poolshuntstatus"), F("pool shunt status opening/closing"))
MAKE_PSTR_LIST(poolShunt, F("poolshunt"), F("pool shunt open/close (0% = pool / 100% = heat)"))
MAKE_PSTR_LIST(hydrTemp, F("hydrTemp"), F("hydraulic header temperature"))
// solar
MAKE_PSTR_LIST(collectorTemp, F("collectortemp"), F("Kollektortemperatur (TS1)"))

View File

@@ -218,6 +218,12 @@ MAKE_PSTR(tag_wwc1, "wwc1")
MAKE_PSTR(tag_wwc2, "wwc2")
MAKE_PSTR(tag_wwc3, "wwc3")
MAKE_PSTR(tag_wwc4, "wwc4")
MAKE_PSTR(tag_wwc5, "wwc5")
MAKE_PSTR(tag_wwc6, "wwc6")
MAKE_PSTR(tag_wwc7, "wwc7")
MAKE_PSTR(tag_wwc8, "wwc8")
MAKE_PSTR(tag_wwc9, "wwc9")
MAKE_PSTR(tag_wwc10, "wwc10")
MAKE_PSTR(tag_hs1, "hs1")
MAKE_PSTR(tag_hs2, "hs2")
MAKE_PSTR(tag_hs3, "hs3")
@@ -331,7 +337,7 @@ MAKE_PSTR(functioning_mode, "functioning mode")
MAKE_PSTR(smoke_temperature, "smoke temperature")
// thermostat lists
MAKE_PSTR_LIST(tpl_datetime, F("Format: < NTP | hh:mm:ss dd.mm.yyyy-dw-dst >"))
MAKE_PSTR_LIST(tpl_datetime, F("Format: < NTP | dd.mm.yyyy-hh:mm:ss-dw-dst >"))
MAKE_PSTR_LIST(tpl_switchtime, F("Format: <nn> [ not_set | day hh:mm on|off ]"))
MAKE_PSTR_LIST(tpl_holidays, F("format: < dd.mm.yyyy-dd.mm.yyyy >"))
MAKE_PSTR_LIST(enum_ibaMainDisplay,
@@ -345,6 +351,7 @@ MAKE_PSTR_LIST(enum_ibaMainDisplay,
F_(date),
F_(smoke_temperature))
MAKE_PSTR_LIST(enum_ibaLanguage, F_(german), F_(dutch), F_(french), F_(italian))
MAKE_PSTR_LIST(enum_ibaLanguage_RC30, F_(german), F_(dutch))
MAKE_PSTR_LIST(enum_floordrystatus, F_(off), F_(start), F_(heat), F_(hold), F_(cool), F_(end))
MAKE_PSTR_LIST(enum_ibaBuildingType, F_(light), F_(medium), F_(heavy))
MAKE_PSTR_LIST(enum_PID, F("fast"), F_(medium), F("slow"))
@@ -370,6 +377,7 @@ MAKE_PSTR_LIST(enum_modetype4, F_(nofrost), F_(eco), F_(heat))
MAKE_PSTR_LIST(enum_modetype5, F_(off), F_(on))
MAKE_PSTR_LIST(enum_reducemode, F_(nofrost), F_(reduce), F_(room), F_(outdoor))
MAKE_PSTR_LIST(enum_nofrostmode, F_(off), F_(room), F_(outdoor))
MAKE_PSTR_LIST(enum_controlmode, F_(off), F_(optimized), F_(simple), F_(mpc), F_(room), F_(power), F_(constant))
MAKE_PSTR_LIST(enum_controlmode2, F_(outdoor), F_(room))
@@ -441,6 +449,7 @@ MAKE_PSTR_LIST(setBurnPow, F("setburnpow"), F("burner set power"))
MAKE_PSTR_LIST(curBurnPow, F("curburnpow"), F("burner current power"))
MAKE_PSTR_LIST(burnStarts, F("burnstarts"), F("burner starts"))
MAKE_PSTR_LIST(burnWorkMin, F("burnworkmin"), F("total burner operating time"))
MAKE_PSTR_LIST(burn2WorkMin, F("burn2workmin"), F("burner stage 2 operating time"))
MAKE_PSTR_LIST(heatWorkMin, F("heatworkmin"), F("total heat operating time"))
MAKE_PSTR_LIST(UBAuptime, F("ubauptime"), F("total UBA operating time"))
MAKE_PSTR_LIST(lastCode, F("lastcode"), F("last error code"))
@@ -452,7 +461,7 @@ MAKE_PSTR_LIST(maintenanceType, F_(maintenance), F("maintenance scheduled"))
MAKE_PSTR_LIST(maintenanceTime, F("maintenancetime"), F("time to next maintenance"))
// heatpump/compress specific
MAKE_PSTR_LIST(upTimeControl, F("uptimecontrol"), F("operating time total heat"))
MAKE_PSTR_LIST(upTimeControl, F("uptimecontrol"), F("total operating time heat"))
MAKE_PSTR_LIST(upTimeCompHeating, F("uptimecompheating"), F("operating time compressor heating"))
MAKE_PSTR_LIST(upTimeCompCooling, F("uptimecompcooling"), F("operating time compressor cooling"))
MAKE_PSTR_LIST(upTimeCompWw, F("uptimecompww"), F("operating time compressor dhw"))
@@ -462,7 +471,7 @@ MAKE_PSTR_LIST(heatingStarts, F("heatingstarts"), F("heating control starts"))
MAKE_PSTR_LIST(coolingStarts, F("coolingstarts"), F("cooling control starts"))
MAKE_PSTR_LIST(poolStarts, F("poolstarts"), F("pool control starts"))
MAKE_PSTR_LIST(nrgConsTotal, F("nrgconstotal"), F("total energy consumption"))
MAKE_PSTR_LIST(nrgConsCompTotal, F("nrgconscomptotal"), F("energy consumption compressor total"))
MAKE_PSTR_LIST(nrgConsCompTotal, F("nrgconscomptotal"), F("total energy consumption compressor"))
MAKE_PSTR_LIST(nrgConsCompHeating, F("nrgconscompheating"), F("energy consumption compressor heating"))
MAKE_PSTR_LIST(nrgConsCompWw, F("nrgconscompww"), F("energy consumption compressor dhw"))
MAKE_PSTR_LIST(nrgConsCompCooling, F("nrgconscompcooling"), F("energy consumption compressor cooling"))
@@ -472,7 +481,7 @@ MAKE_PSTR_LIST(nrgSuppHeating, F("nrgsuppheating"), F("total energy supplied hea
MAKE_PSTR_LIST(nrgSuppWw, F("nrgsuppww"), F("total energy warm supplied dhw"))
MAKE_PSTR_LIST(nrgSuppCooling, F("nrgsuppcooling"), F("total energy supplied cooling"))
MAKE_PSTR_LIST(nrgSuppPool, F("nrgsupppool"), F("total energy supplied pool"))
MAKE_PSTR_LIST(auxElecHeatNrgConsTotal, F("auxelecheatnrgconstotal"), F("auxiliary electrical heater energy consumption total"))
MAKE_PSTR_LIST(auxElecHeatNrgConsTotal, F("auxelecheatnrgconstotal"), F("total auxiliary electrical heater energy consumption"))
MAKE_PSTR_LIST(auxElecHeatNrgConsHeating, F("auxelecheatnrgconsheating"), F("auxiliary electrical heater energy consumption heating"))
MAKE_PSTR_LIST(auxElecHeatNrgConsWW, F("auxelecheatnrgconsww"), F("auxiliary electrical heater energy consumption dhw"))
MAKE_PSTR_LIST(auxElecHeatNrgConsPool, F("auxelecheatnrgconspool"), F("auxiliary electrical heater energy consumption pool"))
@@ -504,6 +513,16 @@ MAKE_PSTR_LIST(hpTl2, F("hptl2"), F("air inlet temperature (TL2)"))
MAKE_PSTR_LIST(hpPl1, F("hppl1"), F("low pressure side temperature (PL1)"))
MAKE_PSTR_LIST(hpPh1, F("hpph1"), F("high pressure side temperature (PH1)"))
// hybrid heatpump
MAKE_PSTR_LIST(enum_hybridStrategy, F("co2-optimized"), F("cost-optimized"), F("outside-temp-switched"), F("co2-cost-mix"))
MAKE_PSTR_LIST(hybridStrategy, F("hybridstrategy"), F("hybrid control strategy"))
MAKE_PSTR_LIST(switchOverTemp, F("switchovertemp"), F("outside switchover temperature"))
MAKE_PSTR_LIST(energyCostRatio, F("energycostratio"), F("energy cost ratio"))
MAKE_PSTR_LIST(fossileFactor, F("fossilefactor"), F("fossile energy factor"))
MAKE_PSTR_LIST(electricFactor, F("electricfactor"), F("electric energy factor"))
MAKE_PSTR_LIST(delayBoiler, F("delayboiler"), F("delay boiler support"))
MAKE_PSTR_LIST(tempDiffBoiler, F("tempdiffboiler"), F("tempediff boiler support"))
// the following are dhw for the boiler and automatically tagged with 'ww'
MAKE_PSTR_LIST(wwSelTemp, F("wwseltemp"), F("selected temperature"))
MAKE_PSTR_LIST(wwSelTempLow, F("wwseltemplow"), F("selected lower temperature"))
@@ -536,7 +555,7 @@ MAKE_PSTR_LIST(wwSetPumpPower, F("wwsetpumppower"), F("set pump power"))
MAKE_PSTR_LIST(wwMixerTemp, F("wwmixertemp"), F("mixer temperature"))
MAKE_PSTR_LIST(wwCylMiddleTemp, F("wwcylmiddletemp"), F("cylinder middle temperature (TS3)"))
MAKE_PSTR_LIST(wwStarts, F("wwstarts"), F("starts"))
MAKE_PSTR_LIST(wwStarts2, F("wwstarts2"), F("control starts"))
MAKE_PSTR_LIST(wwStarts2, F("wwstarts2"), F("control starts2"))
MAKE_PSTR_LIST(wwWorkM, F("wwworkm"), F("active time"))
MAKE_PSTR_LIST(wwHystOn, F("wwhyston"), F("hysteresis on temperature"))
MAKE_PSTR_LIST(wwHystOff, F("wwhystoff"), F("hysteresis off temperature"))
@@ -567,6 +586,11 @@ MAKE_PSTR_LIST(tempsensor2, F("inttemp2"), F("temperature sensor 2"))
MAKE_PSTR_LIST(dampedoutdoortemp, F("dampedoutdoortemp"), F("damped outdoor temperature"))
MAKE_PSTR_LIST(floordrystatus, F("floordry"), F("floor drying"))
MAKE_PSTR_LIST(floordrytemp, F("floordrytemp"), F("floor drying temperature"))
MAKE_PSTR_LIST(brightness, F("brightness"), F("screen brightness"))
MAKE_PSTR_LIST(autodst, F("autodst"), F("automatic change daylight saving time"))
MAKE_PSTR_LIST(preheating, F("preheating"), F("preheating in the clock program"))
MAKE_PSTR_LIST(offtemp, F("offtemp"), F("temperature when mode is off"))
MAKE_PSTR_LIST(mixingvalves, F("mixingvalves"), F("mixing valves"))
// thermostat ww
MAKE_PSTR_LIST(wwMode, F("wwmode"), F("mode"))
MAKE_PSTR_LIST(wwSetTempLow, F("wwsettemplow"), F("set low temperature"))
@@ -623,6 +647,10 @@ MAKE_PSTR_LIST(holidaymode, F("holidaymode"), F("holiday mode"))
MAKE_PSTR_LIST(flowtempoffset, F("flowtempoffset"), F("flow temperature offset for mixer"))
MAKE_PSTR_LIST(reducemode, F("reducemode"), F("reduce mode"))
MAKE_PSTR_LIST(noreducetemp, F("noreducetemp"), F("no reduce below temperature"))
MAKE_PSTR_LIST(reducetemp, F("reducetemp"), F("off/reduce switch temperature"))
MAKE_PSTR_LIST(vacreducetemp, F("vacreducetemp"), F("vacations off/reduce switch temperature"))
MAKE_PSTR_LIST(vacreducemode, F("vacreducemode"), F("vacations reduce mode"))
MAKE_PSTR_LIST(nofrostmode, F("nofrostmode"), F("nofrost mode"))
MAKE_PSTR_LIST(remotetemp, F("remotetemp"), F("room temperature from remote"))
MAKE_PSTR_LIST(reducehours, F("reducehours"), F("duration for nighttemp"))
MAKE_PSTR_LIST(reduceminutes, F("reduceminutes"), F("remaining time for nightmode"))
@@ -647,6 +675,7 @@ MAKE_PSTR_LIST(poolSetTemp, F("poolsettemp"), F("pool set temperature"))
MAKE_PSTR_LIST(poolTemp, F("pooltemp"), F("pool temperature"))
MAKE_PSTR_LIST(poolShuntStatus, F("poolshuntstatus"), F("pool shunt status opening/closing"))
MAKE_PSTR_LIST(poolShunt, F("poolshunt"), F("pool shunt open/close (0% = pool / 100% = heat)"))
MAKE_PSTR_LIST(hydrTemp, F("hydrTemp"), F("hydraulic header temperature"))
// solar
MAKE_PSTR_LIST(collectorTemp, F("collectortemp"), F("collector temperature (TS1)"))
@@ -675,8 +704,8 @@ MAKE_PSTR_LIST(pumpWorkTime, F("pumpworktime"), F("pump working time"))
MAKE_PSTR_LIST(pump2WorkTime, F("pump2worktime"), F("pump 2 working time"))
MAKE_PSTR_LIST(m1WorkTime, F("m1worktime"), F("differential control working time"))
MAKE_PSTR_LIST(energyLastHour, F("energylasthour"), F("energy last hour"))
MAKE_PSTR_LIST(energyTotal, F("energytotal"), F("energy total"))
MAKE_PSTR_LIST(energyToday, F("energytoday"), F("energy today"))
MAKE_PSTR_LIST(energyTotal, F("energytotal"), F("total energy"))
MAKE_PSTR_LIST(energyToday, F("energytoday"), F("total energy today"))
MAKE_PSTR_LIST(pumpMinMod, F("pumpminmod"), F("minimum pump modulation"))
MAKE_PSTR_LIST(maxFlow, F("maxflow"), F("maximum solar flow"))
MAKE_PSTR_LIST(solarPower, F("solarpower"), F("actual solar power"))

View File

@@ -386,6 +386,10 @@ void Mqtt::on_publish(uint16_t packetId) const {
// called when MQTT settings have changed via the Web forms
void Mqtt::reset_mqtt() {
if (!mqtt_enabled_) {
mqtt_messages_.clear();
}
if (!mqttClient_) {
return;
}
@@ -648,6 +652,7 @@ void Mqtt::ha_status() {
}
publish_system_ha_sensor_config(DeviceValueType::INT, F("Uptime"), F("uptime"), DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::INT, F("Uptime (sec)"), F("uptime_sec"), DeviceValueUOM::SECONDS);
publish_system_ha_sensor_config(DeviceValueType::BOOL, F("NTP status"), F("ntp_status"), DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::INT, F("Free memory"), F("freemem"), DeviceValueUOM::KB);
publish_system_ha_sensor_config(DeviceValueType::INT, F("MQTT fails"), F("mqttfails"), DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::INT, F("Rx received"), F("rxreceived"), DeviceValueUOM::NONE);
@@ -918,6 +923,10 @@ void Mqtt::publish_ha_sensor_config(DeviceValue & dv, const std::string & model,
int16_t dv_set_min, dv_set_max;
(void)dv.get_min_max(dv_set_min, dv_set_max);
// determine if we're creating the command topics which we use special HA configs
// unless the entity has been marked as read-only and so it'll default to using the sensor/ type
bool has_cmd = dv.has_cmd && !dv.has_state(DeviceValueState::DV_READONLY);
publish_ha_sensor_config(dv.type,
dv.tag,
dv.full_name,
@@ -925,7 +934,7 @@ void Mqtt::publish_ha_sensor_config(DeviceValue & dv, const std::string & model,
dv.short_name,
dv.uom,
remove,
dv.has_cmd,
has_cmd,
dv.options,
dv.options_size,
dv_set_min,
@@ -988,7 +997,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type,
// create the topic, depending on the type and whether the device entity is writable (a command)
// https://developers.home-assistant.io/docs/core/entity
char topic[MQTT_TOPIC_MAX_SIZE];
// if it's a command then we can use Number, Switch. Otherwise stick to Sensor
// if it's a command then we can use Number, Switch, Select. Otherwise stick to Sensor
if (has_cmd) {
switch (type) {
case DeviceValueType::INT:
@@ -996,13 +1005,13 @@ void Mqtt::publish_ha_sensor_config(uint8_t type,
case DeviceValueType::SHORT:
case DeviceValueType::USHORT:
case DeviceValueType::ULONG:
// number - https://www.home-assistant.io/integrations/number.mqtt/
// number - https://www.home-assistant.io/integrations/number.mqtt
// https://developers.home-assistant.io/docs/core/entity/number
snprintf(topic, sizeof(topic), "number/%s/%s/config", mqtt_base_.c_str(), uniq);
break;
case DeviceValueType::BOOL:
// switch - https://www.home-assistant.io/integrations/switch.mqtt/
// switch - https://www.home-assistant.io/integrations/switch.mqtt
snprintf(topic, sizeof(topic), "switch/%s/%s/config", mqtt_base_.c_str(), uniq);
break;
case DeviceValueType::ENUM:

View File

@@ -543,6 +543,18 @@ bool System::heartbeat_json(JsonObject & output) {
output["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
output["uptime_sec"] = uuid::get_uptime_sec();
bool value_b = EMSESP::system_.ntp_connected();
if (Mqtt::ha_enabled()) {
char s[7];
output["ntp_status"] = Helpers::render_boolean(s, value_b); // for HA always render as string
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
output["ntp_status"] = value_b;
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
output["ntp_status"] = value_b ? 1 : 0;
} else {
char s[7];
output["ntp_status"] = Helpers::render_boolean(s, value_b);
}
output["rxreceived"] = EMSESP::rxservice_.telegram_count();
output["rxfails"] = EMSESP::rxservice_.telegram_error_count();
output["txreads"] = EMSESP::txservice_.telegram_read_count();
@@ -949,6 +961,7 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject &
node["publish_time_other"] = settings.publish_time_other;
node["publish_time_sensor"] = settings.publish_time_sensor;
node["publish_single"] = settings.publish_single;
node["publish_2_command"] = settings.publish_single2cmd;
node["send_response"] = settings.send_response;
});
@@ -1016,7 +1029,6 @@ bool System::command_customizations(const char * value, const int8_t id, JsonObj
JsonObject node = output.createNestedObject("Customizations");
// hide ssid from this list
EMSESP::webCustomizationService.read([&](WebCustomization & settings) {
// sensors
JsonArray sensorsJson = node.createNestedArray("sensors");
@@ -1051,16 +1063,16 @@ bool System::command_customizations(const char * value, const int8_t id, JsonObj
}
}
// exclude entities
JsonArray exclude_entitiesJson = node.createNestedArray("exclude_entities");
// masked entities
JsonArray mask_entitiesJson = node.createNestedArray("masked_entities");
for (const auto & entityCustomization : settings.entityCustomizations) {
JsonObject entityJson = exclude_entitiesJson.createNestedObject();
JsonObject entityJson = mask_entitiesJson.createNestedObject();
entityJson["product_id"] = entityCustomization.product_id;
entityJson["device_id"] = entityCustomization.device_id;
JsonArray exclude_entityJson = entityJson.createNestedArray("entity_ids");
for (uint8_t entity_id : entityCustomization.entity_ids) {
exclude_entityJson.add(entity_id);
JsonArray mask_entityJson = entityJson.createNestedArray("entities");
for (std::string entity_id : entityCustomization.entity_ids) {
mask_entityJson.add(entity_id);
}
}
});
@@ -1079,6 +1091,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
node["version"] = EMSESP_APP_VERSION;
node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
node["uptime (seconds)"] = uuid::get_uptime_sec();
node["network time"] = EMSESP::system_.ntp_connected() ? "connected" : "disconnected";
#ifndef EMSESP_STANDALONE
node["freemem"] = ESP.getFreeHeap() / 1000L; // kilobytes
@@ -1191,7 +1204,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
obj["product id"] = emsdevice->product_id();
obj["version"] = emsdevice->version();
obj["entities"] = emsdevice->count_entities();
char result[250];
char result[300];
(void)emsdevice->show_telegram_handlers(result, sizeof(result), EMSdevice::Handlers::RECEIVED);
if (result[0] != '\0') {
obj["handlers received"] = result; // don't show handlers if there aren't any
@@ -1204,6 +1217,10 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
if (result[0] != '\0') {
obj["handlers pending"] = result;
}
(void)emsdevice->show_telegram_handlers(result, sizeof(result), EMSdevice::Handlers::IGNORED);
if (result[0] != '\0') {
obj["handlers ignored"] = result;
}
}
}
}
@@ -1321,4 +1338,13 @@ std::string System::reset_reason(uint8_t cpu) const {
return ("Unkonwn");
}
// set NTP status
void System::ntp_connected(bool b) {
if (b != ntp_connected_) {
LOG_INFO(b ? F("NTP connected") : F("NTP disconnected"));
}
ntp_connected_ = b;
ntp_last_check_ = b ? uuid::get_uptime_sec() : 0;
}
} // namespace emsesp

View File

@@ -154,6 +154,16 @@ class System {
ethernet_connected_ = b;
}
void ntp_connected(bool b);
bool ntp_connected() {
// timeout 2 hours, ntp sync is normally every hour.
if ((uuid::get_uptime_sec() - ntp_last_check_ > 7201) && ntp_connected_) {
ntp_connected(false);
}
return ntp_connected_;
}
bool network_connected() {
#ifndef EMSESP_STANDALONE
return (ethernet_connected() || WiFi.isConnected());
@@ -218,6 +228,9 @@ class System {
bool upload_status_ = false; // true if we're in the middle of a OTA firmware upload
bool ethernet_connected_ = false;
bool ntp_connected_ = false;
uint32_t ntp_last_check_ = 0;
// EMS-ESP settings
// copies from WebSettings class in WebSettingsService.h and loaded with reload_settings()
std::string hostname_ = FACTORY_WIFI_HOSTNAME;

View File

@@ -595,8 +595,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
EMSESP::system_.healthcheck(n);
}
if (command == "exclude") {
shell.printfln(F("Testing exclude entities"));
if (command == "masked") {
shell.printfln(F("Testing masked entities"));
Mqtt::ha_enabled(true);
Mqtt::send_response(false);
@@ -609,8 +609,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
// toggle mode
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice->unique_id() == 1) { // boiler
uint8_t entity_id = 47; // wwseltemp
emsdevice->exclude_entity(entity_id);
std::string a = "07wwseltemp";
emsdevice->mask_entity(a);
break;
}
}

View File

@@ -31,12 +31,12 @@ namespace emsesp {
// #define EMSESP_DEBUG_DEFAULT "mixer"
// #define EMSESP_DEBUG_DEFAULT "web"
// #define EMSESP_DEBUG_DEFAULT "mqtt"
// #define EMSESP_DEBUG_DEFAULT "general"
#define EMSESP_DEBUG_DEFAULT "general"
// #define EMSESP_DEBUG_DEFAULT "boiler"
// #define EMSESP_DEBUG_DEFAULT "mqtt2"
// #define EMSESP_DEBUG_DEFAULT "mqtt_nested"
#define EMSESP_DEBUG_DEFAULT "ha"
// #define EMSESP_DEBUG_DEFAULT "exclude"
// #define EMSESP_DEBUG_DEFAULT "ha"
// #define EMSESP_DEBUG_DEFAULT "masked"
// #define EMSESP_DEBUG_DEFAULT "board_profile"
// #define EMSESP_DEBUG_DEFAULT "shower_alert"
// #define EMSESP_DEBUG_DEFAULT "310"

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.4.0b8"
#define EMSESP_APP_VERSION "3.4.0b12"

View File

@@ -31,9 +31,9 @@ WebCustomizationService::WebCustomizationService(AsyncWebServer * server, FS * f
securityManager,
AuthenticationPredicates::IS_AUTHENTICATED)
, _fsPersistence(WebCustomization::read, WebCustomization::update, this, fs, EMSESP_CUSTOMIZATION_FILE)
, _exclude_entities_handler(EXCLUDE_ENTITIES_PATH,
securityManager->wrapCallback(std::bind(&WebCustomizationService::exclude_entities, this, _1, _2),
AuthenticationPredicates::IS_AUTHENTICATED))
, _masked_entities_handler(MASKED_ENTITIES_PATH,
securityManager->wrapCallback(std::bind(&WebCustomizationService::masked_entities, this, _1, _2),
AuthenticationPredicates::IS_AUTHENTICATED))
, _device_entities_handler(DEVICE_ENTITIES_PATH,
securityManager->wrapCallback(std::bind(&WebCustomizationService::device_entities, this, _1, _2),
AuthenticationPredicates::IS_AUTHENTICATED)) {
@@ -45,16 +45,17 @@ WebCustomizationService::WebCustomizationService(AsyncWebServer * server, FS * f
HTTP_POST,
securityManager->wrapRequest(std::bind(&WebCustomizationService::reset_customization, this, _1), AuthenticationPredicates::IS_ADMIN));
_exclude_entities_handler.setMethod(HTTP_POST);
_exclude_entities_handler.setMaxContentLength(1024);
server->addHandler(&_exclude_entities_handler);
_masked_entities_handler.setMethod(HTTP_POST);
_masked_entities_handler.setMaxContentLength(4096);
_masked_entities_handler.setMaxJsonBufferSize(4096);
server->addHandler(&_masked_entities_handler);
_device_entities_handler.setMethod(HTTP_POST);
_device_entities_handler.setMaxContentLength(256);
server->addHandler(&_device_entities_handler);
}
// this creates the customization file, saving to the FS
// this creates the customization file, saving it to the FS
void WebCustomization::read(WebCustomization & settings, JsonObject & root) {
// Dallas Sensor customization
JsonArray sensorsJson = root.createNestedArray("sensors");
@@ -77,21 +78,21 @@ void WebCustomization::read(WebCustomization & settings, JsonObject & root) {
sensorJson["type"] = sensor.type; // t
}
// Exclude entities customization
JsonArray exclude_entitiesJson = root.createNestedArray("exclude_entities");
// Masked entities customization
JsonArray masked_entitiesJson = root.createNestedArray("masked_entities");
for (const EntityCustomization & entityCustomization : settings.entityCustomizations) {
JsonObject entityJson = exclude_entitiesJson.createNestedObject();
JsonObject entityJson = masked_entitiesJson.createNestedObject();
entityJson["product_id"] = entityCustomization.product_id;
entityJson["device_id"] = entityCustomization.device_id;
JsonArray exclude_entityJson = entityJson.createNestedArray("entity_ids");
for (uint8_t entity_id : entityCustomization.entity_ids) {
exclude_entityJson.add(entity_id);
JsonArray masked_entityJson = entityJson.createNestedArray("entity_ids");
for (std::string entity_id : entityCustomization.entity_ids) {
masked_entityJson.add(entity_id);
}
}
}
// call on initialization and also when the page is saved via web
// call on initialization and also when the page is saved via web UI
// this loads the data into the internal class
StateUpdateResult WebCustomization::update(JsonObject & root, WebCustomization & settings) {
// Dallas Sensor customization
@@ -123,17 +124,20 @@ StateUpdateResult WebCustomization::update(JsonObject & root, WebCustomization &
}
}
// load array of entities id's to exclude, building up the object class
// load array of entities id's with masks, building up the object class
settings.entityCustomizations.clear();
if (root["exclude_entities"].is<JsonArray>()) {
for (const JsonObject exclude_entities : root["exclude_entities"].as<JsonArray>()) {
if (root["masked_entities"].is<JsonArray>()) {
for (const JsonObject masked_entities : root["masked_entities"].as<JsonArray>()) {
auto new_entry = EntityCustomization();
new_entry.product_id = exclude_entities["product_id"];
new_entry.device_id = exclude_entities["device_id"];
new_entry.product_id = masked_entities["product_id"];
new_entry.device_id = masked_entities["device_id"];
for (const JsonVariant exclude_entity_id : exclude_entities["entity_ids"].as<JsonArray>()) {
new_entry.entity_ids.push_back(exclude_entity_id.as<uint8_t>()); // add entity list
for (const JsonVariant masked_entity_id : masked_entities["entity_ids"].as<JsonArray>()) {
if (masked_entity_id.is<std::string>()) {
new_entry.entity_ids.push_back(masked_entity_id.as<std::string>()); // add entity list
}
}
settings.entityCustomizations.push_back(new_entry); // save the new object
}
}
@@ -156,7 +160,7 @@ void WebCustomizationService::reset_customization(AsyncWebServerRequest * reques
#endif
}
// send back a short list devices used in the customization page
// send back a list of devices used to the customization web page
void WebCustomizationService::devices(AsyncWebServerRequest * request) {
auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_LARGE_DYN);
JsonObject root = response->getRoot();
@@ -167,14 +171,16 @@ void WebCustomizationService::devices(AsyncWebServerRequest * request) {
JsonObject obj = devices.createNestedObject();
obj["i"] = emsdevice->unique_id(); // a unique id
// shortname - we prefix the count to make it unique
/*
uint8_t device_index = EMSESP::device_index(emsdevice->device_type(), emsdevice->unique_id());
if (device_index) {
char s[10];
obj["s"] = emsdevice->device_type_name() + Helpers::smallitoa(s, device_index);
obj["s"] = emsdevice->device_type_name() + Helpers::smallitoa(s, device_index) + " (" + emsdevice->name() + ")"; // shortname - we prefix the count to make it unique
} else {
obj["s"] = emsdevice->device_type_name();
obj["s"] = emsdevice->device_type_name() + " (" + emsdevice->name() + ")";
}
*/
obj["s"] = emsdevice->device_type_name() + " (" + emsdevice->name() + ")";
}
}
@@ -182,10 +188,10 @@ void WebCustomizationService::devices(AsyncWebServerRequest * request) {
request->send(response);
}
// send back list device entities
// send back list of device entities
void WebCustomizationService::device_entities(AsyncWebServerRequest * request, JsonVariant & json) {
if (json.is<JsonObject>()) {
auto * response = new MsgpackAsyncJsonResponse(true, EMSESP_JSON_SIZE_XXLARGE_DYN);
auto * response = new MsgpackAsyncJsonResponse(true, EMSESP_JSON_SIZE_XXXLARGE_DYN);
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice->unique_id() == json["id"]) {
#ifndef EMSESP_STANDALONE
@@ -204,52 +210,53 @@ void WebCustomizationService::device_entities(AsyncWebServerRequest * request, J
request->send(response);
}
// takes a list of excluded ids send from the webUI
// takes a list of updated entities with new masks from the web UI
// saves it in the customization service
// and updates the entity list real-time
void WebCustomizationService::exclude_entities(AsyncWebServerRequest * request, JsonVariant & json) {
void WebCustomizationService::masked_entities(AsyncWebServerRequest * request, JsonVariant & json) {
if (json.is<JsonObject>()) {
// find the device using the unique_id
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice) {
uint8_t unique_device_id = json["id"];
if (emsdevice->unique_id() == unique_device_id) {
// first reset all the entity ids
emsdevice->reset_exclude_entities();
// build a list of entities to exclude and then set the flag to non-visible
JsonArray entity_ids_json = json["entity_ids"];
std::vector<uint8_t> entity_ids;
for (JsonVariant id : entity_ids_json) {
uint8_t entity_id = id.as<int>();
emsdevice->exclude_entity(entity_id); // this will have immediate affect
entity_ids.push_back(entity_id);
}
// Save the list to the customization file
uint8_t product_id = emsdevice->product_id();
uint8_t device_id = emsdevice->device_id();
// and set the mask immediately for the changed entities
JsonArray entity_ids_json = json["entity_ids"];
for (const JsonVariant id : entity_ids_json) {
emsdevice->mask_entity(id.as<std::string>());
}
// Save the list to the customization file
EMSESP::webCustomizationService.update(
[&](WebCustomization & settings) {
// if it exists (productid and deviceid match) overwrite it
for (auto & entityCustomization : settings.entityCustomizations) {
if ((entityCustomization.product_id == product_id) && (entityCustomization.device_id == device_id)) {
// already exists, clear the list and add the new values
entityCustomization.entity_ids.clear();
for (uint8_t i = 0; i < entity_ids.size(); i++) {
entityCustomization.entity_ids.push_back(entity_ids[i]);
}
return StateUpdateResult::CHANGED;
// see if we already have a mask list for this device, if so remove it
for (auto it = settings.entityCustomizations.begin(); it != settings.entityCustomizations.end();) {
if ((*it).product_id == product_id && (*it).device_id == device_id) {
it = settings.entityCustomizations.erase(it);
break;
} else {
++it;
}
}
// create a new entry in the list
if (!entity_ids_json.size()) {
return StateUpdateResult::UNCHANGED; // nothing to add
}
// create a new entry for this device if there are values
EntityCustomization new_entry;
new_entry.product_id = product_id;
new_entry.device_id = device_id;
for (uint8_t i = 0; i < entity_ids.size(); i++) {
new_entry.entity_ids.push_back(entity_ids[i]);
}
// get list of entities that have masks
std::vector<std::string> entity_ids;
emsdevice->getMaskedEntities(entity_ids);
new_entry.entity_ids = entity_ids;
// add the record and save
settings.entityCustomizations.push_back(new_entry);
return StateUpdateResult::CHANGED;
},

View File

@@ -27,7 +27,7 @@
// POST
#define DEVICE_ENTITIES_PATH "/rest/deviceEntities"
#define EXCLUDE_ENTITIES_PATH "/rest/excludeEntities"
#define MASKED_ENTITIES_PATH "/rest/maskedEntities"
#define RESET_CUSTOMIZATION_SERVICE_PATH "/rest/resetCustomizations"
namespace emsesp {
@@ -61,19 +61,18 @@ class AnalogCustomization {
// we use product_id and device_id to make the device unique
class EntityCustomization {
public:
uint8_t product_id; // device's product id
uint8_t device_id; // device's device id
std::vector<uint8_t> entity_ids; // array of entity ids to exclude
uint8_t product_id; // device's product id
uint8_t device_id; // device's device id
std::vector<std::string> entity_ids; // array of entity ids with masks
};
class WebCustomization {
public:
std::list<SensorCustomization> sensorCustomizations; // for sensor names and offsets
std::list<AnalogCustomization> analogCustomizations; // for analog sensors
std::list<EntityCustomization> entityCustomizations; // for a list of entities that should be excluded from the device list
static void read(WebCustomization & settings, JsonObject & root);
static StateUpdateResult update(JsonObject & root, WebCustomization & settings);
std::list<EntityCustomization> entityCustomizations; // for a list of entities that have a special mask set
static void read(WebCustomization & settings, JsonObject & root);
static StateUpdateResult update(JsonObject & root, WebCustomization & settings);
};
class WebCustomizationService : public StatefulService<WebCustomization> {
@@ -94,11 +93,11 @@ class WebCustomizationService : public StatefulService<WebCustomization> {
void devices(AsyncWebServerRequest * request);
// POST
void exclude_entities(AsyncWebServerRequest * request, JsonVariant & json);
void masked_entities(AsyncWebServerRequest * request, JsonVariant & json);
void device_entities(AsyncWebServerRequest * request, JsonVariant & json);
void reset_customization(AsyncWebServerRequest * request);
AsyncCallbackJsonWebHandler _exclude_entities_handler, _device_entities_handler;
AsyncCallbackJsonWebHandler _masked_entities_handler, _device_entities_handler;
};
} // namespace emsesp

View File

@@ -77,7 +77,7 @@ void WebDataService::core_data(AsyncWebServerRequest * request) {
// Ignore Contoller
JsonArray devices = root.createNestedArray("devices");
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice && emsdevice->device_type() != EMSdevice::DeviceType::CONTROLLER) {
if (emsdevice && (emsdevice->device_type() != EMSdevice::DeviceType::CONTROLLER || emsdevice->count_entities() > 0)) {
JsonObject obj = devices.createNestedObject();
obj["i"] = emsdevice->unique_id(); // a unique id
obj["t"] = emsdevice->device_type_name(); // type