remove mqtt custom, add thermostat wwtemps, mixing mqtt formats and api output

This commit is contained in:
MichaelDvP
2020-10-08 10:37:21 +02:00
parent 7d5a654f52
commit bddf377b59
9 changed files with 93 additions and 77 deletions

View File

@@ -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"

View File

@@ -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;

View File

@@ -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

View File

@@ -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
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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