mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 15:59:52 +03:00
remove mqtt custom, add thermostat wwtemps, mixing mqtt formats and api output
This commit is contained in:
@@ -116,7 +116,6 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
|
||||
<MenuItem value={1}>Single</MenuItem>
|
||||
<MenuItem value={2}>Nested</MenuItem>
|
||||
<MenuItem value={3}>Home Assistant</MenuItem>
|
||||
<MenuItem value={4}>Custom</MenuItem>
|
||||
</SelectValidator>
|
||||
<SelectValidator name="mqtt_qos"
|
||||
label="QoS"
|
||||
|
||||
@@ -67,21 +67,21 @@ void EMSESPAPIService::emsespAPIService(AsyncWebServerRequest * request) {
|
||||
id = request->getParam(F_(id))->value();
|
||||
}
|
||||
|
||||
if (id.isEmpty()) {
|
||||
id = "-1";
|
||||
}
|
||||
|
||||
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_LARGE);
|
||||
JsonObject output = doc.to<JsonObject>();
|
||||
bool ok = false;
|
||||
|
||||
// execute the command
|
||||
if (data.isEmpty()) {
|
||||
ok = Command::call(device_type, cmd.c_str(), nullptr, -1, output); // command only
|
||||
ok = Command::call(device_type, cmd.c_str(), nullptr, id.toInt(), output); // command only
|
||||
} else {
|
||||
if (api_enabled) {
|
||||
// we only allow commands with parameters if the API is enabled
|
||||
if (id.isEmpty()) {
|
||||
ok = Command::call(device_type, cmd.c_str(), data.c_str(), -1, output); // only ID
|
||||
} else {
|
||||
ok = Command::call(device_type, cmd.c_str(), data.c_str(), id.toInt(), output); // has cmd, data and id
|
||||
}
|
||||
ok = Command::call(device_type, cmd.c_str(), data.c_str(), id.toInt(), output); // has cmd, data and id
|
||||
} else {
|
||||
request->send(401, "text/plain", F("Unauthorized"));
|
||||
return;
|
||||
|
||||
@@ -40,7 +40,7 @@ bool Command::call(const uint8_t device_type, const char * cmd, const char * val
|
||||
LOG_DEBUG(F("[DEBUG] Calling command %s, value %s, id is %d in %s"), cmd, value, id, dname.c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
bool ok = false;
|
||||
if (!cmdfunctions_.empty()) {
|
||||
for (const auto & cf : cmdfunctions_) {
|
||||
if (cf.device_type_ == device_type) {
|
||||
@@ -50,16 +50,17 @@ bool Command::call(const uint8_t device_type, const char * cmd, const char * val
|
||||
// check if json object is empty, if so quit
|
||||
if (output.isNull()) {
|
||||
LOG_WARNING(F("Ignore call for command %s in %s because no json"), cmd, EMSdevice::device_type_2_device_name(device_type).c_str());
|
||||
return false;
|
||||
}
|
||||
return ((cf.cmdfunction_json_)(value, id, output));
|
||||
ok |= ((cf.cmdfunction_json_)(value, id, output));
|
||||
} else {
|
||||
return ((cf.cmdfunction_)(value, id));
|
||||
ok |= ((cf.cmdfunction_)(value, id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false; // command not found
|
||||
return ok;
|
||||
}
|
||||
|
||||
// add a command to the list, which does not return json
|
||||
|
||||
@@ -69,22 +69,22 @@ void Mixing::device_info_web(JsonArray & root) {
|
||||
// fetch the values into a JSON document
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
|
||||
JsonObject output = doc.to<JsonObject>();
|
||||
if (!export_values(output)) {
|
||||
if (!export_values(Mqtt::Format::SINGLE, output)) {
|
||||
return; // empty
|
||||
}
|
||||
|
||||
char prefix_str[10];
|
||||
if (type() == Type::WWC) {
|
||||
snprintf_P(prefix_str, sizeof(prefix_str), PSTR("(wwc %d) "), hc_);
|
||||
print_value_json(root, F("wwTemp"), FPSTR(prefix_str), F_(wwTemp), F_(degrees), output);
|
||||
print_value_json(root, F("pumpStatus"), FPSTR(prefix_str), F_(pumpStatus), nullptr, output);
|
||||
print_value_json(root, F("tempStatus"), FPSTR(prefix_str), F_(tempStatus), nullptr, output);
|
||||
} else {
|
||||
if (type() == Type::HC) {
|
||||
snprintf_P(prefix_str, sizeof(prefix_str), PSTR("(hc %d) "), hc_);
|
||||
print_value_json(root, F("flowTemp"), FPSTR(prefix_str), F_(flowTemp), F_(degrees), output);
|
||||
print_value_json(root, F("flowSetTemp"), FPSTR(prefix_str), F_(flowSetTemp), F_(degrees), output);
|
||||
print_value_json(root, F("pumpStatus"), FPSTR(prefix_str), F_(pumpStatus), nullptr, output);
|
||||
print_value_json(root, F("valveStatus"), FPSTR(prefix_str), F_(valveStatus), F_(percent), output);
|
||||
} else {
|
||||
snprintf_P(prefix_str, sizeof(prefix_str), PSTR("(wwc %d) "), hc_);
|
||||
print_value_json(root, F("wwTemp"), FPSTR(prefix_str), F_(wwTemp), F_(degrees), output);
|
||||
print_value_json(root, F("pumpStatus"), FPSTR(prefix_str), F_(pumpStatus), nullptr, output);
|
||||
print_value_json(root, F("tempStatus"), FPSTR(prefix_str), F_(tempStatus), nullptr, output);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,28 +108,32 @@ void Mixing::show_values(uuid::console::Shell & shell) {
|
||||
// fetch the values into a JSON document
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
|
||||
JsonObject output = doc.to<JsonObject>();
|
||||
if (!export_values(output)) {
|
||||
if (!export_values(Mqtt::Format::SINGLE, output)) {
|
||||
return; // empty
|
||||
}
|
||||
|
||||
if (type() == Type::WWC) {
|
||||
shell.println(F_(ww_hc));
|
||||
print_value_json(shell, F("wwTemp"), nullptr, F_(wwTemp), F_(degrees), output);
|
||||
print_value_json(shell, F("pumpStatus"), nullptr, F_(pumpStatus), nullptr, output);
|
||||
print_value_json(shell, F("tempStatus"), nullptr, F_(tempStatus), nullptr, output);
|
||||
if (type() == Type::HC) {
|
||||
shell.printfln(F_(hc), hc_);
|
||||
print_value_json(shell, F("flowTemp"), F(" "), F_(flowTemp), F_(degrees), output);
|
||||
print_value_json(shell, F("flowSetTemp"), F(" "), F_(flowSetTemp), F_(degrees), output);
|
||||
print_value_json(shell, F("pumpStatus"), F(" "), F_(pumpStatus), nullptr, output);
|
||||
print_value_json(shell, F("valveStatus"), F(" "), F_(valveStatus), F_(percent), output);
|
||||
} else {
|
||||
shell.println(F_(hc));
|
||||
print_value_json(shell, F("flowTemp"), nullptr, F_(flowTemp), F_(degrees), output);
|
||||
print_value_json(shell, F("flowSetTemp"), nullptr, F_(flowSetTemp), F_(degrees), output);
|
||||
print_value_json(shell, F("pumpStatus"), nullptr, F_(pumpStatus), nullptr, output);
|
||||
print_value_json(shell, F("valveStatus"), nullptr, F_(valveStatus), F_(percent), output);
|
||||
shell.printfln(F_(ww_hc), hc_);
|
||||
print_value_json(shell, F("wwTemp"), F(" "), F_(wwTemp), F_(degrees), output);
|
||||
print_value_json(shell, F("pumpStatus"), F(" "), F_(pumpStatus), nullptr, output);
|
||||
print_value_json(shell, F("tempStatus"), F(" "), F_(tempStatus), nullptr, output);
|
||||
}
|
||||
|
||||
shell.println();
|
||||
}
|
||||
|
||||
// export all valuet to info command
|
||||
bool Mixing::command_info(const char * value, const int8_t id, JsonObject & output) {
|
||||
return (export_values(output));
|
||||
if (id != (device_id() - 0x20 + 1) && id > 0) { // defaults to first hc if no id
|
||||
return false;
|
||||
}
|
||||
return (export_values(Mqtt::Format::NESTED, output));
|
||||
}
|
||||
|
||||
// publish values via MQTT
|
||||
@@ -137,7 +141,7 @@ bool Mixing::command_info(const char * value, const int8_t id, JsonObject & outp
|
||||
void Mixing::publish_values() {
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
|
||||
JsonObject output = doc.to<JsonObject>();
|
||||
if (export_values(output)) {
|
||||
if (export_values(Mqtt::mqtt_format(), output)) {
|
||||
char topic[30];
|
||||
char s[5];
|
||||
strlcpy(topic, "mixing_data", 30);
|
||||
@@ -189,43 +193,49 @@ void Mixing::register_mqtt_ha_config(const char * topic) {
|
||||
|
||||
// creates JSON doc from values
|
||||
// returns false if empty
|
||||
bool Mixing::export_values(JsonObject & output) {
|
||||
switch (this->type()) {
|
||||
case Type::HC:
|
||||
output["type"] = "hc";
|
||||
bool Mixing::export_values(uint8_t mqtt_format, JsonObject & output) {
|
||||
JsonObject output_hc;
|
||||
char hc_name[10]; // hc{1-4}
|
||||
|
||||
if (this->type() == Type::HC) {
|
||||
snprintf_P(hc_name, sizeof(hc_name), PSTR("hc%d"), hc_);
|
||||
if ((mqtt_format == Mqtt::Format::NESTED)) {
|
||||
output_hc = output.createNestedObject(hc_name);
|
||||
} else {
|
||||
output_hc = output;
|
||||
output["type"] = "hc";
|
||||
}
|
||||
if (Helpers::hasValue(flowTemp_)) {
|
||||
output["flowTemp"] = (float)flowTemp_ / 10;
|
||||
output_hc["flowTemp"] = (float)flowTemp_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(flowSetTemp_)) {
|
||||
output["flowSetTemp"] = flowSetTemp_;
|
||||
output_hc["flowSetTemp"] = flowSetTemp_;
|
||||
}
|
||||
if (Helpers::hasValue(pumpStatus_)) {
|
||||
char s[5]; // for formatting strings
|
||||
output["pumpStatus"] = Helpers::render_value(s, pumpStatus_, EMS_VALUE_BOOL);
|
||||
output_hc["pumpStatus"] = Helpers::render_value(s, pumpStatus_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(status_)) {
|
||||
output["valveStatus"] = status_;
|
||||
output_hc["valveStatus"] = status_;
|
||||
}
|
||||
} else {
|
||||
snprintf_P(hc_name, sizeof(hc_name), PSTR("wwc%d"), hc_);
|
||||
if ((mqtt_format == Mqtt::Format::NESTED)) {
|
||||
output_hc = output.createNestedObject(hc_name);
|
||||
} else {
|
||||
output_hc = output;
|
||||
output["type"] = "wwc";
|
||||
}
|
||||
break;
|
||||
|
||||
case Type::WWC:
|
||||
output["type"] = "wwc";
|
||||
if (Helpers::hasValue(flowTemp_)) {
|
||||
output["wwTemp"] = (float)flowTemp_ / 10;
|
||||
output_hc["wwTemp"] = (float)flowTemp_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(pumpStatus_)) {
|
||||
char s[5]; // for formatting strings
|
||||
output["pumpStatus"] = Helpers::render_value(s, pumpStatus_, EMS_VALUE_BOOL);
|
||||
output_hc["pumpStatus"] = Helpers::render_value(s, pumpStatus_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(status_)) {
|
||||
output["tempStatus"] = status_;
|
||||
output_hc["tempStatus"] = status_;
|
||||
}
|
||||
break;
|
||||
|
||||
case Type::NONE:
|
||||
default:
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
return output.size();
|
||||
@@ -285,8 +295,8 @@ void Mixing::process_MMStatusMessage(std::shared_ptr<const Telegram> telegram) {
|
||||
// 0x21 is position 2. 0x20 is typically reserved for the WM10 switch module
|
||||
// see https://github.com/proddy/EMS-ESP/issues/270 and https://github.com/proddy/EMS-ESP/issues/386#issuecomment-629610918
|
||||
hc_ = device_id() - 0x20 + 1;
|
||||
changed_ |= telegram->read_value(flowTemp_, 1); // is * 10
|
||||
changed_ |= telegram->read_bitvalue(pumpStatus_, 3, 0);
|
||||
changed_ |= telegram->read_value(flowTemp_, 1); // is * 10
|
||||
changed_ |= telegram->read_bitvalue(pumpStatus_, 3, 2); // is 0 or 0x64 (100%), check only bit 2
|
||||
changed_ |= telegram->read_value(flowSetTemp_, 0);
|
||||
changed_ |= telegram->read_value(status_, 4); // valve status -100 to 100
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ class Mixing : public EMSdevice {
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
bool export_values(JsonObject & doc);
|
||||
bool export_values(uint8_t mqtt_format, JsonObject & doc);
|
||||
void register_mqtt_ha_config(const char * topic);
|
||||
bool command_info(const char * value, const int8_t id, JsonObject & output);
|
||||
|
||||
|
||||
@@ -182,6 +182,8 @@ void Thermostat::device_info_web(JsonArray & root) {
|
||||
print_value_json(root, F("minexttemp"), nullptr, F_(minexttemp), F_(degrees), output_main);
|
||||
print_value_json(root, F("building"), nullptr, F_(building), nullptr, output_main);
|
||||
print_value_json(root, F("wwmode"), nullptr, F_(wwmode), nullptr, output_main);
|
||||
print_value_json(root, F("wwtemp"), nullptr, F_(wwtemp), nullptr, output_main);
|
||||
print_value_json(root, F("wwtemplow"), nullptr, F_(wwtemplow), nullptr, output_main);
|
||||
print_value_json(root, F("wwcircmode"), nullptr, F_(wwcircmode), nullptr, output_main);
|
||||
}
|
||||
|
||||
@@ -192,13 +194,10 @@ void Thermostat::device_info_web(JsonArray & root) {
|
||||
for (const auto & hc : heating_circuits_) {
|
||||
if (hc->is_active()) {
|
||||
char prefix_str[10];
|
||||
snprintf_P(prefix_str, sizeof(prefix_str), PSTR("(hc %d) "), hc->hc_num());
|
||||
snprintf_P(prefix_str, sizeof(prefix_str), PSTR("hc%d"), hc->hc_num());
|
||||
JsonObject output = output_hc[prefix_str];
|
||||
|
||||
char hc_name[10]; // hc{1-4}
|
||||
strlcpy(hc_name, "hc", 10);
|
||||
char s[3];
|
||||
strlcat(hc_name, Helpers::itoa(s, hc->hc_num()), 10);
|
||||
JsonObject output = output_hc[hc_name];
|
||||
snprintf_P(prefix_str, sizeof(prefix_str), PSTR("(hc %d) "), hc->hc_num());
|
||||
|
||||
print_value_json(root, F("seltemp"), FPSTR(prefix_str), F_(seltemp), F_(degrees), output);
|
||||
print_value_json(root, F("currtemp"), FPSTR(prefix_str), F_(currtemp), F_(degrees), output);
|
||||
@@ -263,6 +262,8 @@ void Thermostat::show_values(uuid::console::Shell & shell) {
|
||||
print_value_json(shell, F("minexttemp"), nullptr, F_(minexttemp), F_(degrees), output_main);
|
||||
print_value_json(shell, F("building"), nullptr, F_(building), nullptr, output_main);
|
||||
print_value_json(shell, F("wwmode"), nullptr, F_(wwmode), nullptr, output_main);
|
||||
print_value_json(shell, F("wwtemp"), nullptr, F_(wwtemp), nullptr, output_main);
|
||||
print_value_json(shell, F("wwtemplow"), nullptr, F_(wwtemplow), nullptr, output_main);
|
||||
print_value_json(shell, F("wwcircmode"), nullptr, F_(wwcircmode), nullptr, output_main);
|
||||
}
|
||||
|
||||
@@ -274,12 +275,10 @@ void Thermostat::show_values(uuid::console::Shell & shell) {
|
||||
// display for each active heating circuit
|
||||
for (const auto & hc : heating_circuits_) {
|
||||
if (hc->is_active()) {
|
||||
shell.printfln(" Heating Circuit %d:", hc->hc_num());
|
||||
shell.printfln(F_(hc), hc->hc_num());
|
||||
|
||||
char hc_name[10]; // hc{1-4}
|
||||
strlcpy(hc_name, "hc", 10);
|
||||
char s[3];
|
||||
strlcat(hc_name, Helpers::itoa(s, hc->hc_num()), 10);
|
||||
snprintf_P(hc_name, sizeof(hc_name), PSTR("hc%d"), hc->hc_num());
|
||||
JsonObject output = output_hc[hc_name];
|
||||
|
||||
print_value_json(shell, F("seltemp"), F(" "), F_(seltemp), F_(degrees), output);
|
||||
@@ -423,6 +422,16 @@ bool Thermostat::export_values_main(JsonObject & rootThermostat) {
|
||||
}
|
||||
}
|
||||
|
||||
// Warm water temp
|
||||
if (Helpers::hasValue(wwTemp_)) {
|
||||
rootThermostat["wwtemp"] = wwTemp_;
|
||||
}
|
||||
|
||||
// Warm water low temp
|
||||
if (Helpers::hasValue(wwTempLow_)) {
|
||||
rootThermostat["wwtemplow"] = wwTempLow_;
|
||||
}
|
||||
|
||||
// Warm Water circulation mode
|
||||
if (Helpers::hasValue(wwCircMode_)) {
|
||||
char s[7];
|
||||
@@ -546,7 +555,7 @@ bool Thermostat::export_values_hc(uint8_t mqtt_format, JsonObject & rootThermost
|
||||
// Summer mode
|
||||
if (Helpers::hasValue(hc->summer_setmode)) {
|
||||
char s[7];
|
||||
dataThermostat["summermode"] = Helpers::render_enum(s, {"off", "auto", "on"}, hc->summer_setmode);
|
||||
dataThermostat["summermode"] = Helpers::render_enum(s, {"summer", "auto", "winter"}, hc->summer_setmode);
|
||||
}
|
||||
|
||||
// mode - always force showing this when in HA so not to break HA's climate component
|
||||
@@ -1710,7 +1719,7 @@ bool Thermostat::set_summermode(const char * value, const int8_t id) {
|
||||
return false;
|
||||
}
|
||||
uint8_t set = 0xFF;
|
||||
if (!Helpers::value2enum(value, set, {"off", "auto", "on"})) {
|
||||
if (!Helpers::value2enum(value, set, {"summer", "auto", "winter"})) {
|
||||
LOG_WARNING(F("Setting summer mode: Invalid mode"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -214,11 +214,11 @@ MAKE_PSTR(tankHeated, "Tank Heated")
|
||||
MAKE_PSTR(collectorShutdown, "Collector shutdown")
|
||||
|
||||
// mixing
|
||||
MAKE_PSTR(ww_hc, "Warm Water Circuit")
|
||||
MAKE_PSTR(ww_hc, " Warm Water Circuit %d:")
|
||||
MAKE_PSTR(wwTemp, "Current warm water temperature")
|
||||
MAKE_PSTR(pumpStatus, "Current pump status")
|
||||
MAKE_PSTR(tempStatus, "Current temperature status")
|
||||
MAKE_PSTR(hc, "Heating Circuit")
|
||||
MAKE_PSTR(hc, " Heating Circuit %d:")
|
||||
MAKE_PSTR(flowTemp, "Current flow temperature")
|
||||
MAKE_PSTR(flowSetTemp, "Setpoint flow temperature")
|
||||
|
||||
@@ -234,6 +234,8 @@ MAKE_PSTR(intoffset, "Offset int. temperature")
|
||||
MAKE_PSTR(minexttemp, "Min ext. temperature")
|
||||
MAKE_PSTR(building, "Building")
|
||||
MAKE_PSTR(wwmode, "Warm water mode")
|
||||
MAKE_PSTR(wwtemp, "Warm water high temperature")
|
||||
MAKE_PSTR(wwtemplow, "Warm water low temperature")
|
||||
MAKE_PSTR(wwcircmode, "Warm Water circulation mode")
|
||||
|
||||
// thermostat - per heating circuit
|
||||
|
||||
@@ -79,7 +79,7 @@ class Mqtt {
|
||||
|
||||
enum Operation { PUBLISH, SUBSCRIBE };
|
||||
|
||||
enum Format : uint8_t { NONE = 0, SINGLE, NESTED, HA, CUSTOM };
|
||||
enum Format : uint8_t { NONE = 0, SINGLE, NESTED, HA };
|
||||
|
||||
static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 100;
|
||||
|
||||
|
||||
@@ -325,10 +325,7 @@ void Sensor::publish_values() {
|
||||
for (const auto & device : devices_) {
|
||||
char s[7];
|
||||
|
||||
if (mqtt_format_ == Mqtt::Format::CUSTOM) {
|
||||
// e.g. sensor_data = {28-EA41-9497-0E03-5F":23.30,"28-233D-9497-0C03-8B":24.0}
|
||||
doc[device.to_string()] = Helpers::render_value(s, device.temperature_c, 1);
|
||||
} else if ((mqtt_format_ == Mqtt::Format::NESTED) || (mqtt_format_ == Mqtt::Format::HA)) {
|
||||
if ((mqtt_format_ == Mqtt::Format::NESTED) || (mqtt_format_ == Mqtt::Format::HA)) {
|
||||
// e.g. sensor_data = {"sensor1":{"id":"28-EA41-9497-0E03-5F","temp":"23.30"},"sensor2":{"id":"28-233D-9497-0C03-8B","temp":"24.0"}}
|
||||
char sensorID[20]; // sensor{1-n}
|
||||
strlcpy(sensorID, "sensor", 20);
|
||||
@@ -372,9 +369,7 @@ void Sensor::publish_values() {
|
||||
i++; // increment sensor count
|
||||
}
|
||||
|
||||
if (mqtt_format_ != Mqtt::Format::SINGLE) {
|
||||
Mqtt::publish(F("sensor_data"), doc.as<JsonObject>());
|
||||
}
|
||||
Mqtt::publish(F("sensor_data"), doc.as<JsonObject>());
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
Reference in New Issue
Block a user