added all devices - API: HTTP read/write #506

This commit is contained in:
proddy
2020-09-20 15:52:44 +02:00
parent 22fcff6682
commit 9fc15650a2
35 changed files with 742 additions and 425 deletions

View File

@@ -69,6 +69,12 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_mqtt_cmd(F("burnperiod"), [&](const char * value, const int8_t id) { return set_burn_period(value, id); });
register_mqtt_cmd(F("pumpdelay"), [&](const char * value, const int8_t id) { return set_pump_delay(value, id); });
// API call
// Command::add_with_json(this->device_type(), F("info"), Boiler::command_info);
Command::add_with_json(this->device_type(), F("info"), [&](const char * value, const int8_t id, JsonObject & object) {
return command_info(value, id, object);
});
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
mqtt_format_ = settings.mqtt_format; // single, nested or ha
@@ -140,208 +146,217 @@ void Boiler::device_info_web(JsonArray & root) {
render_value_json(root, "", F("Heat Pump modulation"), pumpMod2_, F_(percent));
}
bool Boiler::command_info(const char * value, const int8_t id, JsonObject & output) {
return (export_values(output));
}
// creates JSON doc from values
// returns false if empty
bool Boiler::export_values(JsonObject & output) {
char s[10]; // for formatting strings
if (Helpers::hasValue(wWComfort_)) {
if (wWComfort_ == 0x00) {
output["wWComfort"] = "Hot";
} else if (wWComfort_ == 0xD8) {
output["wWComfort"] = "Eco";
} else if (wWComfort_ == 0xEC) {
output["wWComfort"] = "Intelligent";
}
}
if (Helpers::hasValue(wWSelTemp_)) {
output["wWSelTemp"] = wWSelTemp_;
}
if (Helpers::hasValue(wWSetTmp_)) {
output["wWSetTemp"] = wWSetTmp_;
}
if (Helpers::hasValue(wWDisinfectTemp_)) {
output["wWDisinfectionTemp"] = wWDisinfectTemp_;
}
if (Helpers::hasValue(selFlowTemp_)) {
output["selFlowTemp"] = selFlowTemp_;
}
if (Helpers::hasValue(selBurnPow_)) {
output["selBurnPow"] = selBurnPow_;
}
if (Helpers::hasValue(curBurnPow_)) {
output["curBurnPow"] = curBurnPow_;
}
if (Helpers::hasValue(pumpMod_)) {
output["pumpMod"] = pumpMod_;
}
if (Helpers::hasValue(pumpMod2_)) {
output["pumpMod2"] = pumpMod2_;
}
if (wWType_ == 0) { // no output if not set
output["wWType"] = F("off");
} else if (wWType_ == 1) {
output["wWType"] = F("flow");
} else if (wWType_ == 2) {
output["wWType"] = F("buffered flow");
} else if (wWType_ == 3) {
output["wWType"] = F("buffer");
} else if (wWType_ == 4) {
output["wWType"] = F("layered buffer");
}
if (Helpers::hasValue(wWChargeType_, EMS_VALUE_BOOL)) {
output["wWChargeType"] = wWChargeType_ ? "valve" : "pump";
}
if (Helpers::hasValue(wWCircPump_, EMS_VALUE_BOOL)) {
output["wWCircPump"] = Helpers::render_value(s, wWCircPump_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWCircPumpMode_)) {
output["wWCiPuMode"] = wWCircPumpMode_;
}
if (Helpers::hasValue(wWCirc_, EMS_VALUE_BOOL)) {
output["wWCirc"] = Helpers::render_value(s, wWCirc_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(extTemp_)) {
output["outdoorTemp"] = (float)extTemp_ / 10;
}
if (Helpers::hasValue(wWCurTmp_)) {
output["wWCurTmp"] = (float)wWCurTmp_ / 10;
}
if (Helpers::hasValue(wWCurTmp2_)) {
output["wWCurTmp2"] = (float)wWCurTmp2_ / 10;
}
if (Helpers::hasValue(wWCurFlow_)) {
output["wWCurFlow"] = (float)wWCurFlow_ / 10;
}
if (Helpers::hasValue(curFlowTemp_)) {
output["curFlowTemp"] = (float)curFlowTemp_ / 10;
}
if (Helpers::hasValue(retTemp_)) {
output["retTemp"] = (float)retTemp_ / 10;
}
if (Helpers::hasValue(switchTemp_)) {
output["switchTemp"] = (float)switchTemp_ / 10;
}
if (Helpers::hasValue(sysPress_)) {
output["sysPress"] = (float)sysPress_ / 10;
}
if (Helpers::hasValue(boilTemp_)) {
output["boilTemp"] = (float)boilTemp_ / 10;
}
if (Helpers::hasValue(wwStorageTemp1_)) {
output["wwStorageTemp1"] = (float)wwStorageTemp1_ / 10;
}
if (Helpers::hasValue(wwStorageTemp2_)) {
output["wwStorageTemp2"] = (float)wwStorageTemp2_ / 10;
}
if (Helpers::hasValue(exhaustTemp_)) {
output["exhaustTemp"] = (float)exhaustTemp_ / 10;
}
if (Helpers::hasValue(wWActivated_, EMS_VALUE_BOOL)) {
output["wWActivated"] = Helpers::render_value(s, wWActivated_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWOneTime_, EMS_VALUE_BOOL)) {
output["wWOnetime"] = Helpers::render_value(s, wWOneTime_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWDisinfecting_, EMS_VALUE_BOOL)) {
output["wWDisinfecting"] = Helpers::render_value(s, wWDisinfecting_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWReadiness_, EMS_VALUE_BOOL)) {
output["wWReady"] = Helpers::render_value(s, wWReadiness_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWRecharging_, EMS_VALUE_BOOL)) {
output["wWRecharge"] = Helpers::render_value(s, wWRecharging_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWTemperatureOK_, EMS_VALUE_BOOL)) {
output["wWTempOK"] = Helpers::render_value(s, wWTemperatureOK_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWCirc_, EMS_VALUE_BOOL)) {
output["wWCirc"] = Helpers::render_value(s, wWCirc_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(burnGas_, EMS_VALUE_BOOL)) {
output["burnGas"] = Helpers::render_value(s, burnGas_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(flameCurr_)) {
output["flameCurr"] = (float)(int16_t)flameCurr_ / 10;
}
if (Helpers::hasValue(heatPmp_, EMS_VALUE_BOOL)) {
output["heatPump"] = Helpers::render_value(s, heatPmp_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(fanWork_, EMS_VALUE_BOOL)) {
output["fanWork"] = Helpers::render_value(s, fanWork_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(ignWork_, EMS_VALUE_BOOL)) {
output["ignWork"] = Helpers::render_value(s, ignWork_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWHeat_, EMS_VALUE_BOOL)) {
output["wWHeat"] = Helpers::render_value(s, wWHeat_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(heating_activated_, EMS_VALUE_BOOL)) {
output["heatingActivated"] = Helpers::render_value(s, heating_activated_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(heating_temp_)) {
output["heatingTemp"] = heating_temp_;
}
if (Helpers::hasValue(pump_mod_max_)) {
output["pumpModMax"] = pump_mod_max_;
}
if (Helpers::hasValue(pump_mod_min_)) {
output["pumpModMin"] = pump_mod_min_;
}
if (Helpers::hasValue(pumpDelay_)) {
output["pumpDelay"] = pumpDelay_;
}
if (Helpers::hasValue(burnPeriod_)) {
output["burnMinPeriod"] = burnPeriod_;
}
if (Helpers::hasValue(burnPowermin_)) {
output["burnMinPower"] = burnPowermin_;
}
if (Helpers::hasValue(burnPowermax_)) {
output["burnMaxPower"] = burnPowermax_;
}
if (Helpers::hasValue(boilTemp_on_)) {
output["boilHystOn"] = boilTemp_on_;
}
if (Helpers::hasValue(boilTemp_off_)) {
output["boilHystOff"] = boilTemp_off_;
}
if (Helpers::hasValue(setFlowTemp_)) {
output["setFlowTemp"] = setFlowTemp_;
}
if (Helpers::hasValue(setWWPumpPow_)) {
output["wWSetPumpPower"] = setWWPumpPow_;
}
if (Helpers::hasValue(wWStarts_)) {
output["wWStarts"] = wWStarts_;
}
if (Helpers::hasValue(wWWorkM_)) {
output["wWWorkM"] = wWWorkM_;
}
if (Helpers::hasValue(UBAuptime_)) {
output["UBAuptime"] = UBAuptime_;
}
if (Helpers::hasValue(burnStarts_)) {
output["burnStarts"] = burnStarts_;
}
if (Helpers::hasValue(burnWorkMin_)) {
output["burnWorkMin"] = burnWorkMin_;
}
if (Helpers::hasValue(heatWorkMin_)) {
output["heatWorkMin"] = heatWorkMin_;
}
if (Helpers::hasValue(serviceCode_)) {
output["serviceCode"] = serviceCodeChar_;
output["serviceCodeNumber"] = serviceCode_;
}
return (output.size());
}
// publish values via MQTT
void Boiler::publish_values() {
// const size_t capacity = JSON_OBJECT_SIZE(56); // must recalculate if more objects addded https://arduinojson.org/v6/assistant/
// DynamicJsonDocument doc(capacity);
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc;
char s[10]; // for formatting strings
if (Helpers::hasValue(wWComfort_)) {
if (wWComfort_ == 0x00) {
doc["wWComfort"] = "Hot";
} else if (wWComfort_ == 0xD8) {
doc["wWComfort"] = "Eco";
} else if (wWComfort_ == 0xEC) {
doc["wWComfort"] = "Intelligent";
}
}
if (Helpers::hasValue(wWSelTemp_)) {
doc["wWSelTemp"] = wWSelTemp_;
}
if (Helpers::hasValue(wWSetTmp_)) {
doc["wWSetTemp"] = wWSetTmp_;
}
if (Helpers::hasValue(wWDisinfectTemp_)) {
doc["wWDisinfectionTemp"] = wWDisinfectTemp_;
}
if (Helpers::hasValue(selFlowTemp_)) {
doc["selFlowTemp"] = selFlowTemp_;
}
if (Helpers::hasValue(selBurnPow_)) {
doc["selBurnPow"] = selBurnPow_;
}
if (Helpers::hasValue(curBurnPow_)) {
doc["curBurnPow"] = curBurnPow_;
}
if (Helpers::hasValue(pumpMod_)) {
doc["pumpMod"] = pumpMod_;
}
if (Helpers::hasValue(pumpMod2_)) {
doc["pumpMod2"] = pumpMod2_;
}
if (wWType_ == 0) { // no output if not set
doc["wWType"] = F("off");
} else if (wWType_ == 1) {
doc["wWType"] = F("flow");
} else if (wWType_ == 2) {
doc["wWType"] = F("buffered flow");
} else if (wWType_ == 3) {
doc["wWType"] = F("buffer");
} else if (wWType_ == 4) {
doc["wWType"] = F("layered buffer");
}
if (Helpers::hasValue(wWChargeType_, EMS_VALUE_BOOL)) {
doc["wWChargeType"] = wWChargeType_ ? "valve" : "pump";
}
if (Helpers::hasValue(wWCircPump_, EMS_VALUE_BOOL)) {
doc["wWCircPump"] = Helpers::render_value(s, wWCircPump_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWCircPumpMode_)) {
doc["wWCiPuMode"] = wWCircPumpMode_;
}
if (Helpers::hasValue(wWCirc_, EMS_VALUE_BOOL)) {
doc["wWCirc"] = Helpers::render_value(s, wWCirc_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(extTemp_)) {
doc["outdoorTemp"] = (float)extTemp_ / 10;
}
if (Helpers::hasValue(wWCurTmp_)) {
doc["wWCurTmp"] = (float)wWCurTmp_ / 10;
}
if (Helpers::hasValue(wWCurTmp2_)) {
doc["wWCurTmp2"] = (float)wWCurTmp2_ / 10;
}
if (Helpers::hasValue(wWCurFlow_)) {
doc["wWCurFlow"] = (float)wWCurFlow_ / 10;
}
if (Helpers::hasValue(curFlowTemp_)) {
doc["curFlowTemp"] = (float)curFlowTemp_ / 10;
}
if (Helpers::hasValue(retTemp_)) {
doc["retTemp"] = (float)retTemp_ / 10;
}
if (Helpers::hasValue(switchTemp_)) {
doc["switchTemp"] = (float)switchTemp_ / 10;
}
if (Helpers::hasValue(sysPress_)) {
doc["sysPress"] = (float)sysPress_ / 10;
}
if (Helpers::hasValue(boilTemp_)) {
doc["boilTemp"] = (float)boilTemp_ / 10;
}
if (Helpers::hasValue(wwStorageTemp1_)) {
doc["wwStorageTemp1"] = (float)wwStorageTemp1_ / 10;
}
if (Helpers::hasValue(wwStorageTemp2_)) {
doc["wwStorageTemp2"] = (float)wwStorageTemp2_ / 10;
}
if (Helpers::hasValue(exhaustTemp_)) {
doc["exhaustTemp"] = (float)exhaustTemp_ / 10;
}
if (Helpers::hasValue(wWActivated_, EMS_VALUE_BOOL)) {
doc["wWActivated"] = Helpers::render_value(s, wWActivated_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWOneTime_, EMS_VALUE_BOOL)) {
doc["wWOnetime"] = Helpers::render_value(s, wWOneTime_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWDisinfecting_, EMS_VALUE_BOOL)) {
doc["wWDisinfecting"] = Helpers::render_value(s, wWDisinfecting_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWReadiness_, EMS_VALUE_BOOL)) {
doc["wWReady"] = Helpers::render_value(s, wWReadiness_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWRecharging_, EMS_VALUE_BOOL)) {
doc["wWRecharge"] = Helpers::render_value(s, wWRecharging_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWTemperatureOK_, EMS_VALUE_BOOL)) {
doc["wWTempOK"] = Helpers::render_value(s, wWTemperatureOK_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWCirc_, EMS_VALUE_BOOL)) {
doc["wWCirc"] = Helpers::render_value(s, wWCirc_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(burnGas_, EMS_VALUE_BOOL)) {
doc["burnGas"] = Helpers::render_value(s, burnGas_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(flameCurr_)) {
doc["flameCurr"] = (float)(int16_t)flameCurr_ / 10;
}
if (Helpers::hasValue(heatPmp_, EMS_VALUE_BOOL)) {
doc["heatPump"] = Helpers::render_value(s, heatPmp_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(fanWork_, EMS_VALUE_BOOL)) {
doc["fanWork"] = Helpers::render_value(s, fanWork_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(ignWork_, EMS_VALUE_BOOL)) {
doc["ignWork"] = Helpers::render_value(s, ignWork_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWHeat_, EMS_VALUE_BOOL)) {
doc["wWHeat"] = Helpers::render_value(s, wWHeat_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(heating_activated_, EMS_VALUE_BOOL)) {
doc["heatingActivated"] = Helpers::render_value(s, heating_activated_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(heating_temp_)) {
doc["heatingTemp"] = heating_temp_;
}
if (Helpers::hasValue(pump_mod_max_)) {
doc["pumpModMax"] = pump_mod_max_;
}
if (Helpers::hasValue(pump_mod_min_)) {
doc["pumpModMin"] = pump_mod_min_;
}
if (Helpers::hasValue(pumpDelay_)) {
doc["pumpDelay"] = pumpDelay_;
}
if (Helpers::hasValue(burnPeriod_)) {
doc["burnMinPeriod"] = burnPeriod_;
}
if (Helpers::hasValue(burnPowermin_)) {
doc["burnMinPower"] = burnPowermin_;
}
if (Helpers::hasValue(burnPowermax_)) {
doc["burnMaxPower"] = burnPowermax_;
}
if (Helpers::hasValue(boilTemp_on_)) {
doc["boilHystOn"] = boilTemp_on_;
}
if (Helpers::hasValue(boilTemp_off_)) {
doc["boilHystOff"] = boilTemp_off_;
}
if (Helpers::hasValue(setFlowTemp_)) {
doc["setFlowTemp"] = setFlowTemp_;
}
if (Helpers::hasValue(setWWPumpPow_)) {
doc["wWSetPumpPower"] = setWWPumpPow_;
}
if (Helpers::hasValue(wWStarts_)) {
doc["wWStarts"] = wWStarts_;
}
if (Helpers::hasValue(wWWorkM_)) {
doc["wWWorkM"] = wWWorkM_;
}
if (Helpers::hasValue(UBAuptime_)) {
doc["UBAuptime"] = UBAuptime_;
}
if (Helpers::hasValue(burnStarts_)) {
doc["burnStarts"] = burnStarts_;
}
if (Helpers::hasValue(burnWorkMin_)) {
doc["burnWorkMin"] = burnWorkMin_;
}
if (Helpers::hasValue(heatWorkMin_)) {
doc["heatWorkMin"] = heatWorkMin_;
}
if (Helpers::hasValue(serviceCode_)) {
doc["serviceCode"] = serviceCodeChar_;
doc["serviceCodeNumber"] = serviceCode_;
}
// if we have data, publish it
if (!doc.isNull()) {
Mqtt::publish(F("boiler_data"), doc);
JsonObject output = doc.to<JsonObject>();
if (export_values(output)) {
Mqtt::publish(F("boiler_data"), doc.as<JsonObject>());
}
}
@@ -801,6 +816,7 @@ bool Boiler::set_heating_activated(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, v)) {
return false;
}
LOG_INFO(F("Setting boiler heating "), v ? "on" : "off");
if (get_toggle_fetch(EMS_TYPE_UBAParametersPlus)) {
write_command(EMS_TYPE_UBAParametersPlus, 0, v ? 0x01 : 0, EMS_TYPE_UBAParametersPlus);

View File

@@ -50,6 +50,7 @@ class Boiler : public EMSdevice {
void console_commands(Shell & shell, unsigned int context);
void register_mqtt_ha_config();
void check_active();
bool export_values(JsonObject & doc);
uint8_t last_boilerState = 0xFF; // remember last state of heating and warm water on/off
uint8_t mqtt_format_; // single, nested or ha
@@ -76,7 +77,7 @@ class Boiler : public EMSdevice {
// MC10Status
uint16_t wwMixTemperature_ = EMS_VALUE_USHORT_NOTSET; // mengertemperatuur
uint16_t wwBufferBoilerTemperature_ = EMS_VALUE_USHORT_NOTSET; // bufferboilertemperatuur
uint16_t wwBufferBoilerTemperature_ = EMS_VALUE_USHORT_NOTSET; // bufferboilertemperature
// UBAMonitorFast - 0x18 on EMS1
uint8_t selFlowTemp_ = EMS_VALUE_UINT_NOTSET; // Selected flow temperature
@@ -146,6 +147,8 @@ class Boiler : public EMSdevice {
uint8_t heating_active_ = EMS_VALUE_BOOL_NOTSET; // Central heating is on/off
uint8_t pumpMod2_ = EMS_VALUE_UINT_NOTSET; // heatpump modulation from 0xE3 (heatpumps)
bool command_info(const char * value, const int8_t id, JsonObject & output);
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);

View File

@@ -53,10 +53,24 @@ Mixing::Mixing(uint8_t device_type, uint8_t device_id, uint8_t product_id, const
if (flags == EMSdevice::EMS_DEVICE_FLAG_IPM) {
register_telegram_type(0x010C, F("IPMSetMessage"), false, [&](std::shared_ptr<const Telegram> t) { process_IPMStatusMessage(t); });
}
// API call
Command::add_with_json(this->device_type(), F("info"), [&](const char * value, const int8_t id, JsonObject & object) {
return command_info(value, id, object);
});
}
// add context submenu
void Mixing::add_context_menu() {
// TODO support for multiple mixing units from a single menu, similar to set master with thermostat
/*
EMSESPShell::commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(mixing)},
[&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
Mixing::console_commands(shell, ShellContext::MIXING);
});
*/
}
// output json to web UI
@@ -89,7 +103,24 @@ bool Mixing::updated_values() {
}
// add console commands
void Mixing::console_commands() {
void Mixing::console_commands(Shell & shell, unsigned int context) {
EMSESPShell::commands->add_command(ShellContext::MIXING,
CommandFlags::ADMIN,
flash_string_vector{F_(read)},
flash_string_vector{F_(typeid_mandatory)},
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
uint16_t type_id = Helpers::hextoint(arguments.front().c_str());
EMSESP::set_read_id(type_id);
EMSESP::send_read_request(type_id, device_id());
});
EMSESPShell::commands->add_command(ShellContext::MIXING,
CommandFlags::USER,
flash_string_vector{F_(show)},
[&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { show_values(shell); });
// enter the context
Console::enter_custom_context(shell, context);
}
// display all values into the shell console
@@ -117,49 +148,66 @@ void Mixing::show_values(uuid::console::Shell & shell) {
shell.println();
}
bool Mixing::command_info(const char * value, const int8_t id, JsonObject & output) {
return (export_values(output));
}
// publish values via MQTT
// ideally we should group up all the mixing units together into a nested JSON but for now we'll send them individually
void Mixing::publish_values() {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
char s[5]; // for formatting strings
JsonObject output = doc.to<JsonObject>();
if (export_values(output)) {
char topic[30];
char s[5];
strlcpy(topic, "mixing_data", 30);
strlcat(topic, Helpers::itoa(s, device_id() - 0x20 + 1), 30); // append hc to topic
Mqtt::publish(topic, doc.as<JsonObject>());
}
}
// creates JSON doc from values
// returns false if empty
bool Mixing::export_values(JsonObject & output) {
char s[5]; // for formatting strings
switch (type_) {
case Type::HC:
doc["type"] = "hc";
output["type"] = "hc";
if (Helpers::hasValue(flowTemp_)) {
doc["flowTemp"] = (float)flowTemp_ / 10;
output["flowTemp"] = (float)flowTemp_ / 10;
}
if (Helpers::hasValue(flowSetTemp_)) {
doc["flowSetTemp"] = flowSetTemp_;
output["flowSetTemp"] = flowSetTemp_;
}
if (Helpers::hasValue(pump_)) {
doc["pumpStatus"] = Helpers::render_value(s, pump_, EMS_VALUE_BOOL);
output["pumpStatus"] = Helpers::render_value(s, pump_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(status_)) {
doc["valveStatus"] = status_;
output["valveStatus"] = status_;
}
break;
case Type::WWC:
doc["type"] = "wwc";
output["type"] = "wwc";
if (Helpers::hasValue(flowTemp_)) {
doc["wwTemp"] = (float)flowTemp_ / 10;
output["wwTemp"] = (float)flowTemp_ / 10;
}
if (Helpers::hasValue(pump_)) {
doc["pumpStatus"] = pump_;
output["pumpStatus"] = pump_;
}
if (Helpers::hasValue(status_)) {
doc["tempStatus"] = status_;
output["tempStatus"] = status_;
}
break;
case Type::NONE:
default:
return;
return false;
break;
}
char topic[30];
strlcpy(topic, "mixing_data", 30);
strlcat(topic, Helpers::itoa(s, device_id() - 0x20 + 1), 30); // append hc to topic
Mqtt::publish(topic, doc);
return output.size();
}
// heating circuits 0x02D7, 0x02D8 etc...

View File

@@ -25,6 +25,7 @@
#include <uuid/log.h>
#include "emsdevice.h"
#include "emsesp.h"
#include "telegram.h"
#include "helpers.h"
#include "mqtt.h"
@@ -44,7 +45,9 @@ class Mixing : public EMSdevice {
private:
static uuid::log::Logger logger_;
void console_commands();
void console_commands(Shell & shell, unsigned int context);
bool export_values(JsonObject & doc);
bool command_info(const char * value, const int8_t id, JsonObject & output);
void process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram);
void process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> telegram);
@@ -66,7 +69,8 @@ class Mixing : public EMSdevice {
int8_t status_ = EMS_VALUE_UINT_NOTSET;
uint8_t flowSetTemp_ = EMS_VALUE_UINT_NOTSET;
Type type_ = Type::NONE;
bool changed_ = false;
bool changed_ = false;
};
} // namespace emsesp

View File

@@ -47,10 +47,28 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
register_telegram_type(0x0103, F("ISM1StatusMessage"), true, [&](std::shared_ptr<const Telegram> t) { process_ISM1StatusMessage(t); });
register_telegram_type(0x0101, F("ISM1Set"), false, [&](std::shared_ptr<const Telegram> t) { process_ISM1Set(t); });
}
// API call
Command::add_with_json(this->device_type(), F("info"), [&](const char * value, const int8_t id, JsonObject & object) {
return command_info(value, id, object);
});
}
bool Solar::command_info(const char * value, const int8_t id, JsonObject & output) {
return (export_values(output));
}
// context submenu
void Solar::add_context_menu() {
// TODO support for multiple solar units from a single menu, similar to set master with thermostat
/*
EMSESPShell::commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(solar)},
[&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
Solar::console_commands(shell, ShellContext::SOLAR);
});
*/
}
// print to web
@@ -109,69 +127,74 @@ void Solar::show_values(uuid::console::Shell & shell) {
// publish values via MQTT
void Solar::publish_values() {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
JsonObject output = doc.to<JsonObject>();
if (export_values(output)) {
Mqtt::publish(F("sm_data"), doc.as<JsonObject>());
}
}
// creates JSON doc from values
// returns false if empty
bool Solar::export_values(JsonObject & output) {
char s[10]; // for formatting strings
if (Helpers::hasValue(collectorTemp_)) {
doc["collectorTemp"] = (float)collectorTemp_ / 10;
output["collectorTemp"] = (float)collectorTemp_ / 10;
}
if (Helpers::hasValue(tankBottomTemp_)) {
doc["tankBottomTemp"] = (float)tankBottomTemp_ / 10;
output["tankBottomTemp"] = (float)tankBottomTemp_ / 10;
}
if (Helpers::hasValue(tankBottomTemp2_)) {
doc["tankBottomTemp2"] = (float)tankBottomTemp2_ / 10;
output["tankBottomTemp2"] = (float)tankBottomTemp2_ / 10;
}
if (Helpers::hasValue(heatExchangerTemp_)) {
doc["heatExchangerTemp"] = (float)heatExchangerTemp_ / 10;
output["heatExchangerTemp"] = (float)heatExchangerTemp_ / 10;
}
if (Helpers::hasValue(solarPumpModulation_)) {
doc["solarPumpModulation"] = solarPumpModulation_;
output["solarPumpModulation"] = solarPumpModulation_;
}
if (Helpers::hasValue(cylinderPumpModulation_)) {
doc["cylinderPumpModulation"] = cylinderPumpModulation_;
output["cylinderPumpModulation"] = cylinderPumpModulation_;
}
if (Helpers::hasValue(solarPump_, EMS_VALUE_BOOL)) {
doc["solarPump"] = Helpers::render_value(s, solarPump_, EMS_VALUE_BOOL);
output["solarPump"] = Helpers::render_value(s, solarPump_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(valveStatus_, EMS_VALUE_BOOL)) {
doc["valveStatus"] = Helpers::render_value(s, valveStatus_, EMS_VALUE_BOOL);
output["valveStatus"] = Helpers::render_value(s, valveStatus_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(pumpWorkMin_)) {
doc["pumpWorkMin"] = pumpWorkMin_;
output["pumpWorkMin"] = pumpWorkMin_;
}
if (Helpers::hasValue(tankHeated_, EMS_VALUE_BOOL)) {
doc["tankHeated"] = Helpers::render_value(s, tankHeated_, EMS_VALUE_BOOL);
output["tankHeated"] = Helpers::render_value(s, tankHeated_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(collectorShutdown_, EMS_VALUE_BOOL)) {
doc["collectorShutdown"] = Helpers::render_value(s, collectorShutdown_, EMS_VALUE_BOOL);
output["collectorShutdown"] = Helpers::render_value(s, collectorShutdown_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(energyLastHour_)) {
doc["energyLastHour"] = (float)energyLastHour_ / 10;
output["energyLastHour"] = (float)energyLastHour_ / 10;
}
if (Helpers::hasValue(energyToday_)) {
doc["energyToday"] = energyToday_;
output["energyToday"] = energyToday_;
}
if (Helpers::hasValue(energyTotal_)) {
doc["energyTotal"] = (float)energyTotal_ / 10;
output["energyTotal"] = (float)energyTotal_ / 10;
}
// if we have data, publish it
if (!doc.isNull()) {
Mqtt::publish(F("sm_data"), doc);
}
return output.size();
}
// check to see if values have been updated
@@ -184,7 +207,24 @@ bool Solar::updated_values() {
}
// add console commands
void Solar::console_commands() {
void Solar::console_commands(Shell & shell, unsigned int context) {
EMSESPShell::commands->add_command(ShellContext::SOLAR,
CommandFlags::ADMIN,
flash_string_vector{F_(read)},
flash_string_vector{F_(typeid_mandatory)},
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
uint16_t type_id = Helpers::hextoint(arguments.front().c_str());
EMSESP::set_read_id(type_id);
EMSESP::send_read_request(type_id, device_id());
});
EMSESPShell::commands->add_command(ShellContext::SOLAR,
CommandFlags::USER,
flash_string_vector{F_(show)},
[&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { show_values(shell); });
// enter the context
Console::enter_custom_context(shell, context);
}
// SM10Monitor - type 0x97

View File

@@ -25,6 +25,7 @@
#include <uuid/log.h>
#include "emsdevice.h"
#include "emsesp.h"
#include "telegram.h"
#include "helpers.h"
#include "mqtt.h"
@@ -44,7 +45,9 @@ class Solar : public EMSdevice {
private:
static uuid::log::Logger logger_;
void console_commands();
void console_commands(Shell & shell, unsigned int context);
bool export_values(JsonObject & doc);
bool command_info(const char * value, const int8_t id, JsonObject & output);
int16_t collectorTemp_ = EMS_VALUE_SHORT_NOTSET; // TS1: Temperature sensor for collector array 1
int16_t tankBottomTemp_ = EMS_VALUE_SHORT_NOTSET; // TS2: Temperature sensor 1 cylinder, bottom (solar thermal system)
@@ -65,7 +68,8 @@ class Solar : public EMSdevice {
uint8_t availabilityFlag_ = EMS_VALUE_BOOL_NOTSET;
uint8_t configFlag_ = EMS_VALUE_BOOL_NOTSET;
uint8_t userFlag_ = EMS_VALUE_BOOL_NOTSET;
bool changed_ = false;
bool changed_ = false;
void process_SM10Monitor(std::shared_ptr<const Telegram> telegram);
void process_SM100Monitor(std::shared_ptr<const Telegram> telegram);

View File

@@ -248,21 +248,34 @@ bool Thermostat::updated_values() {
return false;
}
// info API command
// returns the same MQTT publish payload in Nested format
bool Thermostat::command_info(const char * value, const int8_t id, JsonObject & output) {
return (export_values(MQTT_format::NESTED, output));
}
// publish values via MQTT
void Thermostat::publish_values() {
// only publish on the master thermostat
if (EMSESP::actual_master_thermostat() != this->device_id()) {
return;
}
uint8_t flags = this->model();
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
JsonObject rootThermostat = doc.to<JsonObject>();
JsonObject dataThermostat;
JsonObject output = doc.to<JsonObject>();
export_values(mqtt_format_, output);
if (mqtt_format_ == MQTT_format::NESTED) {
Mqtt::publish(F("thermostat_data"), output);
}
}
// creates JSON doc from values
// returns false if empty
bool Thermostat::export_values(uint8_t mqtt_format, JsonObject & rootThermostat) {
uint8_t flags = this->model();
JsonObject dataThermostat;
// add external temp and other stuff specific to the RC30 and RC35
// if ((flags == EMS_DEVICE_FLAG_RC35 || flags == EMS_DEVICE_FLAG_RC30_1) && (mqtt_format_ == MQTT_format::SINGLE || mqtt_format_ == MQTT_format::CUSTOM)) {
if (flags == EMS_DEVICE_FLAG_RC35 || flags == EMS_DEVICE_FLAG_RC30_1) {
if (datetime_.size()) {
rootThermostat["time"] = datetime_.c_str();
@@ -302,9 +315,9 @@ void Thermostat::publish_values() {
}
// send this specific data using the thermostat_data topic
if (mqtt_format_ != MQTT_format::NESTED) {
Mqtt::publish(F("thermostat_data"), doc);
rootThermostat = doc.to<JsonObject>(); // clear object
if (mqtt_format != MQTT_format::NESTED) {
Mqtt::publish(F("thermostat_data"), rootThermostat);
rootThermostat.clear(); // clear object
}
}
@@ -315,8 +328,7 @@ void Thermostat::publish_values() {
has_data = true;
// if the MQTT format is 'nested' or 'ha' then create the parent object hc<n>
// if (mqtt_format_ != MQTT_format::SINGLE) {
if ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::HA)) {
if ((mqtt_format == MQTT_format::NESTED) || (mqtt_format == MQTT_format::HA)) {
char hc_name[10]; // hc{1-4}
strlcpy(hc_name, "hc", 10);
char s[3];
@@ -397,11 +409,11 @@ void Thermostat::publish_values() {
dataThermostat["summertemp"] = hc->summertemp;
}
// when using HA always send the mode otherwise it'll may break the component/widget and report an error
if ((Helpers::hasValue(hc->mode)) || (mqtt_format_ == MQTT_format::HA)) {
// mode - always force showing this when in HA so not to break HA's climate component
if ((Helpers::hasValue(hc->mode)) || (mqtt_format == MQTT_format::HA)) {
uint8_t hc_mode = hc->get_mode(flags);
// if we're sending to HA the only valid mode types are heat, auto and off
if (mqtt_format_ == MQTT_format::HA) {
if (mqtt_format == MQTT_format::HA) {
if ((hc_mode == HeatingCircuit::Mode::MANUAL) || (hc_mode == HeatingCircuit::Mode::DAY)) {
hc_mode = HeatingCircuit::Mode::HEAT;
} else if ((hc_mode == HeatingCircuit::Mode::NIGHT) || (hc_mode == HeatingCircuit::Mode::OFF)) {
@@ -425,15 +437,13 @@ void Thermostat::publish_values() {
// if format is single, send immediately and clear object for next hc
// the topic will have the hc number appended
// if (mqtt_format_ == MQTT_format::SINGLE) {
if ((mqtt_format_ == MQTT_format::SINGLE) || (mqtt_format_ == MQTT_format::CUSTOM)) {
if ((mqtt_format == MQTT_format::SINGLE) || (mqtt_format == MQTT_format::CUSTOM)) {
char topic[30];
char s[3];
strlcpy(topic, "thermostat_data", 30);
strlcat(topic, Helpers::itoa(s, hc->hc_num()), 30); // append hc to topic
Mqtt::publish(topic, doc);
rootThermostat = doc.to<JsonObject>(); // clear object
} else if (mqtt_format_ == MQTT_format::HA) {
rootThermostat.clear(); // clear object
} else if (mqtt_format == MQTT_format::HA) {
// see if we have already registered this with HA MQTT Discovery, if not send the config
if (!hc->ha_registered()) {
register_mqtt_ha_config(hc->hc_num());
@@ -442,19 +452,12 @@ void Thermostat::publish_values() {
// send the thermostat topic and payload data
std::string topic(100, '\0');
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/state"), hc->hc_num());
Mqtt::publish(topic, doc);
Mqtt::publish(topic, rootThermostat);
}
}
}
if (!has_data) {
return; // nothing to send, quit
}
// if we're using nested json, send all in one go under one topic called thermostat_data
if (mqtt_format_ == MQTT_format::NESTED) {
Mqtt::publish(F("thermostat_data"), doc);
}
return (has_data);
}
// returns the heating circuit object based on the hc number
@@ -599,7 +602,7 @@ void Thermostat::register_mqtt_ha_config(uint8_t hc_num) {
std::string topic(100, '\0'); // e.g homeassistant/climate/hc1/thermostat/config
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/config"), hc_num);
// Mqtt::publish(topic); // empty payload, this remove any previous config sent to HA
Mqtt::publish_retain(topic, doc, true); // publish the config payload with retain flag
Mqtt::publish_retain(topic, doc.as<JsonObject>(), true); // publish the config payload with retain flag
// subscribe to the temp and mode commands
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/cmd_temp"), hc_num);
@@ -1922,8 +1925,13 @@ bool Thermostat::set_manualtemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::MANUAL);
}
// commands for MQTT and Console
// API commands for MQTT and Console
void Thermostat::add_commands() {
// API call
Command::add_with_json(this->device_type(), F("info"), [&](const char * value, const int8_t id, JsonObject & object) {
return command_info(value, id, object);
});
// if this thermostat doesn't support write, don't add the commands
if ((this->flags() & EMSdevice::EMS_DEVICE_FLAG_NO_WRITE) == EMSdevice::EMS_DEVICE_FLAG_NO_WRITE) {
return;

View File

@@ -108,6 +108,7 @@ class Thermostat : public EMSdevice {
void console_commands(Shell & shell, unsigned int context);
void add_commands();
bool export_values(uint8_t mqtt_format, JsonObject & doc);
// specific thermostat characteristics, stripping the option bits at pos 6 and 7
inline uint8_t model() const {
@@ -221,6 +222,7 @@ class Thermostat : public EMSdevice {
std::shared_ptr<Thermostat::HeatingCircuit> heating_circuit(const uint8_t hc_num);
void register_mqtt_ha_config(uint8_t hc_num);
bool command_info(const char * value, const int8_t id, JsonObject & output);
void process_RCOutdoorTemp(std::shared_ptr<const Telegram> telegram);
void process_IBASettings(std::shared_ptr<const Telegram> telegram);