Merge remote-tracking branch 'origin/v3.4' into dev

This commit is contained in:
proddy
2022-01-23 17:56:52 +01:00
parent 02e2b51814
commit 77e1898512
538 changed files with 32282 additions and 38655 deletions

413
src/analogsensor.cpp Normal file
View File

@@ -0,0 +1,413 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "analogsensor.h"
#include "emsesp.h"
namespace emsesp {
uuid::log::Logger AnalogSensor::logger_{F_(analogsensor), uuid::log::Facility::DAEMON};
void AnalogSensor::start() {
reload(); // fetch the list of sensors from our customization service
if (analog_enabled_) {
analogSetAttenuation(ADC_2_5db); // for all channels
}
LOG_INFO(F("Starting Analog sensor service"));
// Add API call for /info
Command::add(
EMSdevice::DeviceType::ANALOGSENSOR,
F_(info),
[&](const char * value, const int8_t id, JsonObject & output) { return command_info(value, id, output); },
F_(info_cmd));
}
// load settings from the customization file, sorts them and initializes the GPIOs
void AnalogSensor::reload() {
EMSESP::webSettingsService.read([&](WebSettings & settings) { analog_enabled_ = settings.analog_enabled; });
#if defined(EMSESP_STANDALONE)
analog_enabled_ = true; // for local offline testing
#endif
// load the list of analog sensors from the customization service
// and store them locally and then activate them
EMSESP::webCustomizationService.read([&](WebCustomization & settings) {
auto sensors = settings.analogCustomizations;
sensors_.clear(); // start with an empty list
if (sensors.size() != 0) {
for (auto & sensor : sensors) {
sensors_.emplace_back(sensor.id, sensor.name, sensor.offset, sensor.factor, sensor.uom, sensor.type);
sensors_.back().ha_registered = false; // this will trigger recrate of the HA config
}
}
return true;
});
// sort the list based on GPIO (id)
std::sort(sensors_.begin(), sensors_.end(), [](const Sensor & a, const Sensor & b) { return a.id() < b.id(); });
// activate each sensor
for (auto & sensor : sensors_) {
sensor.ha_registered = false; // force HA configs to be re-created
if (sensor.type() == AnalogType::ADC) {
LOG_DEBUG(F("Adding analog ADC sensor on GPIO%d"), sensor.id());
// analogSetPinAttenuation does not work with analogReadMilliVolts
sensor.analog_ = 0; // initialize
sensor.last_reading_ = 0;
} else if (sensor.type() == AnalogType::COUNTER) {
LOG_DEBUG(F("Adding analog I/O Counter sensor on GPIO%d"), sensor.id());
pinMode(sensor.id(), INPUT_PULLUP);
sensor.set_value(0); // reset count
sensor.set_uom(0); // no uom, just for safe measures
sensor.polltime_ = 0;
sensor.poll_ = digitalRead(sensor.id());
publish_sensor(sensor);
} else if (sensor.type() == AnalogType::DIGITAL_IN) {
LOG_DEBUG(F("Adding analog Read sensor on GPIO%d"), sensor.id());
pinMode(sensor.id(), INPUT_PULLUP);
sensor.set_value(digitalRead(sensor.id())); // initial value
sensor.set_uom(0); // no uom, just for safe measures
sensor.polltime_ = 0;
sensor.poll_ = digitalRead(sensor.id());
publish_sensor(sensor);
}
}
}
// measure and moving average adc
void AnalogSensor::measure() {
static uint32_t measure_last_ = 0;
// measure interval 500ms for analog sensors
if (!measure_last_ || (uuid::get_uptime() - measure_last_) >= MEASURE_ANALOG_INTERVAL) {
measure_last_ = uuid::get_uptime();
// go through the list of ADC sensors
for (auto & sensor : sensors_) {
if (sensor.type() == AnalogType::ADC) {
uint16_t a = analogReadMilliVolts(sensor.id()); // e.g. ADC1_CHANNEL_0_GPIO_NUM
if (!sensor.analog_) { // init first time
sensor.analog_ = a;
sensor.sum_ = a * 512;
} else { // simple moving average filter
sensor.sum_ = (sensor.sum_ * 511) / 512 + a;
sensor.analog_ = sensor.sum_ / 512;
}
// detect change with little hysteresis on raw mV value
if (sensor.last_reading_ + 1 < sensor.analog_ || sensor.last_reading_ > sensor.analog_ + 1) {
sensor.set_value(((int32_t)sensor.analog_ - sensor.offset()) * sensor.factor());
sensor.last_reading_ = sensor.analog_;
sensorreads_++;
changed_ = true;
publish_sensor(sensor);
}
}
}
}
// poll digital io every time
// go through the list of digital sensors
for (auto & sensor : sensors_) {
if (sensor.type() == AnalogType::DIGITAL_IN || sensor.type() == AnalogType::COUNTER) {
auto old_value = sensor.value(); // remember current value before reading
auto current_reading = digitalRead(sensor.id());
if (sensor.poll_ != current_reading) { // check for pinchange
sensor.polltime_ = uuid::get_uptime();
sensor.poll_ = current_reading;
}
if (uuid::get_uptime() - sensor.polltime_ >= 15) { // debounce
if (sensor.type() == AnalogType::DIGITAL_IN) {
sensor.set_value(sensor.poll_);
} else if (sensor.type() == AnalogType::COUNTER) {
// capture reading and compare with the last one to see if there is high/low change
if (sensor.poll_ != sensor.last_reading_) {
sensor.last_reading_ = sensor.poll_;
if (!sensor.poll_) {
sensor.set_value(old_value + 1);
}
}
}
// see if there is a change and increment # reads
if (old_value != sensor.value()) {
sensorreads_++;
changed_ = true;
publish_sensor(sensor);
}
}
}
}
}
void AnalogSensor::loop() {
if (!analog_enabled_) {
return;
}
measure(); // take the measurements
}
// update analog information name and offset
bool AnalogSensor::update(uint8_t id, const std::string & name, uint16_t offset, float factor, uint8_t uom, int8_t type) {
boolean found_sensor = false; // see if we can find the sensor in our customization list
EMSESP::webCustomizationService.update(
[&](WebCustomization & settings) {
for (auto & AnalogCustomization : settings.analogCustomizations) {
if (AnalogCustomization.id == id) {
found_sensor = true; // found the record
// see if it's marked for deletion
if (type == AnalogType::MARK_DELETED) {
LOG_DEBUG(F("Removing analog sensor ID %d"), id);
settings.analogCustomizations.remove(AnalogCustomization);
} else {
// update existing record
AnalogCustomization.name = name;
AnalogCustomization.offset = offset;
AnalogCustomization.factor = factor;
AnalogCustomization.uom = uom;
AnalogCustomization.type = type;
LOG_DEBUG(F("Customizing existing analog sensor ID %d"), id);
}
return StateUpdateResult::CHANGED; // persist the change
}
}
return StateUpdateResult::UNCHANGED;
},
"local");
// if the sensor exists and we're using HA, delete the old HA record
if (found_sensor && Mqtt::ha_enabled()) {
remove_ha_topic(id); // id is the GPIO
}
// we didn't find it, it's new, so create and store it
if (!found_sensor) {
EMSESP::webCustomizationService.update(
[&](WebCustomization & settings) {
AnalogCustomization newSensor = AnalogCustomization();
newSensor.id = id;
newSensor.name = name;
newSensor.offset = offset;
newSensor.factor = factor;
newSensor.uom = uom;
newSensor.type = type;
settings.analogCustomizations.push_back(newSensor);
LOG_DEBUG(F("Adding new customization for analog sensor ID %d"), id);
return StateUpdateResult::CHANGED; // persist the change
},
"local");
}
// reloads the sensors in the customizations file into the sensors list
reload();
return true;
}
// check to see if values have been updated
bool AnalogSensor::updated_values() {
if (changed_) {
changed_ = false;
return true;
}
return false;
}
// publish a single sensor to MQTT
void AnalogSensor::publish_sensor(Sensor sensor) {
if (Mqtt::publish_single()) {
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "%s/%s", read_flash_string(F_(analogsensor)).c_str(), sensor.name().c_str());
char payload[10];
Mqtt::publish(topic, Helpers::render_value(payload, sensor.value(), 2)); // always publish as floats
}
}
// send empty config topic to remove the entry from HA
void AnalogSensor::remove_ha_topic(const uint8_t id) {
if (!Mqtt::ha_enabled()) {
return;
}
#ifdef EMSESP_DEBUG
LOG_DEBUG(F("Removing HA config for analog sensor ID %d"), id);
#endif
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "sensor/%s/analogsensor_%d/config", Mqtt::base().c_str(), id);
Mqtt::publish_ha(topic);
}
// send all sensor values as a JSON package to MQTT
void AnalogSensor::publish_values(const bool force) {
uint8_t num_sensors = sensors_.size();
if (num_sensors == 0) {
return;
}
if (force && Mqtt::publish_single()) {
for (const auto & sensor : sensors_) {
publish_sensor(sensor);
}
// return;
}
DynamicJsonDocument doc(120 * num_sensors);
for (auto & sensor : sensors_) {
if (sensor.type() != AnalogType::NOTUSED) {
if (Mqtt::is_nested() || Mqtt::ha_enabled()) {
// nested
char s[10];
JsonObject dataSensor = doc.createNestedObject(Helpers::smallitoa(s, sensor.id()));
dataSensor["name"] = sensor.name();
switch (sensor.type()) {
case AnalogType::COUNTER:
dataSensor["count"] = (uint16_t)sensor.value(); // convert to integer
break;
case AnalogType::ADC:
dataSensor["value"] = (float)sensor.value(); // float
break;
case AnalogType::DIGITAL_IN:
default:
dataSensor["value"] = (uint8_t)sensor.value(); // convert to char for 1 or 0
break;
}
// create HA config
if (Mqtt::ha_enabled()) {
if (!sensor.ha_registered || force) {
LOG_DEBUG(F("Recreating HA config for analog sensor ID %d"), sensor.id());
StaticJsonDocument<EMSESP_JSON_SIZE_MEDIUM> config;
char stat_t[50];
snprintf(stat_t, sizeof(stat_t), "%s/analogsensor_data", Mqtt::base().c_str());
config["stat_t"] = stat_t;
char str[50];
snprintf(str, sizeof(str), "{{value_json['%d'].value}}", sensor.id());
config["val_tpl"] = str;
snprintf(str, sizeof(str), "Analog Sensor %s", sensor.name().c_str());
config["name"] = str;
snprintf(str, sizeof(str), "analogsensor_%d", sensor.id());
config["uniq_id"] = str;
JsonObject dev = config.createNestedObject("dev");
JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp");
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "sensor/%s/analogsensor_%d/config", Mqtt::base().c_str(), sensor.id());
Mqtt::publish_ha(topic, config.as<JsonObject>());
sensor.ha_registered = true;
}
}
} else {
// not nested
doc[sensor.name()] = sensor.value();
}
}
Mqtt::publish(F("analogsensor_data"), doc.as<JsonObject>());
}
}
// called from emsesp.cpp, similar to the emsdevice->get_value_info
// searches by name
bool AnalogSensor::get_value_info(JsonObject & output, const char * cmd, const int8_t id) {
for (const auto & sensor : sensors_) {
if (strcmp(cmd, sensor.name().c_str()) == 0) {
output["id"] = sensor.id();
output["name"] = sensor.name();
output["type"] = sensor.type();
output["uom"] = sensor.uom();
output["offset"] = sensor.offset();
output["factor"] = sensor.factor();
output["value"] = sensor.value();
return true;
}
}
return false;
}
// creates JSON doc from values
// returns false if there are no sensors
bool AnalogSensor::command_info(const char * value, const int8_t id, JsonObject & output) {
if (sensors_.size() == 0) {
return false;
}
for (const auto & sensor : sensors_) {
if (id == -1) { // show number and id
JsonObject dataSensor = output.createNestedObject(sensor.name());
dataSensor["id"] = sensor.id();
dataSensor["name"] = sensor.name();
dataSensor["type"] = sensor.type();
dataSensor["uom"] = sensor.uom();
dataSensor["offset"] = sensor.offset();
dataSensor["factor"] = sensor.factor();
dataSensor["value"] = sensor.value();
} else {
output[sensor.name()] = sensor.value();
}
}
return (output.size() > 0);
}
// this creates the sensor, initializing everything
AnalogSensor::Sensor::Sensor(const uint8_t id, const std::string & name, const uint16_t offset, const float factor, const uint8_t uom, const int8_t type)
: id_(id)
, name_(name)
, offset_(offset)
, factor_(factor)
, uom_(uom)
, type_(type) {
value_ = 0; // init value to 0 always
}
// returns name of the analog sensor or creates one if its empty
std::string AnalogSensor::Sensor::name() const {
if (name_.empty()) {
char name[50];
snprintf(name, sizeof(name), "Analog Sensor GPIO%d", id_);
return name;
}
return name_;
}
// hard coded tests
#ifdef EMSESP_DEBUG
void AnalogSensor::test() {
// Sensor(const uint8_t id, const std::string & name, const uint16_t offset, const float factor, const uint8_t uom, const int8_t type);
sensors_.emplace_back(36, "test12", 0, 0.1, 17, AnalogType::ADC);
sensors_.back().set_value(12.4);
sensors_.emplace_back(37, "test13", 0, 0, 0, AnalogType::DIGITAL_IN);
sensors_.back().set_value(13);
}
#endif
} // namespace emsesp

180
src/analogsensor.h Normal file
View File

@@ -0,0 +1,180 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef EMSESP_ANALOGSENSOR_H
#define EMSESP_ANALOGSENSOR_H
#include "helpers.h"
#include "mqtt.h"
#include "console.h"
#ifndef EMSESP_STANDALONE
#include "driver/adc.h"
#include <esp_bt.h>
#endif
#include <uuid/log.h>
namespace emsesp {
class AnalogSensor {
public:
class Sensor {
public:
Sensor(const uint8_t id, const std::string & name, const uint16_t offset, const float factor, const uint8_t uom, const int8_t type);
~Sensor() = default;
void set_offset(const uint16_t offset) {
offset_ = offset;
}
std::string name() const;
void set_name(const std::string & name) {
name_ = name;
}
uint8_t id() const {
return id_;
}
float value() const {
return value_;
}
void set_value(float value) {
value_ = value;
}
float factor() const {
return factor_;
}
void set_factor(float factor) {
factor_ = factor;
}
uint16_t offset() const {
return offset_;
}
void set_uom(const uint8_t uom) {
uom_ = uom;
}
uint8_t uom() const {
return uom_;
}
int8_t type() const {
return type_;
}
void set_type(int8_t type) {
type_ = type;
}
bool ha_registered = false;
uint16_t analog_ = 0; // ADC - average value
uint32_t sum_ = 0; // ADC - rolling sum
uint16_t last_reading_ = 0; // IO COUNTER & ADC - last reading
uint32_t polltime_ = 0; // digital IO & COUNTER debounce time
int poll_ = 0;
private:
uint8_t id_;
std::string name_;
uint16_t offset_;
float factor_;
uint8_t uom_;
float value_; // float because of the factor is a float
int8_t type_;
};
AnalogSensor() = default;
~AnalogSensor() = default;
enum AnalogType : int8_t {
MARK_DELETED = -1, // mark for deletion
NOTUSED, // 0 - disabled
DIGITAL_IN, // 1
COUNTER, // 2
ADC // 3
};
void start();
void loop();
void publish_sensor(Sensor sensor);
void publish_values(const bool force);
void reload();
bool updated_values();
// return back reference to the sensor list, used by other classes
const std::vector<Sensor> sensors() const {
return sensors_;
}
uint32_t reads() {
return sensorreads_;
}
uint32_t fails() {
return sensorfails_;
}
bool analog_enabled() {
return (analog_enabled_);
}
bool have_sensors() {
return (sensors_.size() > 0);
}
size_t no_sensors() {
return sensors_.size();
}
bool update(uint8_t id, const std::string & name, uint16_t offset, float factor, uint8_t uom, int8_t type);
bool get_value_info(JsonObject & output, const char * cmd, const int8_t id);
#ifdef EMSESP_DEBUG
void test();
#endif
private:
static constexpr uint8_t MAX_SENSORS = 20;
static constexpr uint32_t MEASURE_ANALOG_INTERVAL = 500;
static uuid::log::Logger logger_;
void remove_ha_topic(const uint8_t id);
bool command_counter(const char * value, const int8_t id);
void measure();
bool command_info(const char * value, const int8_t id, JsonObject & output);
std::vector<Sensor> sensors_; // our list of sensors
bool analog_enabled_;
bool changed_ = false;
uint32_t sensorfails_ = 0;
uint32_t sensorreads_ = 0;
};
} // namespace emsesp
#endif

View File

@@ -52,7 +52,9 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
}
}
#if defined(EMSESP_USE_SERIAL)
// Serial.println(p.path().c_str()); // dump paths, for debugging
#endif
// re-calculate new path
// if there is only a path (URL) and no body then error!
@@ -107,7 +109,7 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
command_p = parse_command_string(command_p, id_n);
if (command_p == nullptr) {
// handle dead endpoints like api/system or api/boiler
// default to 'info' for SYSTEM and DALLASENSOR, the other devices to 'values' for shortname version
// default to 'info' for SYSTEM, DALLASENSOR and ANALOGSENSOR, the other devices to 'values' for shortname version
if (num_paths < 3) {
if (device_type < EMSdevice::DeviceType::BOILER) {
command_p = "info";
@@ -126,7 +128,7 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
id_n = input["hc"];
} else if (input.containsKey("wwc")) {
id_n = input["wwc"];
id_n += 7; // wwc1 has id 8
id_n += 8; // wwc1 has id 9
} else if (input.containsKey("id")) {
id_n = input["id"];
}
@@ -146,7 +148,7 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
return_code = Command::call(device_type, command_p, data.as<const char *>(), is_admin, id_n, output);
} else if (data.is<int>()) {
char data_str[10];
return_code = Command::call(device_type, command_p, Helpers::itoa(data_str, (int16_t)data.as<int>()), is_admin, id_n, output);
return_code = Command::call(device_type, command_p, Helpers::itoa((int16_t)data.as<int>(), data_str), is_admin, id_n, output);
} else if (data.is<float>()) {
char data_str[10];
return_code = Command::call(device_type, command_p, Helpers::render_value(data_str, (float)data.as<float>(), 2), is_admin, id_n, output);
@@ -211,7 +213,7 @@ const char * Command::parse_command_string(const char * command, int8_t & id) {
if (!strncmp(command, "hc", 2) && start_pos == 4) {
id = command[start_pos - 2] - '0';
} else if (!strncmp(command, "wwc", 3) && start_pos == 5) {
id = command[start_pos - 2] - '0' + 7; // wwc1 has id 8
id = command[start_pos - 2] - '0' + 8; // wwc1 has id 9
} else {
#if defined(EMSESP_DEBUG)
LOG_DEBUG(F("[DEBUG] Command parse error, unknown hc/wwc in %s"), command_s);
@@ -245,42 +247,48 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char *
// check if its a call to and end-point to a device, i.e. has no value
// except for system commands as this is a special device without any queryable entities (device values)
// exclude SYSTEM and DALLASSENSOR
if ((device_type >= EMSdevice::DeviceType::BOILER) && (!value || !strlen(value))) {
// exclude SYSTEM
if ((device_type > EMSdevice::DeviceType::SYSTEM) && (!value || !strlen(value))) {
if (!cf || !cf->cmdfunction_json_) {
#if defined(EMSESP_DEBUG)
LOG_INFO(F("[DEBUG] Calling %s command '%s' to retrieve values"), dname.c_str(), cmd);
LOG_DEBUG(F("[DEBUG] Calling %s command '%s' to retrieve values"), dname.c_str(), cmd);
#endif
return EMSESP::get_device_value_info(output, cmd, id, device_type) ? CommandRet::OK : CommandRet::ERROR; // entity = cmd
}
}
// check if we have a matching command
if (cf) {
// we have a matching command
if ((value == nullptr) || !strlen(value)) {
LOG_INFO(F("Calling %s command '%s'"), dname.c_str(), cmd);
} else if (id == -1) {
LOG_INFO(F("Calling %s command '%s', value %s, id is default"), dname.c_str(), cmd, value);
} else {
LOG_INFO(F("Calling %s command '%s', value %s, id is %d"), dname.c_str(), cmd, value, id);
}
// check permissions
if (cf->has_flags(CommandFlag::ADMIN_ONLY) && !is_admin) {
output["message"] = "authentication failed";
return CommandRet::NOT_ALLOWED; // command not allowed
}
// call the function
if (value == nullptr) {
if (EMSESP::system_.readonly_mode()) {
LOG_INFO(F("[readonly] Calling command '%s/%s' (%s)"), dname.c_str(), cmd, read_flash_string(cf->description_).c_str());
} else {
LOG_DEBUG(F("Calling command '%s/%s' (%s)"), dname.c_str(), cmd, read_flash_string(cf->description_).c_str());
}
} else {
if (EMSESP::system_.readonly_mode()) {
LOG_INFO(F("[readonly] Calling command '%s/%s' (%s) with value %s"), dname.c_str(), cmd, read_flash_string(cf->description_).c_str(), value);
} else {
LOG_DEBUG(F("Calling command '%s/%s' (%s) with value %s"), dname.c_str(), cmd, read_flash_string(cf->description_).c_str(), value);
}
}
// call the function baesed on type
if (cf->cmdfunction_json_) {
return_code = ((cf->cmdfunction_json_)(value, id, output)) ? CommandRet::OK : CommandRet::ERROR;
}
if (cf->cmdfunction_) {
return_code = ((cf->cmdfunction_)(value, id)) ? CommandRet::OK : CommandRet::ERROR;
}
// report error if call failed
// report back
if (return_code != CommandRet::OK) {
return message(return_code, "callback function failed", output);
}
@@ -417,7 +425,7 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
}
shell.print(COLOR_BRIGHT_CYAN);
if (cf.has_flags(MQTT_SUB_FLAG_WW)) {
shell.print(EMSdevice::tag_to_string(TAG_DEVICE_DATA_WW));
shell.print(EMSdevice::tag_to_string(DeviceValueTAG::TAG_DEVICE_DATA_WW));
shell.print(' ');
}
shell.print(read_flash_string(cf.description_));
@@ -447,7 +455,11 @@ bool Command::device_has_commands(const uint8_t device_type) {
}
if (device_type == EMSdevice::DeviceType::DALLASSENSOR) {
return (EMSESP::sensor_devices().size() != 0);
return (EMSESP::dallassensor_.have_sensors());
}
if (device_type == EMSdevice::DeviceType::ANALOGSENSOR) {
return (EMSESP::analogsensor_.have_sensors());
}
for (const auto & emsdevice : EMSESP::emsdevices) {
@@ -464,12 +476,16 @@ bool Command::device_has_commands(const uint8_t device_type) {
return false;
}
// list sensors and EMS devices
void Command::show_devices(uuid::console::Shell & shell) {
shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::SYSTEM).c_str());
if (EMSESP::have_sensors()) {
if (EMSESP::dallassensor_.have_sensors()) {
shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::DALLASSENSOR).c_str());
}
if (EMSESP::analogsensor_.have_sensors()) {
shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::ANALOGSENSOR).c_str());
}
for (const auto & device_class : EMSFactory::device_handlers()) {
for (const auto & emsdevice : EMSESP::emsdevices) {
@@ -494,14 +510,21 @@ void Command::show_all(uuid::console::Shell & shell) {
shell.print(COLOR_RESET);
show(shell, EMSdevice::DeviceType::SYSTEM, true);
// show sensor
if (EMSESP::have_sensors()) {
// show sensors
if (EMSESP::dallassensor_.have_sensors()) {
shell.print(COLOR_BOLD_ON);
shell.print(COLOR_YELLOW);
shell.printf(" %s: ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::DALLASSENSOR).c_str());
shell.print(COLOR_RESET);
show(shell, EMSdevice::DeviceType::DALLASSENSOR, true);
}
if (EMSESP::analogsensor_.have_sensors()) {
shell.print(COLOR_BOLD_ON);
shell.print(COLOR_YELLOW);
shell.printf(" %s: ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::ANALOGSENSOR).c_str());
shell.print(COLOR_RESET);
show(shell, EMSdevice::DeviceType::ANALOGSENSOR, true);
}
// do this in the order of factory classes to keep a consistent order when displaying
for (const auto & device_class : EMSFactory::device_handlers()) {

View File

@@ -19,18 +19,10 @@
#ifndef EMSESP_COMMAND_H_
#define EMSESP_COMMAND_H_
#include <Arduino.h>
#include <ArduinoJson.h>
#include <string>
#include <vector>
#include <functional>
#include <unordered_map>
#include "console.h"
#include <uuid/log.h>
using uuid::console::Shell;
namespace emsesp {
@@ -38,7 +30,7 @@ namespace emsesp {
// mqtt flags for command subscriptions
enum CommandFlag : uint8_t {
MQTT_SUB_FLAG_DEFAULT = 0, // 0 no flags set, always subscribe to MQTT
MQTT_SUB_FLAG_HC = (1 << 0), // 1 TAG_HC1 - TAG_HC4
MQTT_SUB_FLAG_HC = (1 << 0), // 1 TAG_HC1 - TAG_HC8
MQTT_SUB_FLAG_WWC = (1 << 1), // 2 TAG_WWC1 - TAG_WWC4
MQTT_SUB_FLAG_NOSUB = (1 << 2), // 4
MQTT_SUB_FLAG_WW = (1 << 3), // 8 TAG_DEVICE_DATA_WW

View File

@@ -76,8 +76,9 @@ void EMSESPShell::display_banner() {
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) { console_hostname_ = networkSettings.hostname.c_str(); });
if (console_hostname_.empty()) {
console_hostname_.resize(16, '\0');
snprintf(&console_hostname_[0], console_hostname_.capacity() + 1, "ems-esp");
console_hostname_ = "ems-esp";
// console_hostname_.resize(16, '\0');
// snprintf(&console_hostname_[0], console_hostname_.capacity() + 1, "ems-esp");
}
// load the list of commands
@@ -249,7 +250,7 @@ void EMSESPShell::add_console_commands() {
uint8_t device_id = Helpers::hextoint(arguments.front().c_str());
if (!EMSESP::valid_device(device_id)) {
shell.printfln(F("Invalid device ID"));
shell.printfln(F("Invalid deviceID"));
return;
}
@@ -355,7 +356,7 @@ void EMSESPShell::add_console_commands() {
if (watch_id > 0x80) {
shell.printfln(F("Filtering only telegrams that match a telegram type of 0x%02X"), watch_id);
} else if (watch_id != WATCH_ID_NONE) {
shell.printfln(F("Filtering only telegrams that match a device ID or telegram type of 0x%02X"), watch_id);
shell.printfln(F("Filtering only telegrams that match a deviceID or telegram type of 0x%02X"), watch_id);
}
});
@@ -433,6 +434,7 @@ void EMSESPShell::add_console_commands() {
std::vector<std::string> devices_list;
devices_list.emplace_back(EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::SYSTEM));
devices_list.emplace_back(EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::DALLASSENSOR));
devices_list.emplace_back(EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::ANALOGSENSOR));
for (const auto & device_class : EMSFactory::device_handlers()) {
if (Command::device_has_commands(device_class.first)) {
devices_list.emplace_back(EMSdevice::device_type_2_device_name(device_class.first));
@@ -491,20 +493,21 @@ void Console::enter_custom_context(Shell & shell, unsigned int context) {
// each custom context has the common commands like log, help, exit, su etc
void Console::load_standard_commands(unsigned int context) {
#if defined(EMSESP_DEBUG)
// create commands test and t
EMSESPShell::commands->add_command(context,
CommandFlags::USER,
flash_string_vector{F("test")},
flash_string_vector{F_(name_optional)},
flash_string_vector{F_(name_optional), F_(data_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.size() == 0) {
Test::run_test(shell, "default");
} else {
} else if (arguments.size() == 1) {
Test::run_test(shell, arguments.front());
} else {
Test::run_test(shell, arguments[0].c_str(), arguments[1].c_str());
}
});
#endif
#if defined(EMSESP_STANDALONE)
EMSESPShell::commands->add_command(context, CommandFlags::USER, flash_string_vector{F("t")}, [](Shell & shell, const std::vector<std::string> & arguments) {
Test::run_test(shell, "default");
});
@@ -696,6 +699,16 @@ void Console::load_system_commands(unsigned int context) {
shell.println("Use `wifi reconnect` to save and apply the new settings");
});
// added by mvdp
EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN,
flash_string_vector{F("mqtt"), F("subscribe")},
flash_string_vector{F("<topic>")},
[](Shell & shell, const std::vector<std::string> & arguments) {
Mqtt::subscribe(arguments.front());
shell.println("subscribing");
});
EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(wifi), F_(password)},
@@ -723,72 +736,33 @@ void Console::load_system_commands(unsigned int context) {
EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN,
flash_string_vector{F_(sensorname)},
flash_string_vector{F_(sensorid_optional), F_(name_optional), F_(offset_optional)},
flash_string_vector{F_(set), F_(board_profile)},
flash_string_vector{F_(name_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.size() == 0) {
EMSESP::webSettingsService.read([&](WebSettings & settings) {
for (uint8_t i = 0; i < MAX_NUM_SENSOR_NAMES; i++) {
if (!settings.sensor[i].id.isEmpty()) {
shell.print(settings.sensor[i].id);
shell.print(" : ");
shell.print(settings.sensor[i].name);
shell.print(" : ");
char buf[10];
shell.println(Helpers::render_value(buf, settings.sensor[i].offset, 10));
}
}
});
std::vector<int8_t> data; // led, dallas, rx, tx, button, phy_type, eth_power, eth_phy_addr, eth_clock_mode
std::string board_profile = Helpers::toUpper(arguments.front());
if (!EMSESP::system_.load_board_profile(data, board_profile)) {
shell.println(F("Invalid board profile (S32, E32, MH-ET, NODEMCU, OLIMEX, OLIMEXPOE, CUSTOM)"));
return;
}
if (arguments.size() == 1) {
EMSESP::dallassensor_.update(arguments.front().c_str(), "", 0);
// shell.println(EMSESP::dallassensor_.get_name(arguments.front().c_str()));
return;
}
int16_t offset = 0;
float val;
if (arguments.size() == 2) {
if (Helpers::value2float(arguments.back().c_str(), val)) {
offset = (10 * val);
EMSESP::dallassensor_.update(arguments.front().c_str(), "", offset);
return;
}
} else if (arguments.size() == 3) {
if (Helpers::value2float(arguments.back().c_str(), val)) {
offset = (10 * val);
}
}
EMSESP::dallassensor_.update(arguments.front().c_str(), arguments[1].c_str(), offset);
EMSESP::webSettingsService.update(
[&](WebSettings & settings) {
settings.board_profile = board_profile.c_str();
settings.led_gpio = data[0];
settings.dallas_gpio = data[1];
settings.rx_gpio = data[2];
settings.tx_gpio = data[3];
settings.pbutton_gpio = data[4];
settings.phy_type = data[5];
settings.eth_power = data[6]; // can be -1
settings.eth_phy_addr = data[7];
settings.eth_clock_mode = data[8];
return StateUpdateResult::CHANGED;
},
"local");
shell.printfln("Loaded board profile %s", board_profile.c_str());
EMSESP::system_.network_init(true);
});
EMSESPShell::commands->add_command(
context,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(board_profile)},
flash_string_vector{F_(name_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
std::vector<uint8_t> data; // led, dallas, rx, tx, button
std::string board_profile = Helpers::toUpper(arguments.front());
if (!EMSESP::system_.load_board_profile(data, board_profile)) {
shell.println(F("Invalid board profile (S32, E32, MH-ET, NODEMCU, OLIMEX, CUSTOM)"));
return;
}
EMSESP::webSettingsService.update(
[&](WebSettings & settings) {
settings.board_profile = board_profile.c_str();
settings.led_gpio = data[0];
settings.dallas_gpio = data[1];
settings.rx_gpio = data[2];
settings.tx_gpio = data[3];
settings.pbutton_gpio = data[4];
settings.phy_type = data[5];
return StateUpdateResult::CHANGED;
},
"local");
shell.printfln("Loaded board profile %s (%d,%d,%d,%d,%d,%d)", board_profile.c_str(), data[0], data[1], data[2], data[3], data[4], data[5]);
EMSESP::system_.network_init(true);
});
EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN,
flash_string_vector{F_(show), F_(users)},
@@ -853,7 +827,7 @@ EMSESPStreamConsole::EMSESPStreamConsole(Stream & stream, const IPAddress & addr
ptys_[pty_] = true;
}
snprintf(text.data(), text.size(), "pty%u", pty_);
snprintf(text.data(), text.size(), "pty%u", (uint16_t)pty_);
name_ = text.data();
#ifndef EMSESP_STANDALONE
logger().info(F("Allocated console %s for connection from [%s]:%u"), name_.c_str(), uuid::printable_to_string(addr_).c_str(), port_);
@@ -876,9 +850,12 @@ std::string EMSESPStreamConsole::console_name() {
// Start up telnet and logging
// Log order is off, err, warning, notice, info, debug, trace, all
void Console::start() {
void Console::start(bool telnet_enabled) {
telnet_enabled_ = telnet_enabled;
// Serial Console
shell = std::make_shared<EMSESPStreamConsole>(Serial, true);
shell->maximum_log_messages(100); // default was 50
shell->maximum_log_messages(100);
shell->start();
#if defined(EMSESP_DEBUG)
@@ -889,13 +866,15 @@ void Console::start() {
shell->add_flags(CommandFlags::ADMIN); // always start in su/admin mode when running tests
#endif
// start the telnet service
// default idle is 10 minutes, default write timeout is 0 (automatic)
// note, this must be started after the network/wifi for ESP32 otherwise it'll crash
// start the telnet service
// default idle is 10 minutes, default write timeout is 0 (automatic)
// note, this must be started after the network/wifi for ESP32 otherwise it'll crash
#ifndef EMSESP_STANDALONE
telnet_.start();
telnet_.initial_idle_timeout(3600); // in sec, one hour idle timeout
telnet_.default_write_timeout(1000); // in ms, socket timeout 1 second
if (telnet_enabled) {
telnet_.start();
telnet_.initial_idle_timeout(3600); // in sec, one hour idle timeout
telnet_.default_write_timeout(1000); // in ms, socket timeout 1 second
}
#endif
// turn watch off in case it was still set in the last session
@@ -907,7 +886,9 @@ void Console::loop() {
uuid::loop();
#ifndef EMSESP_STANDALONE
telnet_.loop();
if (telnet_enabled_) {
telnet_.loop();
}
#endif
Shell::loop_all();

View File

@@ -19,11 +19,6 @@
#ifndef EMSESP_CONSOLE_H
#define EMSESP_CONSOLE_H
#include <Arduino.h>
#include <string>
#include <vector>
#include <uuid/console.h>
#include <uuid/log.h>
@@ -101,9 +96,10 @@ class EMSESPShell : virtual public uuid::console::Shell {
void stopped() override;
void display_banner() override;
std::string hostname_text() override;
// std::string context_text() override;
std::string prompt_suffix() override;
void end_of_transmission() override;
// std::string context_text() override;
// bool exit_context() override;
private:
@@ -132,13 +128,16 @@ class EMSESPStreamConsole : public uuid::console::StreamConsole, public EMSESPSh
class Console {
public:
void loop();
void start();
void start(bool telnet_enabled = true);
uuid::log::Level log_level();
// static void enter_custom_context(Shell & shell, unsigned int context);
static void load_standard_commands(unsigned int context);
static void load_system_commands(unsigned int context);
private:
bool telnet_enabled_;
};
} // namespace emsesp

View File

@@ -35,36 +35,38 @@ uuid::log::Logger DallasSensor::logger_{F_(dallassensor), uuid::log::Facility::D
void DallasSensor::start() {
reload();
// disabled if dallas gpio is 0
if (dallas_gpio_) {
#ifndef EMSESP_STANDALONE
bus_.begin(dallas_gpio_);
#endif
// API calls
Command::add(
EMSdevice::DeviceType::DALLASSENSOR,
F_(info),
[&](const char * value, const int8_t id, JsonObject & output) { return command_info(value, id, output); },
F_(info_cmd));
Command::add(
EMSdevice::DeviceType::DALLASSENSOR,
F_(commands),
[&](const char * value, const int8_t id, JsonObject & output) { return command_commands(value, id, output); },
F_(commands_cmd));
if (!dallas_gpio_) {
return; // disabled if dallas gpio is 0
}
#ifndef EMSESP_STANDALONE
bus_.begin(dallas_gpio_);
LOG_INFO(F("Starting Dallas sensor service"));
#endif
// Add API calls
Command::add(
EMSdevice::DeviceType::DALLASSENSOR,
F_(info),
[&](const char * value, const int8_t id, JsonObject & output) { return command_info(value, id, output); },
F_(info_cmd));
Command::add(
EMSdevice::DeviceType::DALLASSENSOR,
F_(commands),
[&](const char * value, const int8_t id, JsonObject & output) { return command_commands(value, id, output); },
F_(commands_cmd));
}
// load the MQTT settings
// load settings
void DallasSensor::reload() {
// load the service settings
EMSESP::webSettingsService.read([&](WebSettings & settings) {
dallas_gpio_ = settings.dallas_gpio;
parasite_ = settings.dallas_parasite;
dallas_format_ = settings.dallas_format;
dallas_gpio_ = settings.dallas_gpio;
parasite_ = settings.dallas_parasite;
});
if (Mqtt::ha_enabled()) {
for (uint8_t i = 0; i < MAX_SENSORS; registered_ha_[i++] = false)
;
for (auto & sensor : sensors_) {
sensor.ha_registered = false; // force HA configs to be re-created
}
}
@@ -93,7 +95,9 @@ void DallasSensor::loop() {
sensorfails_++;
if (++scanretry_ > SCAN_MAX) { // every 30 sec
scanretry_ = 0;
#ifdef EMSESP_DEBUG_SENSOR
LOG_ERROR(F("Bus reset failed"));
#endif
for (auto & sensor : sensors_) {
sensor.temperature_c = EMS_VALUE_SHORT_NOTSET;
}
@@ -110,13 +114,17 @@ void DallasSensor::loop() {
bus_.reset_search();
state_ = State::SCANNING;
} else if (time_now - last_activity_ > READ_TIMEOUT_MS) {
#ifdef EMSESP_DEBUG_SENSOR
LOG_WARNING(F("Dallas sensor read timeout"));
#endif
state_ = State::IDLE;
sensorfails_++;
}
} else if (state_ == State::SCANNING) {
if (time_now - last_activity_ > SCAN_TIMEOUT_MS) {
#ifdef EMSESP_DEBUG_SENSOR
LOG_ERROR(F("Dallas sensor scan timeout"));
#endif
state_ = State::IDLE;
sensorfails_++;
} else {
@@ -136,12 +144,13 @@ void DallasSensor::loop() {
t = get_temperature_c(addr);
if ((t >= -550) && (t <= 1250)) {
sensorreads_++;
// check if we have this sensor already
// check if we already have this sensor
bool found = false;
for (auto & sensor : sensors_) {
if (sensor.id() == get_id(addr)) {
t += sensor.offset();
if (t != sensor.temperature_c) {
publish_sensor(sensor);
changed_ |= true;
}
sensor.temperature_c = t;
@@ -150,12 +159,17 @@ void DallasSensor::loop() {
break;
}
}
// add new sensor
// add new sensor. this will create the id string, empty name and offset
if (!found && (sensors_.size() < (MAX_SENSORS - 1))) {
sensors_.emplace_back(addr);
sensors_.back().temperature_c = t + sensors_.back().offset();
sensors_.back().read = true;
changed_ = true;
// look in the customization service for an optional alias or offset for that particular sensor
sensors_.back().apply_customization();
publish_sensor(sensors_.back()); // call publish single
// sort the sensors based on name
std::sort(sensors_.begin(), sensors_.end(), [](const Sensor & a, const Sensor & b) { return a.name() < b.name(); });
}
} else {
sensorfails_++;
@@ -164,12 +178,12 @@ void DallasSensor::loop() {
default:
sensorfails_++;
LOG_ERROR(F("Unknown dallas sensor %s"), Sensor(addr).to_string().c_str());
LOG_ERROR(F("Unknown dallas sensor %s"), Sensor(addr).id_str().c_str());
break;
}
} else {
sensorfails_++;
LOG_ERROR(F("Invalid dallas sensor %s"), Sensor(addr).to_string().c_str());
LOG_ERROR(F("Invalid dallas sensor %s"), Sensor(addr).id_str().c_str());
}
} else {
if (!parasite_) {
@@ -187,7 +201,7 @@ void DallasSensor::loop() {
scancnt_ = 0;
} else if (scancnt_ == SCAN_START + 1) { // startup
firstscan_ = sensors_.size();
LOG_DEBUG(F("Adding %d dallas sensor(s) from first scan"), firstscan_);
// LOG_DEBUG(F("Adding %d dallas sensor(s) from first scan"), firstscan_);
} else if ((scancnt_ <= 0) && (firstscan_ != sensors_.size())) { // check 2 times for no change of sensor #
scancnt_ = SCAN_START;
sensors_.clear(); // restart scaning and clear to get correct numbering
@@ -213,7 +227,7 @@ bool DallasSensor::temperature_convert_complete() {
int16_t DallasSensor::get_temperature_c(const uint8_t addr[]) {
#ifndef EMSESP_STANDALONE
if (!bus_.reset()) {
LOG_ERROR(F("Bus reset failed before reading scratchpad from %s"), Sensor(addr).to_string().c_str());
LOG_ERROR(F("Bus reset failed before reading scratchpad from %s"), Sensor(addr).id_str().c_str());
return EMS_VALUE_SHORT_NOTSET;
}
YIELD;
@@ -225,7 +239,7 @@ int16_t DallasSensor::get_temperature_c(const uint8_t addr[]) {
YIELD;
if (!bus_.reset()) {
LOG_ERROR(F("Bus reset failed after reading scratchpad from %s"), Sensor(addr).to_string().c_str());
LOG_ERROR(F("Bus reset failed after reading scratchpad from %s"), Sensor(addr).id_str().c_str());
return EMS_VALUE_SHORT_NOTSET;
}
YIELD;
@@ -241,7 +255,7 @@ int16_t DallasSensor::get_temperature_c(const uint8_t addr[]) {
scratchpad[6],
scratchpad[7],
scratchpad[8],
Sensor(addr).to_string().c_str());
Sensor(addr).id_str().c_str());
return EMS_VALUE_SHORT_NOTSET;
}
@@ -273,162 +287,52 @@ int16_t DallasSensor::get_temperature_c(const uint8_t addr[]) {
#endif
}
const std::vector<DallasSensor::Sensor> DallasSensor::sensors() const {
return sensors_;
}
// update dallas information name and offset
bool DallasSensor::update(const std::string & id_str, const std::string & name, int16_t offset) {
// find the sensor
for (auto & sensor : sensors_) {
if (sensor.id_str() == id_str) {
// found a match, update the sensor object
// skip crc from id.
DallasSensor::Sensor::Sensor(const uint8_t addr[])
: id_(((uint64_t)addr[0] << 48) | ((uint64_t)addr[1] << 40) | ((uint64_t)addr[2] << 32) | ((uint64_t)addr[3] << 24) | ((uint64_t)addr[4] << 16)
| ((uint64_t)addr[5] << 8) | ((uint64_t)addr[6])) {
}
uint64_t DallasSensor::get_id(const uint8_t addr[]) {
return (((uint64_t)addr[0] << 48) | ((uint64_t)addr[1] << 40) | ((uint64_t)addr[2] << 32) | ((uint64_t)addr[3] << 24) | ((uint64_t)addr[4] << 16)
| ((uint64_t)addr[5] << 8) | ((uint64_t)addr[6]));
}
uint64_t DallasSensor::Sensor::id() const {
return id_;
}
std::string DallasSensor::Sensor::id_string() const {
std::string str(20, '\0');
snprintf(&str[0],
str.capacity() + 1,
"%02X-%04X-%04X-%04X",
(unsigned int)(id_ >> 48) & 0xFF,
(unsigned int)(id_ >> 32) & 0xFFFF,
(unsigned int)(id_ >> 16) & 0xFFFF,
(unsigned int)(id_)&0xFFFF);
return str;
}
std::string DallasSensor::Sensor::to_string(const bool name) const {
std::string str = id_string();
EMSESP::webSettingsService.read([&](WebSettings & settings) {
if (settings.dallas_format == Dallas_Format::NAME || name) {
for (uint8_t i = 0; i < MAX_NUM_SENSOR_NAMES; i++) {
if (strcmp(settings.sensor[i].id.c_str(), str.c_str()) == 0) {
str = settings.sensor[i].name.c_str();
}
// if HA is enabled then delete the old record
if (Mqtt::ha_enabled()) {
remove_ha_topic(id_str);
}
}
});
return str;
}
sensor.set_name(name);
sensor.set_offset(offset);
int16_t DallasSensor::Sensor::offset() const {
std::string str = id_string();
int16_t offset = 0; // default value
EMSESP::webSettingsService.read([&](WebSettings & settings) {
for (uint8_t i = 0; i < MAX_NUM_SENSOR_NAMES; i++) {
if (strcmp(settings.sensor[i].id.c_str(), str.c_str()) == 0) {
offset = settings.sensor[i].offset;
}
}
});
return offset;
}
// if HA enabled with MQTT Discovery, delete the old config entry by sending an empty topic
// if we're using the name in the MQTT topic name (Dallas format = NAME)
void DallasSensor::delete_ha_config(uint8_t index, const char * name) {
if (Mqtt::ha_enabled() && (dallas_format_ == Dallas_Format::NAME)) {
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
// use '_' as HA doesn't like '-' in the topic name
std::string topicname = name;
std::replace(topicname.begin(), topicname.end(), '-', '_');
snprintf(topic, sizeof(topic), "homeassistant/sensor/%s/dallassensor_%s/config", Mqtt::base().c_str(), topicname.c_str());
Mqtt::publish(topic);
registered_ha_[index] = false; // forces a recreate of the HA config topic
}
}
// update dallas information like name and offset
bool DallasSensor::update(const char * idstr, const char * name, int16_t offset) {
bool ok = false;
char id[20];
strlcpy(id, idstr, sizeof(id));
// check for number and convert to id
if (strlen(id) > 0 && strlen(id) <= 2 && id[0] >= '1' && id[0] <= '9') {
uint8_t no = atoi(idstr) - 1;
if (no < sensors_.size()) {
strlcpy(id, sensors_[no].id_string().c_str(), sizeof(id));
}
}
// check valid id
if (strlen(id) != 17 || id[2] != '-' || id[7] != '-' || id[12] != '-') {
LOG_WARNING(F("Invalid sensor id: %s"), id);
return ok;
}
EMSESP::webSettingsService.update(
[&](WebSettings & settings) {
// check for new name of stored id
for (uint8_t i = 0; i < MAX_NUM_SENSOR_NAMES; i++) {
if (strcmp(id, settings.sensor[i].id.c_str()) == 0) {
if (strlen(name) == 0 && offset == 0) { // delete entry if name and offset is empty
LOG_INFO(F("Deleting entry for sensor %s"), id);
delete_ha_config(i, settings.sensor[i].name.c_str());
settings.sensor[i].id = "";
settings.sensor[i].name = "";
settings.sensor[i].offset = 0;
} else {
char result[10];
LOG_INFO(F("Renaming sensor ID %s to %s with offset %s"), id, name, Helpers::render_value(result, offset, 10));
delete_ha_config(i, settings.sensor[i].name.c_str()); // remove old name in HA
settings.sensor[i].name = (strlen(name) == 0) ? id : name;
settings.sensor[i].offset = offset;
// store the new name and offset in our configuration
EMSESP::webCustomizationService.update(
[&](WebCustomization & settings) {
// look it up to see if it exists
bool found = false;
for (auto & SensorCustomization : settings.sensorCustomizations) {
if (SensorCustomization.id_str == id_str) {
SensorCustomization.name = name;
SensorCustomization.offset = offset;
found = true;
LOG_DEBUG(F("Customizing existing sensor ID %s"), id_str.c_str());
break;
}
}
ok = true;
return StateUpdateResult::CHANGED;
}
}
// check for free place
for (uint8_t i = 0; i < MAX_NUM_SENSOR_NAMES; i++) {
if (settings.sensor[i].id.isEmpty()) {
settings.sensor[i].id = id;
settings.sensor[i].name = (strlen(name) == 0) ? id : name;
settings.sensor[i].offset = offset;
char result[10];
LOG_INFO(F("Adding sensor ID %s to %s with offset %s"), id, name, Helpers::render_value(result, offset, 10));
ok = true;
return StateUpdateResult::CHANGED;
}
}
// check if there is a unused id and overwrite it
for (uint8_t i = 0; i < MAX_NUM_SENSOR_NAMES; i++) {
bool found = false;
for (const auto & sensor : sensors_) {
if (strcmp(sensor.id_string().c_str(), settings.sensor[i].id.c_str()) == 0) {
found = true;
if (!found) {
SensorCustomization newSensor = SensorCustomization();
newSensor.id_str = id_str;
newSensor.name = name;
newSensor.offset = offset;
settings.sensorCustomizations.push_back(newSensor);
LOG_DEBUG(F("Adding new customization for sensor ID %s"), id_str.c_str());
}
}
if (!found) {
char result[10];
LOG_INFO(F("Renaming sensor ID %s to %s with offset %s"), id, name, Helpers::render_value(result, offset, 10));
delete_ha_config(i, settings.sensor[i].name.c_str()); // remove old name in HA
settings.sensor[i].id = id;
settings.sensor[i].name = (strlen(name) == 0) ? id : name;
settings.sensor[i].offset = offset;
ok = true;
sensor.ha_registered = false; // it's changed so we may need to recreate the HA config
return StateUpdateResult::CHANGED;
}
}
},
"local");
return true;
}
}
LOG_ERROR(F("No more empty sensor slots, remove one first"));
return StateUpdateResult::UNCHANGED;
},
"local");
return ok;
return true; // not found, nothing updated
}
// check to see if values have been updated
@@ -446,35 +350,73 @@ bool DallasSensor::command_commands(const char * value, const int8_t id, JsonObj
}
// creates JSON doc from values
// returns false if empty
// e.g. dallassensor_data = {"sensor1":{"id":"28-EA41-9497-0E03-5F","temp":23.30},"sensor2":{"id":"28-233D-9497-0C03-8B","temp":24.0}}
// returns false if there are no sensors
bool DallasSensor::command_info(const char * value, const int8_t id, JsonObject & output) {
if (sensors_.size() == 0) {
return false;
}
uint8_t i = 1; // sensor count
for (const auto & sensor : sensors_) {
char sensorID[10]; // sensor{1-n}
snprintf(sensorID, 10, "sensor%d", i++);
if (id == -1) { // show number and id
JsonObject dataSensor = output.createNestedObject(sensorID);
dataSensor["id"] = sensor.to_string();
JsonObject dataSensor = output.createNestedObject(sensor.name());
dataSensor["id_str"] = sensor.id_str();
if (Helpers::hasValue(sensor.temperature_c)) {
dataSensor["temp"] = (float)(sensor.temperature_c) / 10;
}
} else { // show according to format
if (dallas_format_ == Dallas_Format::NUMBER && Helpers::hasValue(sensor.temperature_c)) {
output[sensorID] = (float)(sensor.temperature_c) / 10;
} else if (Helpers::hasValue(sensor.temperature_c)) {
output[sensor.to_string()] = (float)(sensor.temperature_c) / 10;
}
} else if (Helpers::hasValue(sensor.temperature_c)) {
output[sensor.name()] = (float)(sensor.temperature_c) / 10;
}
}
return (output.size() > 0);
}
// called from emsesp.cpp, similar to the emsdevice->get_value_info
bool DallasSensor::get_value_info(JsonObject & output, const char * cmd, const int8_t id) {
for (const auto & sensor : sensors_) {
if (strcmp(cmd, sensor.name().c_str()) == 0) {
output["id_str"] = sensor.id_str();
output["name"] = sensor.name();
if (Helpers::hasValue(sensor.temperature_c)) {
output["value"] = (float)(sensor.temperature_c) / 10;
}
output["type"] = F_(number);
output["min"] = -55;
output["max"] = 125;
output["unit"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES);
output["writeable"] = false;
return true;
}
}
return false;
}
// publish a single sensor to MQTT
void DallasSensor::publish_sensor(const Sensor & sensor) {
if (Mqtt::publish_single()) {
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "%s/%s", read_flash_string(F_(dallassensor)).c_str(), sensor.name().c_str());
char payload[10];
Mqtt::publish(topic, Helpers::render_value(payload, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0));
}
}
// send empty config topic to remove the entry from HA
void DallasSensor::remove_ha_topic(const std::string & id_str) {
if (!Mqtt::ha_enabled()) {
return;
}
#ifdef EMSESP_DEBUG
LOG_DEBUG(F("Removing HA config for sensor ID %s"), id_str.c_str());
#endif
// use '_' as HA doesn't like '-' in the topic name
std::string sensorid = id_str;
std::replace(sensorid.begin(), sensorid.end(), '-', '_');
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "sensor/%s/dallassensor_%s/config", Mqtt::base().c_str(), sensorid.c_str());
Mqtt::publish_ha(topic);
}
// send all dallas sensor values as a JSON package to MQTT
void DallasSensor::publish_values(const bool force) {
uint8_t num_sensors = sensors_.size();
@@ -483,27 +425,33 @@ void DallasSensor::publish_values(const bool force) {
return;
}
DynamicJsonDocument doc(100 * num_sensors);
uint8_t sensor_no = 1;
if (force && Mqtt::publish_single()) {
for (const auto & sensor : sensors_) {
publish_sensor(sensor);
}
// return;
}
for (const auto & sensor : sensors_) {
char sensorID[10]; // sensor{1-n}
snprintf(sensorID, 10, "sensor%d", sensor_no);
if (dallas_format_ == Dallas_Format::NUMBER) {
// e.g. dallassensor_data = {"sensor1":{"id":"28-EA41-9497-0E03","temp":23.3},"sensor2":{"id":"28-233D-9497-0C03","temp":24.0}}
JsonObject dataSensor = doc.createNestedObject(sensorID);
dataSensor["id"] = sensor.to_string();
if (Helpers::hasValue(sensor.temperature_c)) {
DynamicJsonDocument doc(120 * num_sensors);
for (auto & sensor : sensors_) {
bool has_value = Helpers::hasValue(sensor.temperature_c);
if (Mqtt::is_nested() || Mqtt::ha_enabled()) {
JsonObject dataSensor = doc.createNestedObject(sensor.id_str());
dataSensor["name"] = sensor.name();
if (has_value) {
dataSensor["temp"] = (float)(sensor.temperature_c) / 10;
}
} else if (Helpers::hasValue(sensor.temperature_c)) {
doc[sensor.to_string()] = (float)(sensor.temperature_c) / 10;
} else if (has_value) {
doc[sensor.name()] = (float)(sensor.temperature_c) / 10;
}
// create the HA MQTT config
// to e.g. homeassistant/sensor/ems-esp/dallassensor_28-233D-9497-0C03/config
if (Mqtt::ha_enabled()) {
if (!(registered_ha_[sensor_no - 1]) || force) {
if (!sensor.ha_registered || force) {
LOG_DEBUG(F("Recreating HA config for sensor ID %s"), sensor.id_str().c_str());
StaticJsonDocument<EMSESP_JSON_SIZE_MEDIUM> config;
config["dev_cla"] = FJSON("temperature");
@@ -511,25 +459,20 @@ void DallasSensor::publish_values(const bool force) {
snprintf(stat_t, sizeof(stat_t), "%s/dallassensor_data", Mqtt::base().c_str());
config["stat_t"] = stat_t;
config["unit_of_meas"] = FJSON("°C");
if (EMSESP::system_.fahrenheit()) {
config["unit_of_meas"] = FJSON("°F");
} else {
config["unit_of_meas"] = FJSON("°C");
}
char str[50];
if (dallas_format_ != Dallas_Format::NUMBER) {
snprintf(str, sizeof(str), "{{value_json['%s']}}", sensor.to_string().c_str());
} else {
snprintf(str, sizeof(str), "{{value_json.sensor%d.temp}}", sensor_no);
}
snprintf(str, sizeof(str), "{{value_json['%s'].temp}}", sensor.id_str().c_str());
config["val_tpl"] = str;
// name as sensor number not the long unique ID
if (dallas_format_ != Dallas_Format::NUMBER) {
snprintf(str, sizeof(str), "Dallas Sensor %s", sensor.to_string().c_str());
} else {
snprintf(str, sizeof(str), "Dallas Sensor %d", sensor_no);
}
snprintf(str, sizeof(str), "Temperature Sensor %s", sensor.name().c_str());
config["name"] = str;
snprintf(str, sizeof(str), "dallasensor_%s", sensor.to_string().c_str());
snprintf(str, sizeof(str), "dallasensor_%s", sensor.id_str().c_str());
config["uniq_id"] = str;
JsonObject dev = config.createNestedObject("dev");
@@ -537,23 +480,94 @@ void DallasSensor::publish_values(const bool force) {
ids.add("ems-esp");
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
if (dallas_format_ == Dallas_Format::NUMBER) {
snprintf(topic, sizeof(topic), "sensor/%s/dallassensor_%d/config", Mqtt::base().c_str(), sensor_no);
} else {
// use '_' as HA doesn't like '-' in the topic name
std::string topicname = sensor.to_string();
std::replace(topicname.begin(), topicname.end(), '-', '_');
snprintf(topic, sizeof(topic), "sensor/%s/dallassensor_%s/config", Mqtt::base().c_str(), topicname.c_str());
}
// use '_' as HA doesn't like '-' in the topic name
std::string sensorid = sensor.id_str();
std::replace(sensorid.begin(), sensorid.end(), '-', '_');
snprintf(topic, sizeof(topic), "sensor/%s/dallassensor_%s/config", Mqtt::base().c_str(), sensorid.c_str());
Mqtt::publish_ha(topic, config.as<JsonObject>());
registered_ha_[sensor_no - 1] = true;
sensor.ha_registered = true;
}
}
sensor_no++; // increment sensor count
}
Mqtt::publish(F("dallassensor_data"), doc.as<JsonObject>());
}
// skip crc from id
DallasSensor::Sensor::Sensor(const uint8_t addr[])
: id_(((uint64_t)addr[0] << 48) | ((uint64_t)addr[1] << 40) | ((uint64_t)addr[2] << 32) | ((uint64_t)addr[3] << 24) | ((uint64_t)addr[4] << 16)
| ((uint64_t)addr[5] << 8) | ((uint64_t)addr[6])) {
// create ID string
char id[20];
snprintf(id,
sizeof(id),
"%02X-%04X-%04X-%04X",
(unsigned int)(id_ >> 48) & 0xFF,
(unsigned int)(id_ >> 32) & 0xFFFF,
(unsigned int)(id_ >> 16) & 0xFFFF,
(unsigned int)(id_)&0xFFFF);
id_str_ = std::string(id);
name_ = std::string{}; // name (alias) is empty
offset_ = 0; // 0 degrees offset
}
uint64_t DallasSensor::get_id(const uint8_t addr[]) {
return (((uint64_t)addr[0] << 48) | ((uint64_t)addr[1] << 40) | ((uint64_t)addr[2] << 32) | ((uint64_t)addr[3] << 24) | ((uint64_t)addr[4] << 16)
| ((uint64_t)addr[5] << 8) | ((uint64_t)addr[6]));
}
// find the name from the customization service
// if empty, return the ID as a string
std::string DallasSensor::Sensor::name() const {
if (name_.empty()) {
return id_str_;
}
return name_;
}
// look up in customization service for a specific sensor
// and set the name and offset from that entry if it exists
bool DallasSensor::Sensor::apply_customization() {
EMSESP::webCustomizationService.read([&](WebCustomization & settings) {
auto sensors = settings.sensorCustomizations;
if (sensors.size() != 0) {
for (auto & sensor : sensors) {
#if defined(EMSESP_DEBUG)
LOG_DEBUG(F("Loading customization for dallas sensor %s"), sensor.id_str.c_str());
#endif
if (id_str_ == sensor.id_str) {
set_name(sensor.name);
set_offset(sensor.offset);
return true;
}
}
}
return false;
});
return false; // not found, will use default ID as name and 0 for offset
}
// hard coded tests
#ifdef EMSESP_DEBUG
void DallasSensor::test() {
// add 2 dallas sensors
uint8_t addr[ADDR_LEN] = {1, 2, 3, 4, 5, 6, 7, 8};
sensors_.emplace_back(addr);
sensors_.back().temperature_c = 123;
sensors_.back().read = true;
sensors_.back().apply_customization();
uint8_t addr2[ADDR_LEN] = {11, 12, 13, 14, 15, 16, 17, 18};
sensors_.emplace_back(addr2);
sensors_.back().temperature_c = 456;
sensors_.back().read = true;
sensors_.back().apply_customization();
}
#endif
} // namespace emsesp

View File

@@ -21,9 +21,6 @@
#ifndef EMSESP_DALLASSENSOR_H
#define EMSESP_DALLASSENSOR_H
#include <string>
#include <vector>
#include "helpers.h"
#include "mqtt.h"
#include "console.h"
@@ -36,8 +33,6 @@
namespace emsesp {
enum Dallas_Format : uint8_t { SENSORID = 1, NUMBER, NAME };
class DallasSensor {
public:
class Sensor {
@@ -45,16 +40,37 @@ class DallasSensor {
Sensor(const uint8_t addr[]);
~Sensor() = default;
uint64_t id() const;
std::string id_string() const;
std::string to_string(const bool name = false) const;
int16_t offset() const;
uint64_t id() const {
return id_;
}
std::string id_str() const {
return id_str_;
}
int16_t offset() const {
return offset_;
}
void set_offset(const int16_t offset) {
offset_ = offset;
}
std::string name() const;
void set_name(const std::string & name) {
name_ = name;
}
bool apply_customization();
int16_t temperature_c = EMS_VALUE_SHORT_NOTSET;
bool read = false;
bool ha_registered = false;
private:
const uint64_t id_;
uint64_t id_;
std::string id_str_;
std::string name_;
int16_t offset_;
};
DallasSensor() = default;
@@ -62,11 +78,16 @@ class DallasSensor {
void start();
void loop();
void publish_sensor(const Sensor & sensor);
void publish_values(const bool force);
void reload();
bool updated_values();
bool get_value_info(JsonObject & output, const char * cmd, const int8_t id);
const std::vector<Sensor> sensors() const;
// return back reference to the sensor list, used by other classes
const std::vector<Sensor> sensors() const {
return sensors_;
}
uint32_t reads() {
return sensorreads_;
@@ -80,15 +101,19 @@ class DallasSensor {
return (dallas_gpio_ != 0);
}
uint8_t dallas_format() {
return dallas_format_;
bool have_sensors() {
return (sensors_.size() > 0);
}
void dallas_format(uint8_t dallas_format) {
dallas_format_ = dallas_format;
size_t no_sensors() {
return sensors_.size();
}
bool update(const char * idstr, const char * name, int16_t offset);
bool update(const std::string & id_str, const std::string & name, int16_t offset);
#ifdef EMSESP_DEBUG
void test();
#endif
private:
static constexpr uint8_t MAX_SENSORS = 20;
@@ -129,27 +154,24 @@ class DallasSensor {
bool temperature_convert_complete();
int16_t get_temperature_c(const uint8_t addr[]);
uint64_t get_id(const uint8_t addr[]);
void remove_ha_topic(const std::string & id_str);
bool command_info(const char * value, const int8_t id, JsonObject & output);
bool command_commands(const char * value, const int8_t id, JsonObject & output);
void delete_ha_config(uint8_t index, const char * name);
uint32_t last_activity_ = uuid::get_uptime();
State state_ = State::IDLE;
uint32_t last_activity_ = uuid::get_uptime();
State state_ = State::IDLE;
std::vector<Sensor> sensors_;
std::vector<Sensor> sensors_; // our list of active sensors
bool registered_ha_[MAX_SENSORS];
int8_t scancnt_ = SCAN_START;
uint8_t firstscan_ = 0;
uint8_t dallas_gpio_ = 0;
bool parasite_ = false;
bool changed_ = false;
uint32_t sensorfails_ = 0;
uint32_t sensorreads_ = 0;
int8_t scanretry_ = 0;
uint8_t dallas_format_ = 0;
int8_t scancnt_ = SCAN_START;
uint8_t firstscan_ = 0;
uint8_t dallas_gpio_ = 0;
bool parasite_ = false;
bool changed_ = false;
uint32_t sensorfails_ = 0;
uint32_t sensorreads_ = 0;
int8_t scanretry_ = 0;
};
} // namespace emsesp

View File

@@ -88,6 +88,10 @@
#define EMSESP_DEFAULT_ANALOG_ENABLED false
#endif
#ifndef EMSESP_DEFAULT_TELNET_ENABLED
#define EMSESP_DEFAULT_TELNET_ENABLED false
#endif
#ifndef EMSESP_DEFAULT_BOARD_PROFILE
#define EMSESP_DEFAULT_BOARD_PROFILE "S32" // Gateway S32
#endif
@@ -124,10 +128,6 @@
#define EMSESP_DEFAULT_BOOL_FORMAT 1 // on/off
#endif
#ifndef EMSESP_DEFAULT_DALLAS_FORMAT
#define EMSESP_DEFAULT_DALLAS_FORMAT 1 // sensorid
#endif
#ifndef EMSESP_DEFAULT_HA_CLIMATE_FORMAT
#define EMSESP_DEFAULT_HA_CLIMATE_FORMAT 1 // current temp
#endif
@@ -152,6 +152,14 @@
#define EMSESP_DEFAULT_NESTED_FORMAT 1
#endif
#ifndef EMSESP_DEFAULT_DISCOVERY_PREFIX
#define EMSESP_DEFAULT_DISCOVERY_PREFIX "homeassistant"
#endif
#ifndef EMSESP_DEFAULT_PUBLISH_SINGLE
#define EMSESP_DEFAULT_PUBLISH_SINGLE false
#endif
#ifndef EMSESP_DEFAULT_SEND_RESPONSE
#define EMSESP_DEFAULT_SEND_RESPONSE false
#endif
@@ -164,6 +172,14 @@
#define EMSESP_DEFAULT_SENSOR_NAME ""
#endif
#ifndef EMSESP_DEFAULT_ANALOG_NAME
#define EMSESP_DEFAULT_ANALOG_NAME ""
#endif
#ifndef EMSESP_DEFAULT_ANALOG_UOM
#define EMSESP_DEFAULT_ANALOG_UOM "mV"
#endif
#ifndef EMSESP_DEFAULT_WEBLOG_LEVEL
#define EMSESP_DEFAULT_WEBLOG_LEVEL 6 // INFO
#endif
@@ -176,4 +192,24 @@
#define EMSESP_DEFAULT_WEBLOG_COMPACT true
#endif
// matches Web UI settings
enum {
BOOL_FORMAT_ONOFF_STR = 1,
BOOL_FORMAT_ONOFF_STR_CAP,
BOOL_FORMAT_TRUEFALSE_STR,
BOOL_FORMAT_TRUEFALSE,
BOOL_FORMAT_10_STR,
BOOL_FORMAT_10
};
enum {
ENUM_FORMAT_VALUE = 1,
ENUM_FORMAT_INDEX // 2
};
#endif

View File

@@ -26,12 +26,14 @@
// Boilers - 0x08
{ 64, DeviceType::BOILER, F("BK13/BK15/Smartline/GB1x2"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{ 72, DeviceType::BOILER, F("GB125/MC10"), DeviceFlags::EMS_DEVICE_FLAG_EMS},
{ 81, DeviceType::BOILER, F("Cascade CM10"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{ 84, DeviceType::BOILER, F("Logamax Plus GB022"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{ 95, DeviceType::BOILER, F("Condens 2500/Logamax/Logomatic/Cerapur Top/Greenstar/Generic HT3"), DeviceFlags::EMS_DEVICE_FLAG_HT3},
{115, DeviceType::BOILER, F("Topline/GB162"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{122, DeviceType::BOILER, F("Proline"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{123, DeviceType::BOILER, F("GBx72/Trendline/Cerapur/Greenstar Si/27i"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{131, DeviceType::BOILER, F("GB212"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{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},
{170, DeviceType::BOILER, F("Logano GB212"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
@@ -42,11 +44,12 @@
{208, DeviceType::BOILER, F("Logamax Plus/GB192/Condens GC9000"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{210, DeviceType::BOILER, F("Cascade MC400"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{211, DeviceType::BOILER, F("EasyControl Adapter"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{234, DeviceType::BOILER, F("Logamax Plus GB122"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{234, DeviceType::BOILER, F("Logamax Plus GB122/Condense 2300"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{206, DeviceType::BOILER, F("Ecomline Excellent"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
// Controllers - 0x09 / 0x10 / 0x50
{ 68, DeviceType::CONTROLLER, F("BC10/RFM20"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{ 81, DeviceType::CONTROLLER, F("CM10"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{ 84, DeviceType::CONTROLLER, F("GB022"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{ 89, DeviceType::CONTROLLER, F("BC10 GB142"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{ 95, DeviceType::CONTROLLER, F("HT3"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
@@ -61,6 +64,7 @@
{209, DeviceType::CONTROLLER, F("ErP"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{218, DeviceType::CONTROLLER, F("M200/RFM200"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x50
{224, DeviceType::CONTROLLER, F("9000i"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{229, DeviceType::CONTROLLER, F("8700i"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{230, DeviceType::CONTROLLER, F("BC Base"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{240, DeviceType::CONTROLLER, F("Rego 3000"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{241, DeviceType::CONTROLLER, F("Condens 5000i"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
@@ -80,7 +84,7 @@
{ 90, DeviceType::THERMOSTAT, F("RC10/Moduline 100"), DeviceFlags::EMS_DEVICE_FLAG_RC20_N}, // 0x17
{ 93, DeviceType::THERMOSTAT, F("RC20RF"), DeviceFlags::EMS_DEVICE_FLAG_RC20}, // 0x19
{ 94, DeviceType::THERMOSTAT, F("RFM20 Remote"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x18
{151, DeviceType::THERMOSTAT, F("RC25"), DeviceFlags::EMS_DEVICE_FLAG_RC20_N}, // 0x17
{151, DeviceType::THERMOSTAT, F("RC25"), DeviceFlags::EMS_DEVICE_FLAG_RC25}, // 0x17
{157, DeviceType::THERMOSTAT, F("RC200/CW100"), DeviceFlags::EMS_DEVICE_FLAG_RC100}, // 0x18
{158, DeviceType::THERMOSTAT, F("RC300/RC310/Moduline 3000/1010H/CW400/Sense II"), DeviceFlags::EMS_DEVICE_FLAG_RC300}, // 0x10
{165, DeviceType::THERMOSTAT, F("RC100/Moduline 1000/1010"), DeviceFlags::EMS_DEVICE_FLAG_RC100}, // 0x18, 0x38
@@ -109,13 +113,13 @@
{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
// 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},
{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},
{161, DeviceType::MIXER, F("MM200"), DeviceFlags::EMS_DEVICE_FLAG_MMPLUS},
{204, DeviceType::MIXER, F("MP100"), DeviceFlags::EMS_DEVICE_FLAG_MP},
{204, DeviceType::MIXER, F("MP100"), DeviceFlags::EMS_DEVICE_FLAG_MP}, // pool
// Heat Pumps - 0x38
{200, DeviceType::HEATPUMP, F("HP Module"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
@@ -126,7 +130,7 @@
{205, DeviceType::CONNECT, F("Moduline Easy Connect"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{206, DeviceType::CONNECT, F("Easy Connect"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
// wireless sensor base- 0x50
// Wireless sensor base - 0x50
{236, DeviceType::CONNECT, F("Wireless sensor base"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
// Switches - 0x11
@@ -135,7 +139,7 @@
// Gateways - 0x48
{189, DeviceType::GATEWAY, F("KM200/MB LAN 2"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
// generic - 0x40 or other with no product-id and no version
// Generic - 0x40 or other with no product-id and no version
{0, DeviceType::GENERIC, F("unknown"), DeviceFlags::EMS_DEVICE_FLAG_NONE}
// clang-format on

File diff suppressed because it is too large Load Diff

View File

@@ -25,9 +25,7 @@ namespace emsesp {
class Boiler : public EMSdevice {
public:
Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual bool publish_ha_device_config();
Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand);
private:
static uuid::log::Logger logger_;
@@ -52,50 +50,46 @@ class Boiler : public EMSdevice {
static constexpr uint16_t EMS_TYPE_UBAInformation = 0x495;
static constexpr uint16_t EMS_TYPE_UBAEnergySupplied = 0x494;
static constexpr uint8_t EMS_BOILER_SELFLOWTEMP_HEATING = 20; // was originally 70, changed to 30 for issue #193, then to 20 with issue #344
// ww
uint8_t wwSetTemp_; // Warm Water set temperature
uint8_t wwSelTemp_; // Warm Water selected temperature
uint8_t wwSelTempLow_; // Warm Water lower selected temperature
uint8_t wwSelTempOff_; // Warm Water selected temperature for off position
uint8_t wwSelTempSingle_; // Warm Water single charge temperature
uint8_t wwSetTemp_; // DHW set temperature
uint8_t wwSelTemp_; // DHW selected temperature
uint8_t wwSelTempLow_; // DHW lower selected temperature
uint8_t wwSelTempOff_; // DHW selected temperature for off position
uint8_t wwSelTempSingle_; // DHW single charge temperature
uint8_t wwType_; // 0-off, 1-flow, 2-flowbuffer, 3-buffer, 4-layered buffer
uint8_t wwComfort_; // WW comfort mode
uint8_t wwCircPump_; // Warm Water circulation pump available
uint8_t wwChargeType_; // Warm Water charge type (pump or 3-way-valve)
uint8_t wwDisinfectionTemp_; // Warm Water disinfection temperature to prevent infection
uint8_t wwCircMode_; // Warm Water circulation pump mode
uint8_t wwCircPump_; // DHW circulation pump available
uint8_t wwChargeType_; // DHW charge type (pump or 3-way-valve)
uint8_t wwDisinfectionTemp_; // DHW disinfection temperature to prevent infection
uint8_t wwCircMode_; // DHW circulation pump mode
uint8_t wwCirc_; // Circulation on/off
uint16_t wwCurTemp_; // Warm Water current temperature
uint16_t wwCurTemp2_; // Warm Water current temperature storage
uint8_t wwCurFlow_; // Warm Water current flow temp in l/min
uint16_t wwStorageTemp1_; // warm water storage temp 1
uint16_t wwStorageTemp2_; // warm water storage temp 2
uint8_t wwActivated_; // Warm Water activated
uint8_t wwOneTime_; // Warm Water one time function on/off
uint8_t wwDisinfect_; // Warm Water disinfection on/off
uint8_t wwCharging_; // Warm Water charging on/off
uint8_t wwRecharging_; // Warm Water recharge on/off
uint8_t wwTempOK_; // Warm Water temperature ok on/off
uint16_t wwCurTemp_; // DHW current temperature
uint16_t wwCurTemp2_; // DHW current temperature storage
uint8_t wwCurFlow_; // DHW current flow temp in l/min
uint16_t wwStorageTemp1_; // DHW storage temp 1
uint16_t wwStorageTemp2_; // DHW storage temp 2
uint8_t wwActivated_; // DHW activated
uint8_t wwOneTime_; // DHW one time function on/off
uint8_t wwDisinfect_; // DHW disinfection on/off
uint8_t wwCharging_; // DHW charging on/off
uint8_t wwRecharging_; // DHW recharge on/off
uint8_t wwTempOK_; // DHW temperature ok on/off
uint8_t wwActive_; //
uint8_t wwHeat_; // 3-way valve on WW
uint8_t ww3wayValve_; // 3-way valve on WW
uint8_t wwSetPumpPower_; // ww pump speed/power?
uint8_t wwFlowTempOffset_; // Boiler offset for ww heating
uint8_t wwMaxPower_; // Warm Water maximum power
uint32_t wwStarts_; // Warm Water starts
uint32_t wwStarts2_; // Warm water control starts
uint32_t wwWorkM_; // Warm Water minutes
uint8_t wwMaxPower_; // DHW maximum power
uint32_t wwStarts_; // DHW starts
uint32_t wwStarts2_; // DHW control starts
uint32_t wwWorkM_; // DHW minutes
int8_t wwHystOn_;
int8_t wwHystOff_;
uint8_t wwTapActivated_; // maintenance-mode to switch DHW off
uint16_t wwMixerTemp_; // mixing temperature
uint16_t wwTankMiddleTemp_; // Tank middle temperature (TS3)
uint8_t wwTapActivated_; // maintenance-mode to switch DHW off
uint16_t wwMixerTemp_; // mixing temperature
uint16_t wwCylMiddleTemp_; // Cyl middle temperature (TS3)
// main
uint8_t id_; // product id
uint8_t dummy8u_; // for commands with no output
uint8_t dummybool_; // for commands with no output
uint8_t reset_; // for reset command
uint8_t heatingActive_; // Central heating is on/off
uint8_t tapwaterActive_; // Hot tap water is on/off
uint8_t selFlowTemp_; // Selected flow temperature
@@ -110,10 +104,12 @@ class Boiler : public EMSdevice {
uint16_t boilTemp_; // Boiler temperature
uint16_t exhaustTemp_; // Exhaust temperature
uint8_t burnGas_; // Gas on/off
uint8_t burnGas2_; // Gas stage 2 on/off
uint16_t flameCurr_; // Flame current in micro amps
uint8_t heatingPump_; // Boiler heating pump on/off
uint8_t fanWork_; // Fan on/off
uint8_t ignWork_; // Ignition on/off
uint8_t oilPreHeat_; // oil preheating on
uint8_t heatingActivated_; // Heating activated on the boiler
uint8_t heatingTemp_; // Heating temperature setting on the boiler
uint8_t pumpModMax_; // Boiler circuit pump modulation max. power %
@@ -131,7 +127,7 @@ class Boiler : public EMSdevice {
uint32_t burnWorkMin_; // Total burner operating time
uint32_t heatWorkMin_; // Total heat operating time
uint32_t UBAuptime_; // Total UBA working hours
char lastCode_[60]; // last error code
char lastCode_[75]; // last error code
char serviceCode_[4]; // 3 character status/service code
uint16_t serviceCodeNumber_; // error/service code
@@ -139,21 +135,21 @@ class Boiler : public EMSdevice {
uint32_t upTimeControl_; // Operating time control
uint32_t upTimeCompHeating_; // Operating time compressor heating
uint32_t upTimeCompCooling_; // Operating time compressor cooling
uint32_t upTimeCompWw_; // Operating time compressor warm water
uint32_t upTimeCompWw_; // Operating time compressor DHW
uint32_t upTimeCompPool_; // Operating time compressor pool
uint32_t totalCompStarts_; // Total Commpressor control starts
uint32_t heatingStarts_; // Heating control starts
uint32_t coolingStarts_; // Cooling control starts
uint32_t poolStarts_; // Warm water control starts
uint32_t poolStarts_; // DHW control starts
uint32_t nrgConsTotal_; // Energy consumption total
uint32_t nrgConsCompTotal_; // Energy consumption compressor total
uint32_t nrgConsCompHeating_; // Energy consumption compressor heating
uint32_t nrgConsCompWw_; // Energy consumption compressor warm water
uint32_t nrgConsCompWw_; // Energy consumption compressor DHW
uint32_t nrgConsCompCooling_; // Energy consumption compressor cooling
uint32_t nrgConsCompPool_; // Energy consumption compressor pool
uint32_t nrgSuppTotal_; // Energy supplied total
uint32_t nrgSuppHeating_; // Energy supplied heating
uint32_t nrgSuppWw_; // Energy supplied warm water
uint32_t nrgSuppWw_; // Energy supplied DHW
uint32_t nrgSuppCooling_; // Energy supplied cooling
uint32_t nrgSuppPool_; // Energy supplied pool
uint32_t auxElecHeatNrgConsTotal_; // Auxiliary electrical heater energy consumption total

View File

@@ -22,15 +22,8 @@ namespace emsesp {
REGISTER_FACTORY(Connect, EMSdevice::DeviceType::CONNECT);
uuid::log::Logger Connect::logger_{F_(connect), uuid::log::Facility::CONSOLE};
Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
Connect::Connect(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) {
}
// publish HA config
bool Connect::publish_ha_device_config() {
return true;
}
} // namespace emsesp

View File

@@ -25,12 +25,7 @@ namespace emsesp {
class Connect : public EMSdevice {
public:
Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual bool publish_ha_device_config();
private:
static uuid::log::Logger logger_;
Connect(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);
};
} // namespace emsesp

View File

@@ -22,15 +22,8 @@ namespace emsesp {
REGISTER_FACTORY(Controller, EMSdevice::DeviceType::CONTROLLER);
uuid::log::Logger Controller::logger_{F_(controller), uuid::log::Facility::CONSOLE};
Controller::Controller(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
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) {
}
// publish HA config
bool Controller::publish_ha_device_config() {
return true;
}
} // namespace emsesp

View File

@@ -25,12 +25,7 @@ namespace emsesp {
class Controller : public EMSdevice {
public:
Controller(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual bool publish_ha_device_config();
private:
static uuid::log::Logger logger_;
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);
};
} // namespace emsesp

View File

@@ -22,15 +22,8 @@ namespace emsesp {
REGISTER_FACTORY(Gateway, EMSdevice::DeviceType::GATEWAY);
uuid::log::Logger Gateway::logger_{F_(gateway), uuid::log::Facility::CONSOLE};
Gateway::Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
Gateway::Gateway(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) {
}
// publish HA config
bool Gateway::publish_ha_device_config() {
return true;
}
} // namespace emsesp

View File

@@ -25,12 +25,7 @@ namespace emsesp {
class Gateway : public EMSdevice {
public:
Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual bool publish_ha_device_config();
private:
static uuid::log::Logger logger_;
Gateway(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);
};
} // namespace emsesp

View File

@@ -24,23 +24,18 @@ REGISTER_FACTORY(Generic, EMSdevice::DeviceType::GENERIC);
uuid::log::Logger Generic::logger_{F_(generic), uuid::log::Facility::CONSOLE};
Generic::Generic(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
Generic::Generic(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) {
// RF-Sensor 0x40 sending temperature in telegram 0x435, see https://github.com/emsesp/EMS-ESP32/issues/103
if (device_id == 0x40) {
register_telegram_type(0x435, F("RFSensorMessage"), false, MAKE_PF_CB(process_RFSensorMessage));
register_device_value(TAG_NONE, &rfTemp_, DeviceValueType::SHORT, FL_(div10), FL_(RFTemp), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_NONE, &rfTemp_, DeviceValueType::SHORT, FL_(div10), FL_(RFTemp), DeviceValueUOM::DEGREES);
}
}
// publish HA config
bool Generic::publish_ha_device_config() {
return true;
}
// type 0x435 rf remote sensor
void Generic::process_RFSensorMessage(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(rfTemp_, 0)); // is * 10
has_update(telegram, rfTemp_, 0); // is * 10
}
} // namespace emsesp
} // namespace emsesp

View File

@@ -25,9 +25,7 @@ namespace emsesp {
class Generic : public EMSdevice {
public:
Generic(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual bool publish_ha_device_config();
Generic(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);
private:
static uuid::log::Logger logger_;

View File

@@ -22,50 +22,15 @@ namespace emsesp {
REGISTER_FACTORY(Heatpump, EMSdevice::DeviceType::HEATPUMP);
uuid::log::Logger Heatpump::logger_{F_(heatpump), uuid::log::Facility::CONSOLE};
Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
Heatpump::Heatpump(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) {
// telegram handlers
register_telegram_type(0x042B, F("HP1"), true, MAKE_PF_CB(process_HPMonitor1));
register_telegram_type(0x047B, F("HP2"), true, MAKE_PF_CB(process_HPMonitor2));
// device values
register_device_value(TAG_NONE, &id_, DeviceValueType::UINT, nullptr, FL_(ID), DeviceValueUOM::NONE);
register_device_value(TAG_NONE, &airHumidity_, DeviceValueType::UINT, FL_(div2), FL_(airHumidity), DeviceValueUOM::PERCENT);
register_device_value(TAG_NONE, &dewTemperature_, DeviceValueType::UINT, nullptr, FL_(dewTemperature), DeviceValueUOM::DEGREES);
id_ = product_id;
}
// publish HA config
bool Heatpump::publish_ha_device_config() {
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
doc["uniq_id"] = F_(heatpump);
doc["ic"] = F_(icondevice);
char stat_t[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(stat_t, sizeof(stat_t), "%s/%s", Mqtt::base().c_str(), Mqtt::tag_to_topic(device_type(), DeviceValueTAG::TAG_NONE).c_str());
doc["stat_t"] = stat_t;
char name_s[40];
snprintf(name_s, sizeof(name_s), FSTR_(productid_fmt), device_type_name().c_str());
doc["name"] = name_s;
doc["val_tpl"] = FJSON("{{value_json.id}}");
JsonObject dev = doc.createNestedObject("dev");
dev["name"] = FJSON("EMS-ESP Heat Pump");
dev["sw"] = EMSESP_APP_VERSION;
dev["mf"] = brand_to_string();
dev["mdl"] = this->name();
JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp-heatpump");
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "sensor/%s/heatpump/config", Mqtt::base().c_str());
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
return true;
register_device_value(DeviceValueTAG::TAG_NONE, &airHumidity_, DeviceValueType::UINT, FL_(div2), FL_(airHumidity), DeviceValueUOM::PERCENT);
register_device_value(DeviceValueTAG::TAG_NONE, &dewTemperature_, DeviceValueType::UINT, nullptr, FL_(dewTemperature), DeviceValueUOM::DEGREES);
}
/*
@@ -73,8 +38,8 @@ bool Heatpump::publish_ha_device_config() {
* e.g. "38 10 FF 00 03 7B 08 24 00 4B"
*/
void Heatpump::process_HPMonitor2(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(dewTemperature_, 0));
has_update(telegram->read_value(airHumidity_, 1));
has_update(telegram, dewTemperature_, 0);
has_update(telegram, airHumidity_, 1);
}
#pragma GCC diagnostic push
@@ -90,4 +55,4 @@ void Heatpump::process_HPMonitor1(std::shared_ptr<const Telegram> telegram) {
#pragma GCC diagnostic pop
} // namespace emsesp
} // namespace emsesp

View File

@@ -25,16 +25,11 @@ namespace emsesp {
class Heatpump : public EMSdevice {
public:
Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual bool publish_ha_device_config();
Heatpump(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);
private:
static uuid::log::Logger logger_;
uint8_t airHumidity_;
uint8_t dewTemperature_;
uint8_t id_;
void process_HPMonitor1(std::shared_ptr<const Telegram> telegram);
void process_HPMonitor2(std::shared_ptr<const Telegram> telegram);

View File

@@ -24,41 +24,59 @@ REGISTER_FACTORY(Mixer, EMSdevice::DeviceType::MIXER);
uuid::log::Logger Mixer::logger_{F_(mixer), uuid::log::Facility::CONSOLE};
Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
Mixer::Mixer(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) {
// Pool module
if (flags == EMSdevice::EMS_DEVICE_FLAG_MP) {
register_telegram_type(0x5BA, F("HpPoolStatus"), true, MAKE_PF_CB(process_HpPoolStatus));
type_ = Type::MP;
register_device_value(TAG_NONE, &id_, DeviceValueType::UINT, nullptr, FL_(ID), DeviceValueUOM::NONE);
register_device_value(TAG_NONE, &poolTemp_, DeviceValueType::SHORT, FL_(div10), FL_(poolTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_NONE, &poolShuntStatus_, DeviceValueType::ENUM, FL_(enum_shunt), FL_(poolShuntStatus), DeviceValueUOM::NONE);
register_device_value(TAG_NONE, &poolShunt_, DeviceValueType::UINT, nullptr, FL_(poolShunt), DeviceValueUOM::PERCENT);
register_device_value(DeviceValueTAG::TAG_NONE, &poolTemp_, DeviceValueType::SHORT, FL_(div10), FL_(poolTemp), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_NONE, &poolShuntStatus_, DeviceValueType::ENUM, FL_(enum_shunt), FL_(poolShuntStatus), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_NONE, &poolShunt_, DeviceValueType::UINT, nullptr, FL_(poolShunt), DeviceValueUOM::PERCENT);
}
// EMS+
if (flags == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) {
if (device_id >= 0x20 && device_id <= 0x27) {
register_telegram_type(device_id - 0x20 + 0x02D7, F("MMPLUSStatusMessage_HC"), true, MAKE_PF_CB(process_MMPLUSStatusMessage_HC));
register_telegram_type(device_id - 0x20 + 0x02D7, F("MMPLUSStatusMessage_HC"), false, MAKE_PF_CB(process_MMPLUSStatusMessage_HC));
// register_telegram_type(device_id - 0x20 + 0x02E1, F("MMPLUSStetMessage_HC"), true, MAKE_PF_CB(process_MMPLUSSetMessage_HC));
type_ = Type::HC;
hc_ = device_id - 0x20 + 1;
uint8_t tag = TAG_HC1 + hc_ - 1;
register_device_value(tag, &id_, DeviceValueType::UINT, nullptr, FL_(ID), DeviceValueUOM::NONE);
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));
} else if (device_id >= 0x28 && device_id <= 0x29) {
register_telegram_type(device_id - 0x28 + 0x0331, F("MMPLUSStatusMessage_WWC"), true, MAKE_PF_CB(process_MMPLUSStatusMessage_WWC));
register_telegram_type(device_id - 0x28 + 0x0331, F("MMPLUSStatusMessage_WWC"), false, MAKE_PF_CB(process_MMPLUSStatusMessage_WWC));
register_telegram_type(device_id - 0x28 + 0x0313, F("MMPLUSConfigMessage_WWC"), true, MAKE_PF_CB(process_MMPLUSConfigMessage_WWC));
// register_telegram_type(device_id - 0x28 + 0x033B, F("MMPLUSSetMessage_WWC"), true, MAKE_PF_CB(process_MMPLUSSetMessage_WWC));
type_ = Type::WWC;
hc_ = device_id - 0x28 + 1;
uint8_t tag = TAG_WWC1 + hc_ - 1;
register_device_value(tag, &id_, DeviceValueType::UINT, nullptr, FL_(ID), DeviceValueUOM::NONE);
uint8_t tag = DeviceValueTAG::TAG_WWC1 + hc_ - 1;
register_device_value(tag, &flowTempHc_, DeviceValueType::USHORT, FL_(div10), FL_(wwTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &pumpStatus_, DeviceValueType::BOOL, nullptr, FL_(wwPumpStatus), DeviceValueUOM::NONE);
register_device_value(tag, &status_, DeviceValueType::INT, nullptr, FL_(wwTempStatus), DeviceValueUOM::NONE);
register_device_value(tag, &wwMaxTemp_, DeviceValueType::UINT, nullptr, FL_(wwMaxTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwMaxTemp));
register_device_value(tag, &wwDiffTemp_, DeviceValueType::INT, nullptr, FL_(wwDiffTemp), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_wwDiffTemp));
register_device_value(tag,
&wwDisinfectionTemp_,
DeviceValueType::UINT,
nullptr,
FL_(wwDisinfectionTemp),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_wwDisinfectionTemp));
register_device_value(tag, &wwReducedTemp_, DeviceValueType::UINT, nullptr, FL_(wwRedTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwReducedTemp));
register_device_value(tag, &wwRequiredTemp_, DeviceValueType::UINT, nullptr, FL_(wwRequiredTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwRequiredTemp));
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));
}
}
@@ -67,17 +85,16 @@ Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
register_telegram_type(0x00AA, F("MMConfigMessage"), true, MAKE_PF_CB(process_MMConfigMessage));
register_telegram_type(0x00AB, F("MMStatusMessage"), false, MAKE_PF_CB(process_MMStatusMessage));
register_telegram_type(0x00AC, F("MMSetMessage"), false, MAKE_PF_CB(process_MMSetMessage));
// EMSESP::send_read_request(0xAA, device_id);
type_ = Type::HC;
hc_ = device_id - 0x20 + 1;
uint8_t tag = TAG_HC1 + hc_ - 1;
register_device_value(tag, &id_, DeviceValueType::UINT, nullptr, FL_(ID), DeviceValueUOM::NONE);
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, &activated_, DeviceValueType::BOOL, nullptr, FL_(activated), DeviceValueUOM::NONE, MAKE_CF_CB(set_activated));
register_device_value(tag, &setValveTime_, DeviceValueType::UINT, FL_(mul10), FL_(mixerSetTime), DeviceValueUOM::SECONDS, MAKE_CF_CB(set_setValveTime), 1, 12);
register_device_value(
tag, &setValveTime_, DeviceValueType::UINT, FL_(mul10), FL_(mixerSetTime), DeviceValueUOM::SECONDS, MAKE_CF_CB(set_setValveTime), 10, 120);
}
// HT3
@@ -87,99 +104,32 @@ Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
// register_telegram_type(0x0123, F("IPMSetMessage"), false, MAKE_PF_CB(process_IPMSetMessage));
type_ = Type::HC;
hc_ = device_id - 0x20 + 1;
uint8_t tag = TAG_HC1 + hc_ - 1;
register_device_value(tag, &id_, DeviceValueType::UINT, nullptr, FL_(ID), DeviceValueUOM::NONE);
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);
}
id_ = product_id;
}
// publish HA config
bool Mixer::publish_ha_device_config() {
// if we don't have valid values for this HC don't add it ever again
if (!Helpers::hasValue(pumpStatus_)) {
return false;
}
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
char uniq_id[20];
if (type_ == Type::MP) {
snprintf(uniq_id, sizeof(uniq_id), "MixerMP");
} else {
snprintf(uniq_id, sizeof(uniq_id), "Mixer%02X", device_id() - 0x20 + 1);
}
doc["uniq_id"] = uniq_id;
doc["ic"] = F_(icondevice);
char stat_t[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(stat_t, sizeof(stat_t), "%s/%s", Mqtt::base().c_str(), Mqtt::tag_to_topic(device_type(), DeviceValueTAG::TAG_NONE).c_str());
doc["stat_t"] = stat_t;
char name[20];
if (type_ == Type::MP) {
snprintf(name, sizeof(name), "Mixer MP");
} else {
snprintf(name, sizeof(name), "Mixer %02X", device_id() - 0x20 + 1);
}
doc["name"] = name;
char tpl[30];
if (type_ == Type::HC) {
snprintf(tpl, sizeof(tpl), "{{value_json.hc%d.id}}", device_id() - 0x20 + 1);
} else if (type_ == Type::WWC) {
snprintf(tpl, sizeof(tpl), "{{value_json.wwc%d.id}}", device_id() - 0x28 + 1);
} else {
snprintf(tpl, sizeof(tpl), "{{value_json.id}}");
}
doc["val_tpl"] = tpl;
JsonObject dev = doc.createNestedObject("dev");
dev["name"] = FJSON("EMS-ESP Mixer");
dev["sw"] = EMSESP_APP_VERSION;
dev["mf"] = brand_to_string();
dev["mdl"] = this->name();
JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp-mixer");
// determine the topic, if its HC and WWC. This is determined by the incoming telegram types.
std::string topic(Mqtt::MQTT_TOPIC_MAX_SIZE, '\0');
if (type_ == Type::HC) {
snprintf(&topic[0], topic.capacity() + 1, "sensor/%s/mixer_hc%d/config", Mqtt::base().c_str(), hc_);
} else if (type_ == Type::WWC) {
snprintf(&topic[0], topic.capacity() + 1, "sensor/%s/mixer_wwc%d/config", Mqtt::base().c_str(), hc_); // WWC
} else if (type_ == Type::MP) {
snprintf(&topic[0], topic.capacity() + 1, "sensor/%s/mixer_mp/config", Mqtt::base().c_str());
}
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
return true;
}
// heating circuits 0x02D7, 0x02D8 etc...
// e.g. A0 00 FF 00 01 D7 00 00 00 80 00 00 00 00 03 C5
// A0 0B FF 00 01 D7 00 00 00 80 00 00 00 00 03 80
void Mixer::process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(flowTempHc_, 3)); // is * 10
has_update(telegram->read_value(flowSetTemp_, 5));
has_update(telegram->read_bitvalue(pumpStatus_, 0, 0));
has_update(telegram->read_value(status_, 2)); // valve status
has_update(telegram, flowTempHc_, 3); // is * 10
has_update(telegram, flowSetTemp_, 5);
has_bitupdate(telegram, pumpStatus_, 0, 0);
has_update(telegram, status_, 2); // valve status
}
// Mixer warm water loading/DHW - 0x0331, 0x0332
// e.g. A9 00 FF 00 02 32 02 6C 00 3C 00 3C 3C 46 02 03 03 00 3C // on 0x28
// A8 00 FF 00 02 31 02 35 00 3C 00 3C 3C 46 02 03 03 00 3C // in 0x29
void Mixer::process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(flowTempHc_, 0)); // is * 10
has_update(telegram->read_bitvalue(pumpStatus_, 2, 0));
has_update(telegram->read_value(status_, 11)); // temp status
has_update(telegram, flowTempHc_, 0); // is * 10
has_bitupdate(telegram, pumpStatus_, 2, 0);
has_update(telegram, status_, 11); // temp status
}
// Mixer IPM - 0x010C
@@ -195,18 +145,27 @@ void Mixer::process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram) {
// do we have a mixed circuit
if (ismixed == 2) {
has_update(telegram->read_value(flowTempHc_, 3)); // is * 10
has_update(telegram->read_value(status_, 2)); // valve status
has_update(telegram, flowTempHc_, 3); // is * 10
has_update(telegram, status_, 2); // valve status
}
has_update(telegram->read_bitvalue(pumpStatus_, 1, 0)); // pump is also in unmixed circuits
has_update(telegram->read_value(flowSetTemp_, 5)); // flowSettemp is also in unmixed circuits, see #711
has_bitupdate(telegram, pumpStatus_, 1, 0); // pump is also in unmixed circuits
has_update(telegram, flowSetTemp_, 5); // flowSettemp is also in unmixed circuits, see #711
}
// Mixer IPM - 0x001E Temperature Message in unmixed circuits
// in unmixed circuits FlowTemp in 10C is zero, this is the measured flowtemp in header
void Mixer::process_IPMTempMessage(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(flowTempVf_, 0)); // TC1, is * 10
has_update(telegram, flowTempVf_, 0); // TC1, is * 10
}
// Mixer MP100 for pools - 0x5BA
void Mixer::process_HpPoolStatus(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, poolTemp_, 0);
has_update(telegram, poolShunt_, 3); // 0-100% how much is the shunt open?
telegram->read_value(poolShuntStatus__, 2);
uint8_t pss = poolShunt_ == 100 ? 3 : (poolShunt_ == 0 ? 4 : poolShuntStatus__);
has_update(poolShuntStatus_, pss);
}
// Mixer on a MM10 - 0xAB
@@ -217,41 +176,67 @@ void Mixer::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/emsesp/EMS-ESP/issues/270 and https://github.com/emsesp/EMS-ESP/issues/386#issuecomment-629610918
has_update(telegram->read_value(flowTempHc_, 1)); // is * 10
has_update(telegram->read_bitvalue(pumpStatus_, 3, 2)); // is 0 or 0x64 (100%), check only bit 2
has_update(telegram->read_value(flowSetTemp_, 0));
has_update(telegram->read_value(status_, 4)); // valve status -100 to 100
has_update(telegram, flowTempHc_, 1); // is * 10
has_bitupdate(telegram, pumpStatus_, 3, 2); // is 0 or 0x64 (100%), check only bit 2
has_update(telegram, flowSetTemp_, 0);
has_update(telegram, status_, 4); // valve status -100 to 100
}
// Pool mixer MP100, - 0x5BA
void Mixer::process_HpPoolStatus(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(poolTemp_, 0));
has_update(telegram->read_value(poolShuntStatus__, 2));
has_update(telegram->read_value(poolShunt_, 3)); // 0-100% how much is the shunt open?
poolShuntStatus_ = poolShunt_ == 100 ? 3 : (poolShunt_ == 0 ? 4 : poolShuntStatus__);
}
/*
* The set-messages are not broadcasted and send from thermostat to mixer,
* we have to fetch for processing
*/
// Mixer on a MM10 - 0xAA
// e.g. Thermostat -> Mixer Module, type 0xAA, telegram: 10 21 AA 00 FF 0C 0A 11 0A 32 xx
void Mixer::process_MMConfigMessage(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(activated_, 0)); // on = 0xFF
has_update(telegram->read_value(setValveTime_, 1)); // valve runtime in 10 sec, max 120 s
has_update(telegram, activated_, 0); // on = 0xFF
has_update(telegram, setValveTime_, 1); // valve runtime in 10 sec, max 120 s
}
// Config message 0x313, has to be fetched
void Mixer::process_MMPLUSConfigMessage_WWC(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, wwRequiredTemp_, 4);
has_update(telegram, wwReducedTemp_, 5);
has_update(telegram, wwDiffTemp_, 7);
has_update(telegram, wwDisinfectionTemp_, 9);
has_update(telegram, wwMaxTemp_, 10);
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
// Thermostat(0x10) -> Mixer(0x20), ?(0x2E1), data: 01 1C 64 00 01
// Thermostat(0x10) -> Mixing Module(0x20), (0x2E1), data: 01 00 00 00 01
// Thermostat(0x10) -> Mixing Module(0x20), (0x2EB), data: 00
void Mixer::process_MMPLUSSetMessage_HC(std::shared_ptr<const Telegram> telegram) {
// pos 1: setpoint
// pos2: pump
}
// unknown, 2 examples from older threads
// Thermostat(0x10) -> Mixer(0x28), ?(0x33B), data: 01 01 00
// Thermostat -> Mixing Module, type 0x023B, telegram: 90 28 FF 00 02 3B 00 02 00 (CRC=68)
void Mixer::process_MMPLUSSetMessage_WWC(std::shared_ptr<const Telegram> telegram) {
}
// MMPLUS telegram 0x345 unknown
// Solar Module -> Mixing Module, type 0x0245, telegram: B0 28 FF 00 02 45 64 01 01 (CRC=36)
// ?
// Mixer on a MM10 - 0xAC
// e.g. Thermostat -> Mixer Module, type 0xAC, telegram: 10 21 AC 00 1E 64 01 AB
void Mixer::process_MMSetMessage(std::shared_ptr<const Telegram> telegram) {
// pos 0: flowtemp setpoint 1E = 30°C
// pos 1: position in %
// pos 1: pump in %
// pos 2 flags (mostly 01)
// LOG_INFO("MM10 SetMessage received");
}
// Thermostat(0x10) -> Mixer(0x21), ?(0x23), data: 1A 64 00 90 21 23 00 1A 64 00 89
void Mixer::process_IPMSetMessage(std::shared_ptr<const Telegram> telegram) {
// pos 0: flowtemp setpoint 1A = 26°C
// pos 1: position in %?
// pos 1: pump in %?
}
#pragma GCC diagnostic pop
@@ -261,7 +246,6 @@ bool Mixer::set_flowSetTemp(const char * value, const int8_t id) {
if (!Helpers::value2number(value, v)) {
return false;
}
LOG_INFO(F("Setting mixer flow set temperature to %d"), v);
if (flags() == EMSdevice::EMS_DEVICE_FLAG_MM10) {
write_command(0xAC, 0, v, 0xAB);
return true;
@@ -283,7 +267,6 @@ bool Mixer::set_pump(const char * value, const int8_t id) {
if (!Helpers::value2bool(value, b)) {
return false;
}
LOG_INFO(F("Setting mixer pump %s"), b ? "on" : "off");
if (flags() == EMSdevice::EMS_DEVICE_FLAG_MM10) {
write_command(0xAC, 1, b ? 0x64 : 0, 0xAB);
return true;
@@ -306,7 +289,6 @@ bool Mixer::set_activated(const char * value, const int8_t id) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_MM10) {
LOG_INFO(F("Setting mixer %s"), value);
write_command(0xAA, 0, b ? 0xFF : 0, 0xAA);
return true;
}
@@ -320,11 +302,81 @@ bool Mixer::set_setValveTime(const char * value, const int8_t id) {
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_MM10) {
v = (v + 5) / 10;
LOG_INFO(F("Setting mixer valve time to %ds"), v * 10);
write_command(0xAA, 1, v, 0xAA);
return true;
}
return false;
}
bool Mixer::set_wwMaxTemp(const char * value, const int8_t id) {
uint8_t wwc = device_id() - 0x28;
float v = 0;
if (!Helpers::value2temperature(value, v)) {
return false;
}
write_command(0x313 + wwc, 10, (uint8_t)v, 0x313 + wwc);
return true;
}
bool Mixer::set_wwDiffTemp(const char * value, const int8_t id) {
uint8_t wwc = device_id() - 0x28;
float v = 0;
if (!Helpers::value2temperature(value, v)) {
return false;
}
write_command(0x313 + wwc, 7, (int8_t)(v * 10), 0x313 + wwc);
return true;
}
bool Mixer::set_wwReducedTemp(const char * value, const int8_t id) {
uint8_t wwc = device_id() - 0x28;
float v = 0;
if (!Helpers::value2temperature(value, v)) {
return false;
}
write_command(0x313 + wwc, 5, (uint8_t)v, 0x313 + wwc);
return true;
}
bool Mixer::set_wwRequiredTemp(const char * value, const int8_t id) {
uint8_t wwc = device_id() - 0x28;
float v = 0;
if (!Helpers::value2temperature(value, v)) {
return false;
}
write_command(0x313 + wwc, 4, (uint8_t)v, 0x313 + wwc);
return true;
}
bool Mixer::set_wwDisinfectionTemp(const char * value, const int8_t id) {
uint8_t wwc = device_id() - 0x28;
float v = 0;
if (!Helpers::value2temperature(value, v)) {
return false;
}
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;
if (!Helpers::value2bool(value, v)) {
return false;
}
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);
return true;
}
} // namespace emsesp

View File

@@ -25,15 +25,16 @@ namespace emsesp {
class Mixer : public EMSdevice {
public:
Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual bool publish_ha_device_config();
Mixer(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);
private:
static uuid::log::Logger logger_;
void process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram);
void process_MMPLUSSetMessage_HC(std::shared_ptr<const Telegram> telegram);
void process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> telegram);
void process_MMPLUSSetMessage_WWC(std::shared_ptr<const Telegram> telegram);
void process_MMPLUSConfigMessage_WWC(std::shared_ptr<const Telegram> telegram);
void process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram);
void process_IPMTempMessage(std::shared_ptr<const Telegram> telegram);
void process_IPMSetMessage(std::shared_ptr<const Telegram> telegram);
@@ -47,12 +48,20 @@ class Mixer : public EMSdevice {
bool set_activated(const char * value, const int8_t id);
bool set_setValveTime(const char * value, const int8_t id);
bool set_wwMaxTemp(const char * value, const int8_t id);
bool set_wwDiffTemp(const char * value, const int8_t id);
bool set_wwReducedTemp(const char * value, const int8_t id);
bool set_wwRequiredTemp(const char * value, const int8_t id);
bool set_wwDisinfectionTemp(const char * value, const int8_t id);
bool set_wwCircPump(const char * value, const int8_t id);
bool set_wwCircMode(const char * value, const int8_t id);
enum class Type {
NONE,
HC, // heating circuit
WWC, // warm water circuit
MP // pool
};
private:
@@ -64,14 +73,23 @@ class Mixer : public EMSdevice {
uint8_t activated_;
uint8_t setValveTime_;
// MM100wwParam - 0x0313, 0x033B
uint8_t wwMaxTemp_;
uint8_t wwRequiredTemp_;
uint8_t wwReducedTemp_;
uint8_t wwDiffTemp_;
uint8_t wwDisinfectionTemp_;
uint8_t wwCircPump_;
uint8_t wwCircMode_;
// MP100 pool
int16_t poolTemp_;
int8_t poolShuntStatus_;
int8_t poolShunt_;
uint8_t poolShuntStatus_;
uint8_t poolShunt_;
Type type_ = Type::NONE;
uint16_t hc_ = EMS_VALUE_USHORT_NOTSET;
int8_t poolShuntStatus__ = EMS_VALUE_INT_NOTSET; // temp value
uint8_t id_;
uint8_t poolShuntStatus__ = EMS_VALUE_UINT_NOTSET; // temp value
};
} // namespace emsesp

File diff suppressed because it is too large Load Diff

View File

@@ -25,26 +25,42 @@ namespace emsesp {
class Solar : public EMSdevice {
public:
Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual bool publish_ha_device_config();
Solar(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);
private:
static uuid::log::Logger logger_;
int16_t collectorTemp_; // TS1: Temperature sensor for collector array 1
int16_t tankBottomTemp_; // TS2: Temperature sensor 1 cylinder, bottom tank (solar thermal system)
int16_t tankBottomTemp2_; // TS5: Temperature sensor 2 cylinder, bottom tank, or swimming pool (solar thermal system)
int16_t heatExchangerTemp_; // TS6: Heat exchanger temperature sensor
uint8_t solarPumpModulation_; // PS1: modulation solar pump
uint8_t cylinderPumpModulation_; // PS5: modulation cylinder pump
uint8_t solarPump_; // PS1: solar pump active
uint8_t valveStatus_; // VS2: status 3-way valve for cylinder 2 (solar thermal system) with valve
int16_t collectorTemp_; // TS1: Temperature sensor for collector array 1
int16_t cylBottomTemp_; // TS2: Temperature sensor 1 cylinder, bottom cyl (solar thermal system)
int16_t cylBottomTemp2_; // TS5: Temperature sensor 2 cylinder, bottom cyl, or swimming pool (solar thermal system)
int16_t heatExchangerTemp_; // TS6: Heat exchanger temperature sensor
int16_t collector2Temp_; // TS7: Temperature sensor for collector array 2
int16_t cylMiddleTemp_; // TS14: Cylinder middle temp
int16_t retHeatAssist_; // TS15: return temperature heating assistance
uint8_t solarPumpMod_; // PS1: modulation solar pump
uint8_t cylPumpMod_; // PS5: modulation cylinder pump
uint8_t solarPump_; // PS1: solar pump active
uint8_t valveStatus_; // VS2: status 3-way valve for cylinder 2 (solar thermal system) with valve
uint8_t solarPump2_; // PS4: solar pump 2 active
uint8_t solarPump2Mod_; // PS4: modulation solar pump
uint8_t m1Valve_; // M1: heat assistance valve
uint8_t m1Power_; // M1: heat assistance valve
// 0x363 heat counter
uint16_t heatCntFlowTemp_;
uint16_t heatCntRetTemp_;
uint8_t heatCnt_;
uint16_t swapFlowTemp_;
uint16_t swapRetTemp_;
// 0x38E
uint32_t energyLastHour_;
uint32_t energyToday_;
uint32_t energyTotal_;
uint32_t pumpWorkTime_; // Total solar pump operating time
uint8_t tankHeated_;
uint32_t pumpWorkTime_; // Total solar pump operating time
uint32_t pump2WorkTime_; // Total solar pump 2 operating time
uint32_t m1WorkTime_; // differential control work time
uint8_t cylHeated_;
uint8_t collectorShutdown_; // Collector shutdown on/off
uint8_t availabilityFlag_;
@@ -53,57 +69,104 @@ class Solar : public EMSdevice {
// telegram 0x0358
uint8_t heatTransferSystem_; // Umladesystem, 00=no
uint8_t externalTank_; // Heat exchanger, 00=no
uint8_t externalCyl_; // Heat exchanger, 00=no
uint8_t thermalDisinfect_; // Daily heatup for disinfection, 00=no
uint8_t heatMetering_; // Wärmemengenzählung, 00=no
uint8_t heatMetering_; // Heat quantity metering, 00=no
uint8_t solarIsEnabled_; // System enable, 00=no
// telegram 0x035A
uint8_t collectorMaxTemp_; // maximum allowed collectorTemp array 1
uint8_t tankMaxTemp_; // Current value for max tank temp
uint8_t collectorMaxTemp_; // maximum allowed collectorTemp array 1
uint8_t cylMaxTemp_; // Current value for max cyl temp
// uint8_t cyl2MaxTemp_; // Current value for max cyl temp
uint8_t collectorMinTemp_; // minimum allowed collectorTemp array 1
uint8_t solarPumpMode_; // 00=off, 01=PWM, 02=10V
uint8_t solarPumpMinMod_; // minimum modulation setting, *5 %
uint8_t solarPumpTurnoffDiff_; // solar pump turnoff collector/tank diff
uint8_t solarPumpTurnonDiff_; // solar pump turnon collector/tank diff
uint8_t solarPumpMinMod_; // minimum modulation setting
uint8_t solarPumpTurnoffDiff_; // solar pump turnoff collector/cyl diff
uint8_t solarPumpTurnonDiff_; // solar pump turnon collector/cyl diff
uint8_t solarPumpKick_; // pump kick for vacuum collector, 00=off
uint8_t plainWaterMode_; // system does not use antifreeze, 00=off
uint8_t doubleMatchFlow_; // double Match Flow, 00=off
// telegram 0x035D
uint8_t collector2MaxTemp_; // maximum allowed collectorTemp array 1
uint8_t collector2MinTemp_; // minimum allowed collectorTemp array 1
uint8_t solarPump2MinMod_; // minimum modulation setting
uint8_t solarPump2TurnoffDiff_; // solar pump turnoff collector/cyl diff
uint8_t solarPump2TurnonDiff_; // solar pump turnon collector/cyl diff
uint8_t solarPump2Kick_; // pump kick for vacuum collector, 00=off
uint8_t solarPump2Mode_; // 00=off, 01=PWM, 02=10V
// telegram 0x35C Heat assistance
uint8_t solarHeatAssist_; // is *10
// telegram 0x035F
uint8_t cylPriority_; // 0 or 1
// telegram 0x361 Differential control
uint8_t diffControl_; // is *10
// telegram 0x380
uint8_t climateZone_; // climate zone identifier
uint16_t collector1Area_; // Area of collector field 1
uint8_t collector1Type_; // Type of collector field 1, 01=flat, 02=vacuum
uint16_t collector2Area_; // Area of collector field 2
uint8_t collector2Type_; // Type of collector field 2, 01=flat, 02=vacuum
// SM100wwTemperature - 0x07D6
uint8_t wwTemp_1_;
uint8_t wwTemp_3_;
uint8_t wwTemp_4_;
uint8_t wwTemp_5_;
uint8_t wwTemp_7_;
uint16_t wwTemp_1_;
uint16_t wwTemp_3_;
uint16_t wwTemp_4_;
uint16_t wwTemp_5_;
uint16_t wwTemp_7_;
// SM100wwStatus - 0x07AA
uint8_t wwPump_;
// SM100wwParam - 0x07A6
uint8_t wwMaxTemp_;
uint8_t wwTemp_;
uint8_t wwRedTemp_;
uint8_t wwDailyTemp_;
uint8_t wwDisinfectionTemp_;
// SM100wwKeepWarm - 0x07AE
uint8_t wwKeepWarm_;
// SM100wwCirc - 0x07A5
uint8_t wwCirc_;
uint8_t wwCircMode_;
// SM100wwStatus2 - 0x07E0
uint8_t wwFlow_;
uint8_t wwPumpMod_;
uint8_t wwStatus2_;
// SM10Config - 0x96
uint8_t wwMinTemp_;
uint8_t maxFlow_; // set this to calculate power
uint32_t solarPower_; // calculated from maxFlow
uint8_t wwMinTemp_;
uint8_t maxFlow_; // set this to calculate power
int16_t solarPower_; // calculated from maxFlow
std::deque<uint16_t> energy;
uint8_t data0_;
uint8_t data1_;
uint8_t data11_;
uint8_t data12_;
uint8_t setting3_;
uint8_t setting4_;
char type_[20]; // Solar of WWC
uint8_t id_;
std::deque<int16_t> energy;
void process_SM10Monitor(std::shared_ptr<const Telegram> telegram);
void process_SM10Config(std::shared_ptr<const Telegram> telegram);
void process_SM100SystemConfig(std::shared_ptr<const Telegram> telegram);
void process_SM100SolarCircuitConfig(std::shared_ptr<const Telegram> telegram);
void process_SM100CircuitConfig(std::shared_ptr<const Telegram> telegram);
void process_SM100Circuit2Config(std::shared_ptr<const Telegram> telegram);
void process_SM100ParamCfg(std::shared_ptr<const Telegram> telegram);
void process_SM100Monitor(std::shared_ptr<const Telegram> telegram);
void process_SM100Monitor2(std::shared_ptr<const Telegram> telegram);
void process_SM100Config(std::shared_ptr<const Telegram> telegram);
void process_SM100Config1(std::shared_ptr<const Telegram> telegram);
void process_SM100Status(std::shared_ptr<const Telegram> telegram);
void process_SM100Status2(std::shared_ptr<const Telegram> telegram);
@@ -111,9 +174,16 @@ class Solar : public EMSdevice {
void process_SM100Energy(std::shared_ptr<const Telegram> telegram);
void process_SM100Time(std::shared_ptr<const Telegram> telegram);
void process_SM100HeatAssist(std::shared_ptr<const Telegram> telegram);
void process_SM100Differential(std::shared_ptr<const Telegram> telegram);
void process_SM100wwTemperature(std::shared_ptr<const Telegram> telegram);
void process_SM100wwStatus(std::shared_ptr<const Telegram> telegram);
void process_SM100wwStatus2(std::shared_ptr<const Telegram> telegram);
void process_SM100wwCommand(std::shared_ptr<const Telegram> telegram);
void process_SM100wwCirc(std::shared_ptr<const Telegram> telegram);
void process_SM100wwParam(std::shared_ptr<const Telegram> telegram);
void process_SM100wwKeepWarm(std::shared_ptr<const Telegram> telegram);
void process_ISM1StatusMessage(std::shared_ptr<const Telegram> telegram);
void process_ISM1Set(std::shared_ptr<const Telegram> telegram);
@@ -121,26 +191,47 @@ class Solar : public EMSdevice {
bool set_CollectorMaxTemp(const char * value, const int8_t id);
bool set_CollectorMinTemp(const char * value, const int8_t id);
bool set_TankMaxTemp(const char * value, const int8_t id);
bool set_cylMaxTemp(const char * value, const int8_t id);
bool set_PumpMinMod(const char * value, const int8_t id);
bool set_wwMinTemp(const char * value, const int8_t id);
bool set_TurnonDiff(const char * value, const int8_t id);
bool set_TurnoffDiff(const char * value, const int8_t id);
bool set_Collector2MaxTemp(const char * value, const int8_t id);
bool set_Collector2MinTemp(const char * value, const int8_t id);
bool set_Pump2MinMod(const char * value, const int8_t id);
bool set_TurnonDiff2(const char * value, const int8_t id);
bool set_TurnoffDiff2(const char * value, const int8_t id);
bool set_SM10MaxFlow(const char * value, const int8_t id);
// SM100
bool set_heatTransferSystem(const char * value, const int8_t id);
bool set_externalTank(const char * value, const int8_t id);
bool set_externalCyl(const char * value, const int8_t id);
bool set_thermalDisinfect(const char * value, const int8_t id);
bool set_heatMetering(const char * value, const int8_t id);
bool set_solarEnabled(const char * value, const int8_t id);
bool set_solarMode(const char * value, const int8_t id);
bool set_solarPumpKick(const char * value, const int8_t id);
bool set_solarPump2Kick(const char * value, const int8_t id);
bool set_plainWaterMode(const char * value, const int8_t id);
bool set_doubleMatchFlow(const char * value, const int8_t id);
bool set_climateZone(const char * value, const int8_t id);
bool set_collector1Area(const char * value, const int8_t id);
bool set_collector1Type(const char * value, const int8_t id);
bool set_collector2Area(const char * value, const int8_t id);
bool set_collector2Type(const char * value, const int8_t id);
bool set_cylPriority(const char * value, const int8_t id);
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_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);
bool set_wwCircMode(const char * value, const int8_t id);
bool set_wwKeepWarm(const char * value, const int8_t id);
bool set_wwDisinfectionTemp(const char * value, const int8_t id);
bool set_wwDailyTemp(const char * value, const int8_t id);
};
} // namespace emsesp

View File

@@ -22,76 +22,35 @@ namespace emsesp {
REGISTER_FACTORY(Switch, EMSdevice::DeviceType::SWITCH);
uuid::log::Logger Switch::logger_ {
F_(switch), uuid::log::Facility::CONSOLE
};
Switch::Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
Switch::Switch(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) {
register_telegram_type(0x9C, F("WM10MonitorMessage"), false, MAKE_PF_CB(process_WM10MonitorMessage));
register_telegram_type(0x9D, F("WM10SetMessage"), false, MAKE_PF_CB(process_WM10SetMessage));
register_telegram_type(0x1E, F("WM10TempMessage"), false, MAKE_PF_CB(process_WM10TempMessage));
register_device_value(TAG_NONE, &id_, DeviceValueType::UINT, nullptr, FL_(ID), DeviceValueUOM::NONE);
register_device_value(TAG_NONE, &activated_, DeviceValueType::BOOL, nullptr, FL_(activated), DeviceValueUOM::NONE);
register_device_value(TAG_NONE, &flowTempHc_, DeviceValueType::USHORT, FL_(div10), FL_(flowTempHc), DeviceValueUOM::DEGREES);
register_device_value(TAG_NONE, &status_, DeviceValueType::INT, nullptr, FL_(status), DeviceValueUOM::NONE);
id_ = product_id;
}
// publish HA config
bool Switch::publish_ha_device_config() {
// if we don't have valid values don't add it ever again
if (!Helpers::hasValue(flowTempHc_)) {
return false;
}
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
doc["uniq_id"] = F_(switch);
doc["ic"] = F_(icondevice);
char stat_t[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(stat_t, sizeof(stat_t), "%s/%s", Mqtt::base().c_str(), Mqtt::tag_to_topic(device_type(), DeviceValueTAG::TAG_NONE).c_str());
doc["stat_t"] = stat_t;
char name_s[40];
snprintf(name_s, sizeof(name_s), FSTR_(productid_fmt), device_type_name().c_str());
doc["name"] = name_s;
doc["val_tpl"] = FJSON("{{value_json.id}}");
JsonObject dev = doc.createNestedObject("dev");
dev["name"] = FJSON("EMS-ESP Switch");
dev["sw"] = EMSESP_APP_VERSION;
dev["mf"] = brand_to_string();
dev["mdl"] = this->name();
JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp-switch");
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "sensor/%s/switch/config", Mqtt::base().c_str());
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
return true;
register_device_value(DeviceValueTAG::TAG_NONE, &activated_, DeviceValueType::BOOL, nullptr, FL_(activated), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_NONE, &flowTempHc_, DeviceValueType::USHORT, FL_(div10), FL_(flowTempHc), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_NONE, &status_, DeviceValueType::INT, nullptr, FL_(status), DeviceValueUOM::NONE);
}
// message 0x9D switch on/off
// Thermostat(0x10) -> Switch(0x11), ?(0x9D), data: 00
void Switch::process_WM10SetMessage(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(activated_, 0));
has_update(telegram, activated_, 0);
}
// message 0x9C holds flowtemp and unknown status value
// Switch(0x11) -> All(0x00), ?(0x9C), data: 01 BA 00 01 00
void Switch::process_WM10MonitorMessage(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(flowTempHc_, 0)); // is * 10
has_update(telegram->read_value(status_, 2));
// has_update(telegram->read_value(status2_, 3)); // unknown
has_update(telegram, flowTempHc_, 0); // is * 10
has_update(telegram, status_, 2);
// has_update(telegram, status2_, 3)); // unknown
}
// message 0x1E flow temperature, same as in 9C, published often, republished also by boiler UBAFast 0x18
// Switch(0x11) -> Boiler(0x08), ?(0x1E), data: 01 BA
void Switch::process_WM10TempMessage(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(flowTempHc_, 0)); // is * 10
has_update(telegram, flowTempHc_, 0); // is * 10
}
} // namespace emsesp
} // namespace emsesp

View File

@@ -25,13 +25,9 @@ namespace emsesp {
class Switch : public EMSdevice {
public:
Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual bool publish_ha_device_config();
Switch(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);
private:
static uuid::log::Logger logger_;
void process_WM10SetMessage(std::shared_ptr<const Telegram> telegram);
void process_WM10MonitorMessage(std::shared_ptr<const Telegram> telegram);
void process_WM10TempMessage(std::shared_ptr<const Telegram> telegram);
@@ -39,7 +35,6 @@ class Switch : public EMSdevice {
uint16_t flowTempHc_;
uint8_t status_;
uint8_t activated_;
uint8_t id_;
};
} // namespace emsesp

File diff suppressed because it is too large Load Diff

View File

@@ -25,7 +25,7 @@ namespace emsesp {
class Thermostat : public EMSdevice {
public:
Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand);
class HeatingCircuit {
public:
HeatingCircuit(const uint8_t hc_num, const uint8_t model)
@@ -34,12 +34,11 @@ class Thermostat : public EMSdevice {
}
~HeatingCircuit() = default;
int16_t setpoint_roomTemp;
int16_t curr_roomTemp;
int16_t selTemp;
int16_t roomTemp;
int16_t remotetemp; // for readback
uint8_t tempautotemp;
uint8_t mode;
uint8_t hamode; // special mode for HA. See https://github.com/emsesp/EMS-ESP32/issues/66
uint8_t modetype;
uint8_t summermode;
uint8_t holidaymode;
@@ -68,8 +67,11 @@ class Thermostat : public EMSdevice {
int8_t noreducetemp; // signed -20°C to +10°C
uint8_t wwprio;
uint8_t fastHeatup;
char holiday[22];
char vacation[22];
char holiday[26];
char vacation[26];
char switchtime1[16];
char switchtime2[16];
// RC 10
uint8_t reducehours; // night reduce duration
uint16_t reduceminutes; // remaining minutes to night->day
@@ -78,13 +80,25 @@ class Thermostat : public EMSdevice {
return hc_num_;
}
uint8_t hc() const {
return hc_num_ - 1;
}
uint8_t get_model() const {
return model_;
}
// determines if the heating circuit is actually present and has data
bool is_active() {
return Helpers::hasValue(setpoint_roomTemp);
return Helpers::hasValue(selTemp);
}
bool ha_climate_created() {
return ha_climate_created_;
}
void ha_climate_created(bool ha_climate_created) {
ha_climate_created_ = ha_climate_created;
}
uint8_t get_mode() const;
@@ -116,19 +130,18 @@ class Thermostat : public EMSdevice {
};
// for sorting based on hc number
friend inline bool operator<(const std::shared_ptr<HeatingCircuit> & lhs, const std::shared_ptr<HeatingCircuit> & rhs) {
return (lhs->hc_num_ < rhs->hc_num_);
friend inline bool operator<(const std::shared_ptr<HeatingCircuit> & a, const std::shared_ptr<HeatingCircuit> & b) {
return (a->hc_num_ < b->hc_num_);
}
private:
uint8_t hc_num_; // heating circuit number 1..10
uint8_t model_; // the model type
uint8_t hc_num_; // heating circuit number 1..10
uint8_t model_; // the model type
bool ha_climate_created_; // if we need to create the HA climate control
};
static std::string mode_tostring(uint8_t mode);
virtual bool publish_ha_device_config();
private:
static uuid::log::Logger logger_;
@@ -144,18 +157,17 @@ class Thermostat : public EMSdevice {
std::vector<uint16_t> monitor_typeids;
std::vector<uint16_t> set_typeids;
std::vector<uint16_t> timer_typeids;
std::vector<uint16_t> timer2_typeids;
std::vector<uint16_t> summer_typeids;
std::vector<uint16_t> summer2_typeids;
std::vector<uint16_t> curve_typeids;
// standard for all thermostats
uint8_t id_; // product id
char status_[20]; // online or offline
char dateTime_[25]; // date and time stamp
char errorCode_[15]; // code from 0xA2 as string i.e. "A22(816)"
uint16_t errorNumber_; // used internally to build error code
char lastCode_[30]; // error log
uint8_t dummy_; // for commands with no output
char lastCode_[50]; // error log
// Installation parameters
uint8_t ibaMainDisplay_; // display on Thermostat: 0 int temp, 1 int setpoint, 2 ext temp, 3 burner temp, 4 ww temp, 5 functioning mode, 6 time, 7 data, 9 smoke temp
@@ -184,13 +196,17 @@ class Thermostat : public EMSdevice {
uint8_t wwSetTempLow_;
uint8_t wwCharge_;
uint8_t wwChargeDuration_;
uint8_t wwDisinfect_;
uint8_t wwDisinfecting_;
uint8_t wwDisinfectDay_;
uint8_t wwDisinfectHour_;
uint8_t wwMaxTemp_;
uint8_t wwOneTimeKey_;
uint8_t wwProgMode_;
uint8_t wwCircProg_;
char wwSwitchTime_[16];
char wwCircSwitchTime_[16];
uint8_t wwDailyHeating_;
uint8_t wwDailyHeatTime_;
std::vector<std::shared_ptr<HeatingCircuit>> heating_circuits_; // each thermostat can have multiple heating circuits
@@ -208,7 +224,8 @@ class Thermostat : public EMSdevice {
static constexpr uint8_t EMS_OFFSET_RC20StatusMessage_setpoint = 1; // setpoint temp
static constexpr uint8_t EMS_OFFSET_RC20StatusMessage_curr = 2; // current temp
static constexpr uint8_t EMS_OFFSET_RC20Set_mode = 23; // position of thermostat mode
static constexpr uint8_t EMS_OFFSET_RC20Set_temp = 28; // position of thermostat setpoint temperature
static constexpr uint8_t EMS_OFFSET_RC20Set_temp_auto = 28; // position of thermostat setpoint temperature
static constexpr uint8_t EMS_OFFSET_RC20Set_temp_manual = 29; // position of thermostat setpoint temperature
static constexpr uint8_t EMS_OFFSET_RC20_2_Set_mode = 3; // ES72 - see https://github.com/emsesp/EMS-ESP/issues/334
static constexpr uint8_t EMS_OFFSET_RC20_2_Set_temp_night = 1; // ES72
@@ -280,6 +297,7 @@ class Thermostat : public EMSdevice {
void register_device_values_hc(std::shared_ptr<Thermostat::HeatingCircuit> hc);
bool thermostat_ha_cmd(const char * message, uint8_t hc_num);
void add_ha_climate(std::shared_ptr<HeatingCircuit> hc);
void process_RCOutdoorTemp(std::shared_ptr<const Telegram> telegram);
void process_IBASettings(std::shared_ptr<const Telegram> telegram);
@@ -287,6 +305,7 @@ class Thermostat : public EMSdevice {
void process_RCError(std::shared_ptr<const Telegram> telegram);
void process_RCErrorMessage(std::shared_ptr<const Telegram> telegram);
void process_RC35wwSettings(std::shared_ptr<const Telegram> telegram);
void process_RC35wwTimer(std::shared_ptr<const Telegram> telegram);
void process_RC35Monitor(std::shared_ptr<const Telegram> telegram);
void process_RC35Set(std::shared_ptr<const Telegram> telegram);
void process_RC35Timer(std::shared_ptr<const Telegram> telegram);
@@ -316,12 +335,14 @@ class Thermostat : public EMSdevice {
void process_JunkersSet(std::shared_ptr<const Telegram> telegram);
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);
// internal helper functions
bool set_mode_n(const uint8_t mode, const uint8_t hc_num);
bool set_temperature_value(const char * value, const int8_t id, const uint8_t mode);
bool set_temperature_value(const char * value, const int8_t id, const uint8_t mode, bool relative = false);
bool set_temperature(const float temperature, const uint8_t mode, const uint8_t hc_num);
bool set_switchtime(const char * value, const uint16_t type_id, char * out, size_t len);
// set functions - these use the id/hc
bool set_mode(const char * value, const int8_t id);
@@ -354,7 +375,8 @@ class Thermostat : public EMSdevice {
bool set_minflowtemp(const char * value, const int8_t id);
bool set_maxflowtemp(const char * value, const int8_t id);
bool set_reducemode(const char * value, const int8_t id);
bool set_switchtime(const char * value, const int8_t id);
bool set_switchtime1(const char * value, const int8_t id);
bool set_switchtime2(const char * value, const int8_t id);
bool set_program(const char * value, const int8_t id);
bool set_controlmode(const char * value, const int8_t id);
bool set_wwprio(const char * value, const int8_t id);
@@ -374,6 +396,10 @@ class Thermostat : public EMSdevice {
bool set_wwOneTimeKey(const char * value, const int8_t id);
bool set_wwProgMode(const char * value, const int8_t id);
bool set_wwCircProg(const char * value, const int8_t id);
bool set_wwSwitchTime(const char * value, const int8_t id);
bool set_wwCircSwitchTime(const char * value, const int8_t id);
bool set_wwDailyHeating(const char * value, const int8_t id);
bool set_wwDailyHeatTime(const char * value, const int8_t id);
bool set_datetime(const char * value, const int8_t id);
bool set_minexttemp(const char * value, const int8_t id);

File diff suppressed because it is too large Load Diff

View File

@@ -19,140 +19,35 @@
#ifndef EMSESP_EMSDEVICE_H_
#define EMSESP_EMSDEVICE_H_
#include <string>
#include <vector>
#include <functional>
#include "emsfactory.h"
#include "telegram.h"
#include "mqtt.h"
#include "helpers.h"
#include "emsdevicevalue.h"
namespace emsesp {
enum DeviceValueType : uint8_t {
BOOL,
INT,
UINT,
SHORT,
USHORT,
ULONG,
TIME, // same as ULONG (32 bits)
ENUM,
STRING,
CMD // special for commands only
};
// Unit Of Measurement mapping - maps to DeviceValueUOM_s in emsdevice.cpp. Sequence is important!!
// also used with HA as uom
enum DeviceValueUOM : uint8_t {
NONE = 0, // 0
DEGREES, // 1
PERCENT, // 2
LMIN, // 3
KWH, // 4
WH, // 5
HOURS, // 6
MINUTES, // 7
UA, // 8
BAR, // 9
KW, // 10
W, // 11
KB, // 12
SECONDS, // 13
DBM, // 14
MV, // 15
TIMES, // 16
OCLOCK // 17
};
// Home Assistant icons (https://materialdesignicons.com)
// the following are used with the UOMs (unit of measurements)
MAKE_PSTR(icondegrees, "mdi:coolant-temperature") // DeviceValueUOM::DEGREES
MAKE_PSTR(iconpercent, "mdi:percent-outline") // DeviceValueUOM::PERCENT
MAKE_PSTR(icontime, "mdi:clock-outline") // DeviceValueUOM::SECONDS MINUTES & HOURS
MAKE_PSTR(iconkb, "mdi:memory") // DeviceValueUOM::KB
MAKE_PSTR(iconlmin, "mdi:water-boiler") // DeviceValueUOM::LMIN
MAKE_PSTR(iconkwh, "mdi:transmission-tower") // DeviceValueUOM::KWH & WH
MAKE_PSTR(iconua, "mdi:lightning-bolt-circle") // DeviceValueUOM::UA
MAKE_PSTR(iconbar, "mdi:gauge") // DeviceValueUOM::BAR
MAKE_PSTR(iconkw, "mdi:omega") // DeviceValueUOM::KW & W
MAKE_PSTR(icondbm, "mdi:wifi-strength-2") // DeviceValueUOM::DBM
MAKE_PSTR(iconnum, "mdi:counter") // DeviceValueUOM::NONE
MAKE_PSTR(icondevice, "mdi:home-automation") // for devices in HA
// TAG mapping - maps to DeviceValueTAG_s in emsdevice.cpp
enum DeviceValueTAG : uint8_t {
TAG_NONE = 0, // wild card
TAG_HEARTBEAT,
TAG_BOILER_DATA,
TAG_DEVICE_DATA_WW,
TAG_THERMOSTAT_DATA,
TAG_HC1,
TAG_HC2,
TAG_HC3,
TAG_HC4,
TAG_WWC1,
TAG_WWC2,
TAG_WWC3,
TAG_WWC4,
TAG_HS1,
TAG_HS2,
TAG_HS3,
TAG_HS4,
TAG_HS5,
TAG_HS6,
TAG_HS7,
TAG_HS8,
TAG_HS9,
TAG_HS10,
TAG_HS11,
TAG_HS12,
TAG_HS13,
TAG_HS14,
TAG_HS15,
TAG_HS16
};
// states of a device value
enum DeviceValueState : uint8_t {
DV_DEFAULT = 0, // 0 - does not yet have a value
DV_ACTIVE = (1 << 0), // 1 - has a value
DV_VISIBLE = (1 << 1), // 2 - shown on web and console
DV_HA_CONFIG_CREATED = (1 << 2) // 4 - set if the HA config has been created
};
class EMSdevice {
public:
virtual ~EMSdevice() = default; // destructor of base class must always be virtual because it's a polymorphic class
static constexpr uint8_t EMS_DEVICES_MAX_TELEGRAMS = 20;
// virtual functions overrules by derived classes
virtual bool publish_ha_device_config() = 0;
// device_type defines which derived class to use, e.g. BOILER, THERMOSTAT etc..
EMSdevice(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
EMSdevice(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)
: device_type_(device_type)
, device_id_(device_id)
, product_id_(product_id)
, version_(version)
, name_(name)
, flags_(flags)
, brand_(brand) {
strlcpy(version_, version, sizeof(version_));
}
const std::string device_type_name() const;
const std::string device_type_name() const;
static const std::string device_type_2_device_name(const uint8_t device_type);
static uint8_t device_name_2_device_type(const char * topic);
static const std::string uom_to_string(uint8_t uom);
static const std::string tag_to_string(uint8_t tag);
static const std::string tag_to_mqtt(uint8_t tag);
@@ -191,11 +86,11 @@ class EMSdevice {
return device_type_; // see enum DeviceType below
}
inline void version(std::string & version) {
version_ = version;
inline void version(const char * version) {
strlcpy(version_, version, sizeof(version_));
}
inline std::string version() const {
inline const char * version() {
return version_;
}
@@ -215,6 +110,7 @@ class EMSdevice {
return name_;
}
// unique id of a device
inline uint8_t unique_id() const {
return unique_id_;
}
@@ -227,8 +123,52 @@ class EMSdevice {
return has_update_;
}
inline void has_update(bool has_update) {
has_update_ |= has_update;
inline void has_update(bool flag) {
has_update_ = flag;
}
inline void has_update(void * value) {
has_update_ = true;
publish_value(value);
}
inline void has_update(char * value, char * newvalue, size_t len) {
if (strcmp(value, newvalue) != 0) {
strlcpy(value, newvalue, len);
has_update_ = true;
publish_value(value);
}
}
inline void has_update(uint8_t & value, uint8_t newvalue) {
if (value != newvalue) {
value = newvalue;
has_update_ = true;
publish_value((void *)&value);
}
}
inline void has_enumupdate(std::shared_ptr<const Telegram> telegram, uint8_t & value, const uint8_t index, uint8_t s = 0) {
if (telegram->read_enumvalue(value, index, s)) {
has_update_ = true;
publish_value((void *)&value);
}
}
template <typename Value>
inline void has_update(std::shared_ptr<const Telegram> telegram, Value & value, const uint8_t index, uint8_t s = 0) {
if (telegram->read_value(value, index, s)) {
has_update_ = true;
publish_value((void *)&value);
}
}
template <typename BitValue>
inline void has_bitupdate(std::shared_ptr<const Telegram> telegram, BitValue & value, const uint8_t index, uint8_t b) {
if (telegram->read_bitvalue(value, index, b)) {
has_update_ = true;
publish_value((void *)&value);
}
}
const std::string brand_to_string() const;
@@ -237,10 +177,13 @@ class EMSdevice {
const std::string to_string() const;
const std::string to_string_short() const;
enum Handlers : uint8_t { ALL, RECEIVED, FETCHED, PENDING };
void show_telegram_handlers(uuid::console::Shell & shell);
char * show_telegram_handlers(char * result);
char * show_telegram_handlers(char * result, uint8_t handlers);
void show_mqtt_handlers(uuid::console::Shell & shell);
void list_device_entries(JsonObject & output);
void exclude_entity(uint8_t entity_id);
using process_function_p = std::function<void(std::shared_ptr<const Telegram>)>;
@@ -249,10 +192,12 @@ class EMSdevice {
const std::string get_value_uom(const char * key);
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 };
bool generate_values_json(JsonObject & output, const uint8_t tag_filter, const bool nested, const uint8_t output_target);
void generate_values_json_web(JsonObject & output);
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);
void register_device_value(uint8_t tag,
void * value_p,
@@ -262,8 +207,9 @@ class EMSdevice {
const __FlashStringHelper * full_name,
uint8_t uom,
bool has_cmd,
int32_t min,
uint32_t max);
int16_t min,
uint16_t max);
void register_device_value(uint8_t tag,
void * value_p,
uint8_t type,
@@ -271,8 +217,9 @@ class EMSdevice {
const __FlashStringHelper * const * name,
uint8_t uom,
const cmd_function_p f,
int32_t min,
uint32_t max);
int16_t min,
uint16_t max);
void register_device_value(uint8_t tag,
void * value_p,
uint8_t type,
@@ -280,6 +227,7 @@ class EMSdevice {
const __FlashStringHelper * const * name,
uint8_t uom,
const cmd_function_p f);
void register_device_value(uint8_t tag,
void * value_p,
uint8_t type,
@@ -293,6 +241,9 @@ class EMSdevice {
void read_command(const uint16_t type_id, uint8_t offset = 0, uint8_t length = 0);
void publish_value(void * value);
void publish_all_values();
void publish_mqtt_ha_entity_config();
const std::string telegram_type_name(std::shared_ptr<const Telegram> telegram);
@@ -300,16 +251,22 @@ class EMSdevice {
void fetch_values();
void toggle_fetch(uint16_t telegram_id, bool toggle);
bool is_fetch(uint16_t telegram_id);
bool has_telegram_id(uint16_t id);
void ha_config_clear();
bool ha_config_done() const {
return ha_config_done_;
}
void ha_config_done(const bool v) {
ha_config_done_ = v;
}
void ha_config_clear();
bool ha_config_firstrun() const {
return ha_config_firstrun_;
}
void ha_config_firstrun(const bool v) {
ha_config_firstrun_ = v;
}
enum Brand : uint8_t {
NO_BRAND = 0, // 0
@@ -325,6 +282,7 @@ class EMSdevice {
enum DeviceType : uint8_t {
SYSTEM = 0, // this is us (EMS-ESP)
DALLASSENSOR, // for internal dallas sensors
ANALOGSENSOR, // for internal analog sensors
BOILER,
THERMOSTAT,
MIXER,
@@ -374,34 +332,31 @@ class EMSdevice {
static constexpr uint8_t EMS_DEVICE_FLAG_RC10 = 2;
static constexpr uint8_t EMS_DEVICE_FLAG_RC20 = 3;
static constexpr uint8_t EMS_DEVICE_FLAG_RC20_N = 4; // Variation on RC20, Older, like ES72
static constexpr uint8_t EMS_DEVICE_FLAG_RC30_N = 5; // variation on RC30, Newer models
static constexpr uint8_t EMS_DEVICE_FLAG_RC30 = 6;
static constexpr uint8_t EMS_DEVICE_FLAG_RC35 = 7;
static constexpr uint8_t EMS_DEVICE_FLAG_RC300 = 8;
static constexpr uint8_t EMS_DEVICE_FLAG_RC100 = 9;
static constexpr uint8_t EMS_DEVICE_FLAG_JUNKERS = 10;
static constexpr uint8_t EMS_DEVICE_FLAG_CRF = 11; // CRF200 only monitor
static constexpr uint8_t EMS_DEVICE_FLAG_RC25 = 5;
static constexpr uint8_t EMS_DEVICE_FLAG_RC30_N = 6; // variation on RC30, Newer models
static constexpr uint8_t EMS_DEVICE_FLAG_RC30 = 7;
static constexpr uint8_t EMS_DEVICE_FLAG_RC35 = 8;
static constexpr uint8_t EMS_DEVICE_FLAG_RC300 = 9;
static constexpr uint8_t EMS_DEVICE_FLAG_RC100 = 10;
static constexpr uint8_t EMS_DEVICE_FLAG_JUNKERS = 11;
static constexpr uint8_t EMS_DEVICE_FLAG_CRF = 12; // CRF200 only monitor
void reserve_device_values(uint8_t elements) {
devicevalues_.reserve(elements);
}
void reserve_telgram_functions(uint8_t elements) {
telegram_functions_.reserve(elements);
}
uint8_t count_entities();
bool has_entities();
private:
uint8_t unique_id_;
uint8_t device_type_ = DeviceType::SYSTEM;
uint8_t device_id_ = 0;
uint8_t product_id_ = 0;
std::string version_;
char version_[6];
std::string name_; // the long name for the EMS model
uint8_t flags_ = 0;
uint8_t brand_ = Brand::NO_BRAND;
bool ha_config_done_ = false;
bool has_update_ = false;
bool ha_config_done_ = false;
bool has_update_ = false;
bool ha_config_firstrun_ = true; // this means a first setup of HA is needed after a restart
struct TelegramFunction {
uint16_t telegram_type_id_; // it's type_id
@@ -419,78 +374,14 @@ class EMSdevice {
}
};
// DeviceValue holds all the attributes for a device value (also a device parameter)
struct DeviceValue {
uint8_t device_type; // EMSdevice::DeviceType
uint8_t tag; // DeviceValueTAG::*
void * value_p; // pointer to variable of any type
uint8_t type; // DeviceValueType::*
const __FlashStringHelper * const * options; // options as a flash char array
uint8_t options_size; // number of options in the char array, calculated
const __FlashStringHelper * short_name; // used in MQTT
const __FlashStringHelper * full_name; // used in Web and Console
uint8_t uom; // DeviceValueUOM::*
uint8_t ha; // DevcieValueHA::
bool has_cmd; // true if there is a Console/MQTT command which matches the short_name
int32_t min;
uint32_t max;
uint8_t state; // DeviceValueState::*
DeviceValue(uint8_t device_type,
uint8_t tag,
void * value_p,
uint8_t type,
const __FlashStringHelper * const * options,
uint8_t options_size,
const __FlashStringHelper * short_name,
const __FlashStringHelper * full_name,
uint8_t uom,
uint8_t ha,
bool has_cmd,
int32_t min,
uint32_t max,
uint8_t state)
: device_type(device_type)
, tag(tag)
, value_p(value_p)
, type(type)
, options(options)
, options_size(options_size)
, short_name(short_name)
, full_name(full_name)
, uom(uom)
, ha(ha)
, has_cmd(has_cmd)
, min(min)
, max(max)
, state(state) {
}
// state flags
inline void add_state(uint8_t s) {
state |= s;
}
inline bool has_state(uint8_t s) const {
return (state & s) == s;
}
inline void remove_state(uint8_t s) {
state &= ~s;
}
inline uint8_t get_state() const {
return state;
}
};
const std::vector<DeviceValue> devicevalues() const;
std::vector<TelegramFunction> telegram_functions_; // each EMS device has its own set of registered telegram types
std::vector<DeviceValue> devicevalues_;
const std::string device_entity_ha(DeviceValue const & dv);
// device values
std::vector<DeviceValue> devicevalues_;
bool check_dv_hasvalue(const DeviceValue & dv);
void init_devicevalues(uint8_t size) {
devicevalues_.reserve(size);
uint8_t dv_index_ = 0; // unique counter for each added device value
uint8_t get_next_dv_id() {
return (dv_index_++);
}
};

250
src/emsdevicevalue.cpp Normal file
View File

@@ -0,0 +1,250 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "emsdevicevalue.h"
#include "emsesp.h"
namespace emsesp {
// mapping of UOM, to match order in DeviceValueUOM enum emsdevice.h
// also maps to DeviceValueUOM in interface/src/project/types.ts for the Web UI
// must be an int of 4 bytes, 32bit aligned
const __FlashStringHelper * DeviceValue::DeviceValueUOM_s[] __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = {
F_(blank),
F_(degrees),
F_(degrees),
F_(percent),
F_(lmin),
F_(kwh),
F_(wh),
F_(hours),
F_(minutes),
F_(ua),
F_(bar),
F_(kw),
F_(w),
F_(kb),
F_(seconds),
F_(dbm),
F_(fahrenheit),
F_(mv),
F_(sqm)
};
// mapping of TAGs, to match order in DeviceValueTAG enum in emsdevice.h
// must be an int of 4 bytes, 32bit aligned
const __FlashStringHelper * const DeviceValue::DeviceValueTAG_s[] PROGMEM = {
F_(tag_none), // ""
F_(tag_heartbeat), // ""
F_(tag_boiler_data), // ""
F_(tag_device_data_ww), // "ww"
F_(tag_thermostat_data), // ""
F_(tag_hc1), // "hc1"
F_(tag_hc2), // "hc2"
F_(tag_hc3), // "hc3"
F_(tag_hc4), // "hc4"
F_(tag_hc5), // "hc5"
F_(tag_hc6), // "hc6"
F_(tag_hc7), // "hc7"
F_(tag_hc8), // "hc8"
F_(tag_wwc1), // "wwc1"
F_(tag_wwc2), // "Wwc2"
F_(tag_wwc3), // "wwc3"
F_(tag_wwc4), // "wwc4"
F_(tag_hs1), // "hs1"
F_(tag_hs2), // "hs2"
F_(tag_hs3), // "hs3"
F_(tag_hs4), // "hs4"
F_(tag_hs5), // "hs5"
F_(tag_hs6), // "hs6"
F_(tag_hs7), // "hs7"
F_(tag_hs8), // "hs8"
F_(tag_hs9), // "hs9"
F_(tag_hs10), // "hs10"
F_(tag_hs11), // "hs11"
F_(tag_hs12), // "hs12"
F_(tag_hs13), // "hs13"
F_(tag_hs14), // "hs14"
F_(tag_hs15), // "hs15"
F_(tag_hs16) // "hs16"
};
// MQTT topics derived from tags
const __FlashStringHelper * const DeviceValue::DeviceValueTAG_mqtt[] PROGMEM = {
F_(tag_none), // ""
F_(heartbeat), // "heartbeat"
F_(tag_boiler_data_mqtt), // ""
F_(tag_device_data_ww_mqtt), // "ww"
F_(tag_thermostat_data), // ""
F_(tag_hc1), // "hc1"
F_(tag_hc2), // "hc2"
F_(tag_hc3), // "hc3"
F_(tag_hc4), // "hc4"
F_(tag_hc5), // "hc5"
F_(tag_hc6), // "hc6"
F_(tag_hc7), // "hc7"
F_(tag_hc8), // "hc8"
F_(tag_wwc1), // "wwc1"
F_(tag_wwc2), // "Wwc2"
F_(tag_wwc3), // "wwc3"
F_(tag_wwc4), // "wwc4"
F_(tag_hs1), // "hs1"
F_(tag_hs2), // "hs2"
F_(tag_hs3), // "hs3"
F_(tag_hs4), // "hs4"
F_(tag_hs5), // "hs5"
F_(tag_hs6), // "hs6"
F_(tag_hs7), // "hs7"
F_(tag_hs8), // "hs8"
F_(tag_hs9), // "hs9"
F_(tag_hs10), // "hs10"
F_(tag_hs11), // "hs11"
F_(tag_hs12), // "hs12"
F_(tag_hs13), // "hs13"
F_(tag_hs14), // "hs14"
F_(tag_hs15), // "hs15"
F_(tag_hs16) // "hs16"
};
// count #tags once at compile time
size_t DeviceValue::tag_count = sizeof(DeviceValue::DeviceValueTAG_s) / sizeof(__FlashStringHelper *);
// 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 has_value = false;
switch (type) {
case DeviceValueType::BOOL:
has_value = Helpers::hasValue(*(uint8_t *)(value_p), EMS_VALUE_BOOL);
break;
case DeviceValueType::STRING:
has_value = Helpers::hasValue((char *)(value_p));
break;
case DeviceValueType::ENUM:
has_value = Helpers::hasValue(*(uint8_t *)(value_p));
break;
case DeviceValueType::INT:
has_value = Helpers::hasValue(*(int8_t *)(value_p));
break;
case DeviceValueType::UINT:
has_value = Helpers::hasValue(*(uint8_t *)(value_p));
break;
case DeviceValueType::SHORT:
has_value = Helpers::hasValue(*(int16_t *)(value_p));
break;
case DeviceValueType::USHORT:
has_value = Helpers::hasValue(*(uint16_t *)(value_p));
break;
case DeviceValueType::ULONG:
has_value = Helpers::hasValue(*(uint32_t *)(value_p));
break;
case DeviceValueType::TIME:
has_value = Helpers::hasValue(*(uint32_t *)(value_p));
break;
case DeviceValueType::CMD:
has_value = false; // commands don't have values!
break;
default:
break;
}
#if defined(EMSESP_DEBUG)
// https://github.com/emsesp/EMS-ESP32/issues/196
// if (has_state(DeviceValueState::DV_ACTIVE) && !has_value) {
// emsesp::EMSESP::logger().warning(F("[DEBUG] Lost device value %s"), short_name);
// }
#endif
return has_value;
}
// set the min and max value for a device value
// converts to signed int, which means rounding to an whole integer
// returns false if there is no min/max needed
// Types BOOL, ENUM, STRING and CMD are not used
bool DeviceValue::get_min_max(int16_t & dv_set_min, int16_t & dv_set_max) {
uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (uom == DeviceValueUOM::DEGREES) ? 2 : (uom == DeviceValueUOM::DEGREES_R) ? 1 : 0;
// if we have individual limits set already, just do the conversion
// limits are not scaled with divider and temperatures are °C
if (min != 0 || max != 0) {
dv_set_min = Helpers::round2(min, 0, fahrenheit);
dv_set_max = Helpers::round2(max, 0, fahrenheit);
return true;
}
// init default values to 0 and 0
dv_set_min = 0;
dv_set_max = 0;
int8_t divider = (options_size == 1) ? Helpers::atoint(uuid::read_flash_string(options[0]).c_str()) : 0;
if (type == DeviceValueType::USHORT) {
dv_set_min = Helpers::round2(0, divider, fahrenheit);
dv_set_max = Helpers::round2(EMS_VALUE_USHORT_NOTSET, divider, fahrenheit);
return true;
}
if (type == DeviceValueType::SHORT) {
dv_set_min = Helpers::round2(-EMS_VALUE_SHORT_NOTSET, divider, fahrenheit);
dv_set_max = Helpers::round2(EMS_VALUE_SHORT_NOTSET, divider, fahrenheit);
return true;
}
if (type == DeviceValueType::UINT) {
if (uom == DeviceValueUOM::PERCENT) {
dv_set_max = 100;
} else {
dv_set_max = Helpers::round2(EMS_VALUE_UINT_NOTSET, divider, fahrenheit);
}
return true;
}
if (type == DeviceValueType::INT) {
if (uom == DeviceValueUOM::PERCENT) {
dv_set_min = -100;
dv_set_max = 100;
} else {
dv_set_min = Helpers::round2(-EMS_VALUE_INT_NOTSET, divider, fahrenheit);
dv_set_max = Helpers::round2(EMS_VALUE_INT_NOTSET, divider, fahrenheit);
}
return true;
}
if (type == DeviceValueType::ULONG) {
dv_set_max = Helpers::round2(EMS_VALUE_ULONG_NOTSET, divider);
return true;
}
if (type == DeviceValueType::TIME) {
dv_set_max = Helpers::round2(EMS_VALUE_ULONG_NOTSET, divider);
return true;
}
return false; // nothing changed, not supported
}
} // namespace emsesp

190
src/emsdevicevalue.h Normal file
View File

@@ -0,0 +1,190 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef EMSESP_EMSDEVICEVALUE_H_
#define EMSESP_EMSDEVICEVALUE_H_
#include <Arduino.h>
#include <ArduinoJson.h>
#include "helpers.h" // for conversions
#include "default_settings.h" // for enum types
#include <uuid/common.h> // for read_flash_string
namespace emsesp {
// DeviceValue holds the information for a device entity
class DeviceValue {
public:
enum DeviceValueType : uint8_t {
BOOL,
INT,
UINT,
SHORT,
USHORT,
ULONG,
TIME, // same as ULONG (32 bits)
ENUM,
STRING,
CMD // special for commands only
};
// Unit Of Measurement mapping - maps to DeviceValueUOM_s in emsdevice.cpp. Sequence is important!!
// also used with HA as uom
enum DeviceValueUOM : uint8_t {
NONE = 0, // 0
DEGREES, // 1
DEGREES_R, // 2
PERCENT, // 3
LMIN, // 4
KWH, // 5
WH, // 6
HOURS, // 7
MINUTES, // 8
UA, // 9
BAR, // 10
KW, // 11
W, // 12
KB, // 13
SECONDS, // 14
DBM, // 15
FAHRENHEIT, // 16
MV, // 17
SQM // 18
};
// TAG mapping - maps to DeviceValueTAG_s in emsdevice.cpp
enum DeviceValueTAG : uint8_t {
TAG_NONE = 0, // wild card
TAG_HEARTBEAT,
TAG_BOILER_DATA,
TAG_DEVICE_DATA_WW,
TAG_THERMOSTAT_DATA,
TAG_HC1,
TAG_HC2,
TAG_HC3,
TAG_HC4,
TAG_HC5,
TAG_HC6,
TAG_HC7,
TAG_HC8,
TAG_WWC1,
TAG_WWC2,
TAG_WWC3,
TAG_WWC4,
TAG_HS1,
TAG_HS2,
TAG_HS3,
TAG_HS4,
TAG_HS5,
TAG_HS6,
TAG_HS7,
TAG_HS8,
TAG_HS9,
TAG_HS10,
TAG_HS11,
TAG_HS12,
TAG_HS13,
TAG_HS14,
TAG_HS15,
TAG_HS16
};
// states of a device value
enum DeviceValueState : uint8_t {
DV_DEFAULT = 0, // 0 - does not yet have a value
DV_ACTIVE = (1 << 0), // 1 - has a valid value
DV_VISIBLE = (1 << 1), // 2 - shown on web and console, otherwise hidden
DV_HA_CONFIG_CREATED = (1 << 2) // 4 - set if the HA config has been created
};
uint8_t device_type; // EMSdevice::DeviceType
uint8_t tag; // DeviceValueTAG::*
void * value_p; // pointer to variable of any type
uint8_t type; // DeviceValueType::*
const __FlashStringHelper * const * options; // options as a flash char array
uint8_t options_size; // number of options in the char array, calculated
const __FlashStringHelper * short_name; // used in MQTT
const __FlashStringHelper * full_name; // used in Web and Console
uint8_t uom; // DeviceValueUOM::*
uint8_t ha; // DevcieValueHA::
bool has_cmd; // true if there is a Console/MQTT command which matches the short_name
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,
void * value_p,
uint8_t type,
const __FlashStringHelper * const * options,
uint8_t options_size,
const __FlashStringHelper * short_name,
const __FlashStringHelper * full_name,
uint8_t uom,
uint8_t ha,
bool has_cmd,
int16_t min,
uint16_t max,
uint8_t state,
uint8_t id)
: device_type(device_type)
, tag(tag)
, value_p(value_p)
, type(type)
, options(options)
, options_size(options_size)
, short_name(short_name)
, full_name(full_name)
, uom(uom)
, ha(ha)
, has_cmd(has_cmd)
, min(min)
, max(max)
, state(state)
, id(id) {
}
bool hasValue();
bool get_min_max(int16_t & dv_set_min, int16_t & dv_set_max);
// state flags
inline void add_state(uint8_t s) {
state |= s;
}
inline bool has_state(uint8_t s) const {
return (state & s) == s;
}
inline void remove_state(uint8_t s) {
state &= ~s;
}
inline uint8_t get_state() const {
return state;
}
static const __FlashStringHelper * DeviceValueUOM_s[];
static const __FlashStringHelper * const DeviceValueTAG_s[];
static const __FlashStringHelper * const DeviceValueTAG_mqtt[];
static size_t tag_count; // # tags
};
}; // namespace emsesp
#endif

View File

@@ -23,18 +23,20 @@ namespace emsesp {
#if defined(EMSESP_STANDALONE)
uint32_t heap_start = 0;
#else
uint32_t heap_start = ESP.getFreeHeap(); // get initial available heap memory
uint32_t heap_start = ESP.getFreeHeap(); // get initial available heap memory
#endif
AsyncWebServer webServer(80);
#if defined(EMSESP_STANDALONE)
FS dummyFS;
ESP8266React EMSESP::esp8266React(&webServer, &dummyFS);
WebSettingsService EMSESP::webSettingsService = WebSettingsService(&webServer, &dummyFS, EMSESP::esp8266React.getSecurityManager());
FS dummyFS;
ESP8266React EMSESP::esp8266React(&webServer, &dummyFS);
WebSettingsService EMSESP::webSettingsService = WebSettingsService(&webServer, &dummyFS, EMSESP::esp8266React.getSecurityManager());
WebCustomizationService EMSESP::webCustomizationService = WebCustomizationService(&webServer, &dummyFS, EMSESP::esp8266React.getSecurityManager());
#else
ESP8266React EMSESP::esp8266React(&webServer, &LITTLEFS);
WebSettingsService EMSESP::webSettingsService = WebSettingsService(&webServer, &LITTLEFS, EMSESP::esp8266React.getSecurityManager());
ESP8266React EMSESP::esp8266React(&webServer, &LITTLEFS);
WebSettingsService EMSESP::webSettingsService = WebSettingsService(&webServer, &LITTLEFS, EMSESP::esp8266React.getSecurityManager());
WebCustomizationService EMSESP::webCustomizationService = WebCustomizationService(&webServer, &LITTLEFS, EMSESP::esp8266React.getSecurityManager());
#endif
WebStatusService EMSESP::webStatusService = WebStatusService(&webServer, EMSESP::esp8266React.getSecurityManager());
@@ -60,6 +62,7 @@ Mqtt EMSESP::mqtt_; // mqtt handler
System EMSESP::system_; // core system services
Console EMSESP::console_; // telnet and serial console
DallasSensor EMSESP::dallassensor_; // Dallas sensors
AnalogSensor EMSESP::analogsensor_; // Analog sensors
Shower EMSESP::shower_; // Shower logic
// static/common variables
@@ -74,8 +77,6 @@ uint32_t EMSESP::last_fetch_ = 0;
uint8_t EMSESP::publish_all_idx_ = 0;
uint8_t EMSESP::unique_id_count_ = 0;
bool EMSESP::trace_raw_ = false;
uint8_t EMSESP::bool_format_ = 1;
uint8_t EMSESP::enum_format_ = 1;
uint16_t EMSESP::wait_validate_ = 0;
bool EMSESP::wait_km_ = true;
@@ -94,7 +95,7 @@ void EMSESP::fetch_device_values(const uint8_t device_id) {
}
}
// see if the device ID exists
// see if the deviceID exists
bool EMSESP::valid_device(const uint8_t device_id) {
for (const auto & emsdevice : emsdevices) {
if (emsdevice) {
@@ -117,7 +118,7 @@ void EMSESP::fetch_device_values_type(const uint8_t device_type) {
// clears list of recognized devices
void EMSESP::clear_all_devices() {
// temporary removed: clearing the list causes a crash, the associated commands and mqtt should also be removed.
// temporarily removed: clearing the list causes a crash, the associated commands and mqtt should also be removed.
// emsdevices.clear(); // remove entries, but doesn't delete actual devices
}
@@ -132,6 +133,36 @@ uint8_t EMSESP::count_devices(const uint8_t device_type) {
return count;
}
// return total number of devices excluding the Controller
uint8_t EMSESP::count_devices() {
uint8_t count = 0;
for (const auto & emsdevice : emsdevices) {
if (emsdevice) {
count += (emsdevice->device_type() != EMSdevice::DeviceType::CONTROLLER);
}
}
return count;
}
// returns the index of a device if there are more of the same type
// or 0 if there is only one or none
uint8_t EMSESP::device_index(const uint8_t device_type, const uint8_t unique_id) {
if (count_devices(device_type) <= 1) {
return 0; // none or only 1 device exists
}
uint8_t index = 1;
for (const auto & emsdevice : emsdevices) {
if (emsdevice->device_type() == device_type) {
// did we find it?
if (emsdevice->unique_id() == unique_id) {
return index;
}
index++;
}
}
return 0; // didn't find it
}
// scans for new devices
void EMSESP::scan_devices() {
EMSESP::clear_all_devices();
@@ -139,19 +170,20 @@ void EMSESP::scan_devices() {
}
/**
* if thermostat master is 0x18 it handles only ww and hc1, hc2..hc4 handled by devices 0x19..0x1B
* if thermostat master is 0x18 it handles only ww and hc1, hc2..hc8 handled by devices 0x19..0x1F
* we send to right device and match all reads to 0x18
*/
uint8_t EMSESP::check_master_device(const uint8_t device_id, const uint16_t type_id, const bool read) {
if (actual_master_thermostat_ == 0x18) {
uint16_t mon_ids[4] = {0x02A5, 0x02A6, 0x02A7, 0x02A8};
uint16_t set_ids[4] = {0x02B9, 0x02BA, 0x02BB, 0x02BC};
uint16_t summer_ids[4] = {0x02AF, 0x02B0, 0x02B1, 0x02B2};
uint16_t curve_ids[4] = {0x029B, 0x029C, 0x029D, 0x029E};
uint16_t mon_ids[] = {0x02A5, 0x02A6, 0x02A7, 0x02A8, 0x02A9, 0x02AA, 0x02AB, 0x02AC};
uint16_t set_ids[] = {0x02B9, 0x02BA, 0x02BB, 0x02BC, 0x02BD, 0x02BE, 0x02BF, 0x02C0};
uint16_t summer_ids[] = {0x02AF, 0x02B0, 0x02B1, 0x02B2, 0x02B3, 0x02B4, 0x02B5, 0x02B6};
uint16_t curve_ids[] = {0x029B, 0x029C, 0x029D, 0x029E, 0x029F, 0x02A0, 0x02A1, 0x02A2};
uint16_t summer2_ids[] = {0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0476, 0x0477, 0x0478};
uint16_t master_ids[] = {0x02F5, 0x031B, 0x031D, 0x031E, 0x023A, 0x0267, 0x0240};
// look for heating circuits
for (uint8_t i = 0; i < 4; i++) {
if (type_id == mon_ids[i] || type_id == set_ids[i] || type_id == summer_ids[i] || type_id == curve_ids[i]) {
for (uint8_t i = 0; i < sizeof(mon_ids) / 2; i++) {
if (type_id == mon_ids[i] || type_id == set_ids[i] || type_id == summer_ids[i] || type_id == curve_ids[i] || type_id == summer2_ids[i]) {
if (read) {
// receiving telegrams and map all to master thermostat at 0x18 (src manipulated)
return 0x18;
@@ -180,7 +212,7 @@ uint8_t EMSESP::actual_master_thermostat() {
return actual_master_thermostat_;
}
// to watch both type IDs and device IDs
// to watch both type IDs and deviceIDs
void EMSESP::watch_id(uint16_t watch_id) {
watch_id_ = watch_id;
}
@@ -222,20 +254,21 @@ uint8_t EMSESP::bus_status() {
// check if we have Tx issues.
uint32_t total_sent = txservice_.telegram_read_count() + txservice_.telegram_write_count();
uint32_t total_fail = txservice_.telegram_read_fail_count() + txservice_.telegram_write_fail_count();
// nothing sent and also no errors - must be ok
if ((total_sent == 0) && (txservice_.telegram_fail_count() == 0)) {
if ((total_sent == 0) && (total_fail == 0)) {
return BUS_STATUS_CONNECTED;
}
// nothing sent, but have Tx errors
if ((total_sent == 0) && (txservice_.telegram_fail_count() != 0)) {
if ((total_sent == 0) && (total_fail != 0)) {
return BUS_STATUS_TX_ERRORS;
}
// Tx Failure rate > 10%
if (txservice_.telegram_fail_count() < total_sent) {
if (((txservice_.telegram_fail_count() * 100) / total_sent) > EMSbus::EMS_TX_ERROR_LIMIT) {
if (total_fail < total_sent) {
if (((total_fail * 100) / total_sent) > EMSbus::EMS_TX_ERROR_LIMIT) {
return BUS_STATUS_TX_ERRORS;
}
}
@@ -265,13 +298,15 @@ void EMSESP::show_ems(uuid::console::Shell & shell) {
shell.printfln(F("EMS Bus info:"));
EMSESP::webSettingsService.read([&](WebSettings & settings) { shell.printfln(F(" Tx mode: %d"), settings.tx_mode); });
shell.printfln(F(" Bus protocol: %s"), EMSbus::is_ht3() ? F("HT3") : F("Buderus"));
shell.printfln(F(" #recognized EMS devices: %d"), (EMSESP::emsdevices).size());
shell.printfln(F(" #telegrams received: %d"), rxservice_.telegram_count());
shell.printfln(F(" #read requests sent: %d"), txservice_.telegram_read_count());
shell.printfln(F(" #write requests sent: %d"), txservice_.telegram_write_count());
shell.printfln(F(" #incomplete telegrams: %d"), rxservice_.telegram_error_count());
shell.printfln(F(" #tx fails (after %d retries): %d"), TxService::MAXIMUM_TX_RETRIES, txservice_.telegram_fail_count());
shell.printfln(F(" #read fails (after %d retries): %d"), TxService::MAXIMUM_TX_RETRIES, txservice_.telegram_read_fail_count());
shell.printfln(F(" #write fails (after %d retries): %d"), TxService::MAXIMUM_TX_RETRIES, txservice_.telegram_write_fail_count());
shell.printfln(F(" Rx line quality: %d%%"), rxservice_.quality());
shell.printfln(F(" Tx line quality: %d%%"), txservice_.quality());
shell.printfln(F(" Tx line quality: %d%%"), (txservice_.read_quality() + txservice_.read_quality()) / 2);
shell.println();
}
@@ -315,7 +350,7 @@ void EMSESP::show_ems(uuid::console::Shell & shell) {
// generate_values_json is called in verbose mode
void EMSESP::show_device_values(uuid::console::Shell & shell) {
if (emsdevices.empty()) {
shell.printfln(F("No EMS devices detected. Try using 'scan devices' from the ems menu."));
shell.printfln(F("No EMS devices detected."));
shell.println();
return;
}
@@ -325,11 +360,12 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) {
for (const auto & emsdevice : emsdevices) {
if ((emsdevice) && (emsdevice->device_type() == device_class.first)) {
// print header
shell.printfln(F("%s: %s"), emsdevice->device_type_name().c_str(), emsdevice->to_string().c_str());
shell.printfln(F("%s: %s (%d)"), emsdevice->device_type_name().c_str(), emsdevice->to_string().c_str(), emsdevice->count_entities());
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN); // use max size
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XXLARGE_DYN); // use max size
JsonObject json = doc.to<JsonObject>();
emsdevice->generate_values_json(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::API_VERBOSE); // verbose mode and nested
// print line
uint8_t id = 0;
@@ -351,6 +387,9 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) {
// if there is a uom print it
std::string uom = emsdevice->get_value_uom(key);
if (uom == "°C" && EMSESP::system_.fahrenheit()) {
uom = "°F";
}
if (!uom.empty()) {
shell.print(' ');
shell.print(uom);
@@ -366,24 +405,64 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) {
}
}
// show Dallas temperature sensors
// show Dallas temperature sensors and Analog sensors
void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
if (!have_sensors()) {
return;
if (dallassensor_.have_sensors()) {
shell.printfln(F("Temperature sensors:"));
char s[10];
char s2[10];
uint8_t fahrenheit = EMSESP::system_.fahrenheit() ? 2 : 0;
for (const auto & sensor : dallassensor_.sensors()) {
if (Helpers::hasValue(sensor.temperature_c)) {
shell.printfln(F(" %s: %s%s °%c%s (offset %s, ID: %s)"),
sensor.name().c_str(),
COLOR_BRIGHT_GREEN,
Helpers::render_value(s, sensor.temperature_c, 10, fahrenheit),
(fahrenheit == 0) ? 'C' : 'F',
COLOR_RESET,
Helpers::render_value(s2, sensor.offset(), 10, fahrenheit),
sensor.id_str().c_str());
} else {
shell.printfln(F(" %s (offset %s, ID: %s)"),
sensor.name().c_str(),
Helpers::render_value(s, sensor.offset(), 10, fahrenheit),
sensor.id_str().c_str());
}
}
shell.println();
}
shell.printfln(F("Dallas temperature sensors:"));
uint8_t i = 1;
char s[7];
char s2[7];
for (const auto & device : sensor_devices()) {
shell.printfln(F(" Sensor %d, ID: %s, Temperature: %s °C (offset %s)"),
i++,
device.to_string().c_str(),
Helpers::render_value(s, device.temperature_c, 10),
Helpers::render_value(s2, device.offset(), 10));
if (analogsensor_.have_sensors()) {
char s[10];
char s2[10];
shell.printfln(F("Analog sensors:"));
for (const auto & sensor : analogsensor_.sensors()) {
switch (sensor.type()) {
case AnalogSensor::AnalogType::ADC:
shell.printfln(F(" %s: %s%s %s%s (Type: ADC, Factor: %s, Offset: %d)"),
sensor.name().c_str(),
COLOR_BRIGHT_GREEN,
Helpers::render_value(s, sensor.value(), 2),
EMSdevice::uom_to_string(sensor.uom()).c_str(),
COLOR_RESET,
Helpers::render_value(s2, sensor.factor(), 4),
sensor.offset());
break;
default:
case AnalogSensor::AnalogType::DIGITAL_IN:
case AnalogSensor::AnalogType::COUNTER:
shell.printfln(F(" %s: %s%d%s (Type: %s)"),
sensor.name().c_str(),
COLOR_BRIGHT_GREEN,
(uint16_t)sensor.value(), // as int
COLOR_RESET,
sensor.type() == AnalogSensor::AnalogType::COUNTER ? "Counter" : "Digital in");
break;
}
}
shell.println();
}
shell.println();
}
// MQTT publish everything, immediately
@@ -393,13 +472,14 @@ void EMSESP::publish_all(bool force) {
reset_mqtt_ha();
return;
}
if (Mqtt::connected()) {
publish_device_values(EMSdevice::DeviceType::BOILER);
publish_device_values(EMSdevice::DeviceType::THERMOSTAT);
publish_device_values(EMSdevice::DeviceType::SOLAR);
publish_device_values(EMSdevice::DeviceType::MIXER);
publish_other_values();
publish_sensor_values(true);
publish_other_values(); // switch and heat pump
publish_sensor_values(true); // includes dallas and analog sensors
system_.send_heartbeat();
}
}
@@ -409,10 +489,12 @@ void EMSESP::publish_all_loop() {
if (!Mqtt::connected() || !publish_all_idx_) {
return;
}
// wait for free queue before sending next message, v3 queues HA-messages
// wait for free queue before sending next message, HA-messages are also queued
if (!Mqtt::is_empty()) {
return;
}
switch (publish_all_idx_++) {
case 1:
publish_device_values(EMSdevice::DeviceType::BOILER);
@@ -427,7 +509,7 @@ void EMSESP::publish_all_loop() {
publish_device_values(EMSdevice::DeviceType::MIXER);
break;
case 5:
publish_other_values();
publish_other_values(); // switch and heat pump
break;
case 6:
publish_sensor_values(true, true);
@@ -444,7 +526,8 @@ void EMSESP::publish_all_loop() {
}
}
// force HA to re-create all the devices
// force HA to re-create all the devices next time they are detected
// also removes the old HA topics
void EMSESP::reset_mqtt_ha() {
if (!Mqtt::ha_enabled()) {
return;
@@ -454,27 +537,38 @@ void EMSESP::reset_mqtt_ha() {
emsdevice->ha_config_clear();
}
dallassensor_.reload();
analogsensor_.reload();
}
// create json doc for the devices values and add to MQTT publish queue
// this will also create the HA /config topic
// generate_values_json is called to build the device value (dv) object array
void EMSESP::publish_device_values(uint8_t device_type) {
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN); // use max size
JsonObject json = doc.to<JsonObject>();
bool need_publish = false;
bool nested = (Mqtt::nested_format() == 1); // 1 is nested, 2 is single
bool nested = (Mqtt::is_nested());
// group by device type
for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_type)) {
// specially for HA
// we may have some RETAINED /config topics that reference fields in the data payloads that no longer exist
// remove them immediately to prevent HA from complaining
// we need to do this first before the data payload is published, and only done once!
if (Mqtt::ha_enabled() && emsdevice->ha_config_firstrun()) {
emsdevice->ha_config_clear();
emsdevice->ha_config_firstrun(false);
}
// if its a boiler, generate json for each group and publish it directly. not nested
if (device_type == DeviceType::BOILER) {
if (emsdevice->generate_values_json(json, DeviceValueTAG::TAG_BOILER_DATA, false, EMSdevice::OUTPUT_TARGET::MQTT)) {
if (emsdevice->generate_values(json, DeviceValueTAG::TAG_BOILER_DATA, false, EMSdevice::OUTPUT_TARGET::MQTT)) {
Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_BOILER_DATA), json);
}
doc.clear();
if (emsdevice->generate_values_json(json, DeviceValueTAG::TAG_DEVICE_DATA_WW, false, EMSdevice::OUTPUT_TARGET::MQTT)) {
if (emsdevice->generate_values(json, DeviceValueTAG::TAG_DEVICE_DATA_WW, false, EMSdevice::OUTPUT_TARGET::MQTT)) {
Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_DEVICE_DATA_WW), json);
}
need_publish = false;
@@ -485,15 +579,14 @@ void EMSESP::publish_device_values(uint8_t device_type) {
// only publish the single master thermostat
if (emsdevice->device_id() == EMSESP::actual_master_thermostat()) {
if (nested) {
need_publish |= emsdevice->generate_values_json(json, DeviceValueTAG::TAG_NONE, true, EMSdevice::OUTPUT_TARGET::MQTT); // nested
need_publish |= emsdevice->generate_values(json, DeviceValueTAG::TAG_NONE, true, EMSdevice::OUTPUT_TARGET::MQTT); // nested
} else {
if (emsdevice->generate_values_json(json, DeviceValueTAG::TAG_THERMOSTAT_DATA, false, EMSdevice::OUTPUT_TARGET::MQTT)) { // not nested
if (emsdevice->generate_values(json, DeviceValueTAG::TAG_THERMOSTAT_DATA, false, EMSdevice::OUTPUT_TARGET::MQTT)) { // not nested
Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_NONE), json);
}
doc.clear();
for (uint8_t hc_tag = TAG_HC1; hc_tag <= DeviceValueTAG::TAG_HC4; hc_tag++) {
if (emsdevice->generate_values_json(json, hc_tag, false, EMSdevice::OUTPUT_TARGET::MQTT)) { // not nested
for (uint8_t hc_tag = DeviceValueTAG::TAG_HC1; hc_tag <= DeviceValueTAG::TAG_HC8; hc_tag++) {
if (emsdevice->generate_values(json, hc_tag, false, EMSdevice::OUTPUT_TARGET::MQTT)) { // not nested
Mqtt::publish(Mqtt::tag_to_topic(device_type, hc_tag), json);
}
doc.clear();
@@ -506,10 +599,10 @@ void EMSESP::publish_device_values(uint8_t device_type) {
// Mixer
else if (device_type == DeviceType::MIXER) {
if (nested) {
need_publish |= emsdevice->generate_values_json(json, DeviceValueTAG::TAG_NONE, true, EMSdevice::OUTPUT_TARGET::MQTT); // nested
need_publish |= emsdevice->generate_values(json, DeviceValueTAG::TAG_NONE, true, EMSdevice::OUTPUT_TARGET::MQTT); // nested
} else {
for (uint8_t hc_tag = TAG_HC1; hc_tag <= DeviceValueTAG::TAG_WWC4; hc_tag++) {
if (emsdevice->generate_values_json(json, hc_tag, false, EMSdevice::OUTPUT_TARGET::MQTT)) { // not nested
for (uint8_t hc_tag = DeviceValueTAG::TAG_HC1; hc_tag <= DeviceValueTAG::TAG_WWC4; hc_tag++) {
if (emsdevice->generate_values(json, hc_tag, false, EMSdevice::OUTPUT_TARGET::MQTT)) { // not nested
Mqtt::publish(Mqtt::tag_to_topic(device_type, hc_tag), json);
}
doc.clear();
@@ -519,18 +612,21 @@ void EMSESP::publish_device_values(uint8_t device_type) {
} else {
// for all other devices add the values to the json
need_publish |= emsdevice->generate_values_json(json, DeviceValueTAG::TAG_NONE, true, EMSdevice::OUTPUT_TARGET::MQTT); // nested
need_publish |= emsdevice->generate_values(json, DeviceValueTAG::TAG_NONE, true, EMSdevice::OUTPUT_TARGET::MQTT); // nested
}
}
// if we're using HA, done is checked for each sensor in devices
// we want to create the /config topic after the data payload to prevent HA from throwing up a warning
if (Mqtt::ha_enabled()) {
emsdevice->publish_mqtt_ha_entity_config(); // create the configs for each value as a sensor
emsdevice->publish_mqtt_ha_entity_config();
}
}
// publish it under a single topic, only if we have data to publish
if (need_publish) {
if (doc.overflowed()) {
LOG_WARNING(F("MQTT buffer overflow, please use individual topics"));
}
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "%s_data", EMSdevice::device_type_2_device_name(device_type).c_str());
Mqtt::publish(topic, json);
@@ -543,13 +639,18 @@ void EMSESP::publish_other_values() {
publish_device_values(EMSdevice::DeviceType::HEATPUMP);
}
// publish both the dallas and analog sensor values
void EMSESP::publish_sensor_values(const bool time, const bool force) {
if (!dallas_enabled()) {
return;
if (dallas_enabled()) {
if (dallassensor_.updated_values() || time || force) {
dallassensor_.publish_values(force);
}
}
if (dallassensor_.updated_values() || time || force) {
dallassensor_.publish_values(force);
if (analog_enabled()) {
if (analogsensor_.updated_values() || time || force) {
analogsensor_.publish_values(force);
}
}
}
@@ -586,23 +687,14 @@ bool EMSESP::get_device_value_info(JsonObject & root, const char * cmd, const in
// specific for the dallassensor
if (devicetype == DeviceType::DALLASSENSOR) {
uint8_t i = 1;
for (const auto & sensor : EMSESP::sensor_devices()) {
char sensorID[10];
snprintf(sensorID, 10, "sensor%d", i++);
if ((strcmp(cmd, sensorID) == 0) || (strcmp(cmd, Helpers::toLower(sensor.to_string()).c_str()) == 0)) {
root["name"] = sensor.to_string();
if (Helpers::hasValue(sensor.temperature_c)) {
root["value"] = (float)(sensor.temperature_c) / 10;
}
root["type"] = F_(number);
root["min"] = -55;
root["max"] = 125;
root["unit"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES);
root["writeable"] = false;
return true;
}
}
EMSESP::dallassensor_.get_value_info(root, cmd, id);
return true;
}
// analog sensor
if (devicetype == DeviceType::ANALOGSENSOR) {
EMSESP::analogsensor_.get_value_info(root, cmd, id);
return true;
}
return false;
@@ -628,6 +720,7 @@ std::string EMSESP::pretty_telegram(std::shared_ptr<const Telegram> telegram) {
uint8_t offset = telegram->offset;
// find name for src and dest by looking up known devices
std::string src_name("");
std::string dest_name("");
std::string type_name("");
@@ -674,32 +767,13 @@ std::string EMSESP::pretty_telegram(std::shared_ptr<const Telegram> telegram) {
direction = read_flash_string(F("->"));
}
std::string str(200, '\0');
std::string str;
str.reserve(200);
str = src_name + "(" + Helpers::hextoa(src) + ") " + direction + " " + dest_name + "(" + Helpers::hextoa(dest) + "), " + type_name + "("
+ Helpers::hextoa(telegram->type_id) + "), data: " + telegram->to_string_message();
if (offset) {
snprintf(&str[0],
str.capacity() + 1,
"%s(0x%02X) %s %s(0x%02X), %s(0x%02X), data: %s (offset %d)",
src_name.c_str(),
src,
direction.c_str(),
dest_name.c_str(),
dest,
type_name.c_str(),
telegram->type_id,
telegram->to_string_message().c_str(),
offset);
} else {
snprintf(&str[0],
str.capacity() + 1,
"%s(0x%02X) %s %s(0x%02X), %s(0x%02X), data: %s",
src_name.c_str(),
src,
direction.c_str(),
dest_name.c_str(),
dest,
type_name.c_str(),
telegram->type_id,
telegram->to_string_message().c_str());
str += " (offset " + Helpers::itoa(offset) + ")";
}
return str;
@@ -711,8 +785,8 @@ std::string EMSESP::pretty_telegram(std::shared_ptr<const Telegram> telegram) {
* Junkers has 15 bytes of data
* each byte is a bitmask for which devices are active
* byte 1 = 0x08 - 0x0F, byte 2 = 0x10 - 0x17, etc...
* e.g. in example above 1st byte = x0B = b1011 so we have device ids 0x08, 0x09, 0x011
* and 2nd byte = x80 = b1000 b0000 = device id 0x17
* e.g. in example above 1st byte = x0B = b1011 so we have deviceIDs 0x08, 0x09, 0x011
* and 2nd byte = x80 = b1000 b0000 = deviceID 0x17
*/
void EMSESP::process_UBADevices(std::shared_ptr<const Telegram> telegram) {
// exit it length is incorrect (must be 13 or 15 bytes long)
@@ -748,8 +822,7 @@ void EMSESP::process_version(std::shared_ptr<const Telegram> telegram) {
if (telegram->message_length < 3) {
// for empty telegram add device with empty product, version and brand
if (!telegram->message_length) {
std::string version = "00.00";
(void)add_device(telegram->src, 0, version, 0);
(void)add_device(telegram->src, 0, "00.00", 0);
}
return;
}
@@ -766,12 +839,12 @@ void EMSESP::process_version(std::shared_ptr<const Telegram> telegram) {
}
// extra details from the telegram
uint8_t device_id = telegram->src; // device ID
uint8_t product_id = telegram->message_data[offset]; // product ID
uint8_t device_id = telegram->src; // deviceID
uint8_t product_id = telegram->message_data[offset]; // productID
// get version as XX.XX
std::string version(6, '\0');
snprintf(&version[0], version.capacity() + 1, "%02d.%02d", telegram->message_data[offset + 1], telegram->message_data[offset + 2]);
char version[8];
snprintf(version, sizeof(version), "%02d.%02d", telegram->message_data[offset + 1], telegram->message_data[offset + 2]);
// some devices store the protocol type (HT3, Buderus) in the last byte
uint8_t brand;
@@ -785,14 +858,14 @@ void EMSESP::process_version(std::shared_ptr<const Telegram> telegram) {
(void)add_device(device_id, product_id, version, brand);
}
// find the device object that matches the device ID and see if it has a matching telegram type handler
// find the device object that matches the deviceID and see if it has a matching telegram type handler
// but only process if the telegram is sent to us or it's a broadcast (dest=0x00=all)
// We also check for common telgram types, like the Version(0x02)
// returns false if there are none found
bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
// if watching or reading...
if ((telegram->type_id == read_id_) && (telegram->dest == txservice_.ems_bus_id())) {
LOG_NOTICE(F("%s"), pretty_telegram(telegram).c_str());
LOG_INFO(F("%s"), pretty_telegram(telegram).c_str());
if (Mqtt::send_response()) {
publish_response(telegram);
}
@@ -813,7 +886,9 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
}
// only process broadcast telegrams or ones sent to us on request
if ((telegram->dest != 0x00) && (telegram->dest != rxservice_.ems_bus_id())) {
// if ((telegram->dest != 0x00) && (telegram->dest != rxservice_.ems_bus_id())) {
if (telegram->operation == Telegram::Operation::RX_READ) {
// LOG_DEBUG(F("read telegram received, not processing"));
return false;
}
@@ -837,18 +912,23 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
bool knowndevice = false;
for (const auto & emsdevice : emsdevices) {
if (emsdevice) {
if (emsdevice->is_device_id(telegram->src)) {
if (emsdevice->is_device_id(telegram->src) || emsdevice->is_device_id(telegram->dest)) {
knowndevice = true;
found = emsdevice->handle_telegram(telegram);
// if we correctly processes the telegram follow up with sending it via MQTT if needed
if (found && emsdevice->is_device_id(telegram->dest)) {
LOG_DEBUG(F("Process setting 0x%02X for device 0x%02X"), telegram->type_id, telegram->dest);
}
// if we correctly processed the telegram then follow up with sending it via MQTT (if enabled)
if (found && Mqtt::connected()) {
if ((mqtt_.get_publish_onchange(emsdevice->device_type()) && emsdevice->has_update())
|| (telegram->type_id == publish_id_ && telegram->dest == txservice_.ems_bus_id())) {
if (telegram->type_id == publish_id_) {
publish_id_ = 0;
}
emsdevice->has_update(false); // reset flag
publish_device_values(emsdevice->device_type()); // publish to MQTT if we explicitly have too
emsdevice->has_update(false); // reset flag
if (!Mqtt::publish_single()) {
publish_device_values(emsdevice->device_type()); // publish to MQTT if we explicitly have too
}
}
}
if (wait_validate_ == telegram->type_id) {
@@ -909,7 +989,7 @@ void EMSESP::show_devices(uuid::console::Shell & shell) {
for (const auto & device_class : EMSFactory::device_handlers()) {
for (const auto & emsdevice : emsdevices) {
if ((emsdevice) && (emsdevice->device_type() == device_class.first)) {
shell.printf(F("(%d) %s: %s"), emsdevice->unique_id(), emsdevice->device_type_name().c_str(), emsdevice->to_string().c_str());
shell.printf(F("%s: %s"), emsdevice->device_type_name().c_str(), emsdevice->to_string().c_str());
if ((num_thermostats > 1) && (emsdevice->device_type() == EMSdevice::DeviceType::THERMOSTAT)
&& (emsdevice->device_id() == actual_master_thermostat())) {
shell.printf(F(" **master device**"));
@@ -928,7 +1008,7 @@ void EMSESP::show_devices(uuid::console::Shell & shell) {
// add a new or update existing EMS device to our list of active EMS devices
// if its not in our database, we don't add it
bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::string & version, const uint8_t brand) {
bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const char * version, const uint8_t brand) {
// don't add ourselves!
if (device_id == rxservice_.ems_bus_id()) {
return false;
@@ -938,7 +1018,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::
for (const auto & emsdevice : emsdevices) {
if (emsdevice) {
if (emsdevice->is_device_id(device_id)) {
LOG_DEBUG(F("Updating details for already active device ID 0x%02X"), device_id);
LOG_DEBUG(F("Updating details for already active deviceID 0x%02X"), device_id);
emsdevice->product_id(product_id);
emsdevice->version(version);
// only set brand if it doesn't already exist
@@ -962,7 +1042,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::
Device_record * device_p = nullptr;
for (auto & device : device_library_) {
if (device.product_id == product_id) {
// sometimes boilers share the same product id as controllers
// sometimes boilers share the same productID as controllers
// so only add boilers if the device_id is 0x08, which is fixed for EMS
if (device.device_type == DeviceType::BOILER) {
if (device_id == EMSdevice::EMS_DEVICE_ID_BOILER
@@ -978,9 +1058,9 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::
}
}
// if we don't recognize the product ID report it and add as a generic device
// if we don't recognize the productID report it and add as a generic device
if (device_p == nullptr) {
LOG_NOTICE(F("Unrecognized EMS device (device ID 0x%02X, product ID %d). Please report on GitHub."), device_id, product_id);
LOG_NOTICE(F("Unrecognized EMS device (deviceID 0x%02X, productID %d). Please report on GitHub."), device_id, product_id);
std::string name("unknown");
emsdevices.push_back(
EMSFactory::add(DeviceType::GENERIC, device_id, product_id, version, name, DeviceFlags::EMS_DEVICE_FLAG_NONE, EMSdevice::Brand::NO_BRAND));
@@ -1019,16 +1099,30 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::
device_type = DeviceType::CONNECT;
} else if (device_id == 0x0E) {
name = "converter"; // generic
} else if (device_id == 0x0F) {
name = "clock"; // generic
} else if (device_id == 0x08) {
name = "generic boiler";
device_type = DeviceType::BOILER;
flags = DeviceFlags::EMS_DEVICE_FLAG_HEATPUMP;
LOG_WARNING(F("Unknown EMS boiler. Using generic profile. Please report on GitHub."));
} else {
LOG_WARNING(F("Unrecognized EMS device (device ID 0x%02X, no product ID). Please report on GitHub."), device_id);
return false;
}
}
LOG_DEBUG(F("Adding new device %s (device ID 0x%02X, product ID %d, version %s)"), name.c_str(), device_id, product_id, version.c_str());
LOG_DEBUG(F("Adding new device %s (deviceID 0x%02X, productID %d, version %s)"), name.c_str(), device_id, product_id, version);
emsdevices.push_back(EMSFactory::add(device_type, device_id, product_id, version, name, flags, brand));
// assign a unique ID. Note that this is not actual unique after a restart as it's dependent on the order that devices are found
emsdevices.back()->unique_id(++unique_id_count_);
// sort devices based on type
std::sort(emsdevices.begin(), emsdevices.end(), [](const std::unique_ptr<emsesp::EMSdevice> & a, const std::unique_ptr<emsesp::EMSdevice> & b) {
return a->device_type() < b->device_type();
});
fetch_device_values(device_id); // go and fetch its data
// add command commands for all devices, except for connect, controller and gateway
@@ -1066,7 +1160,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::
Mqtt::subscribe(device_type, EMSdevice::device_type_2_device_name(device_type) + "/#", nullptr);
// Print to LOG showing we've added a new device
LOG_INFO(F("Recognized new %s with device ID 0x%02X"), EMSdevice::device_type_2_device_name(device_type).c_str(), device_id);
LOG_INFO(F("Recognized new %s with deviceID 0x%02X"), EMSdevice::device_type_2_device_name(device_type).c_str(), device_id);
return true;
}
@@ -1094,9 +1188,9 @@ 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 <= 4) {
if (id >= 1 && id <= 8) {
tag = DeviceValueTAG::TAG_HC1 + id - 1;
} else if (id >= 9 && id <= 10) {
} else if (id >= 9 && id <= 12) {
tag = DeviceValueTAG::TAG_WWC1 + id - 9;
} else if (id == -1 || id == 0) {
tag = DeviceValueTAG::TAG_NONE;
@@ -1107,7 +1201,7 @@ bool EMSESP::command_info(uint8_t device_type, JsonObject & output, const int8_t
for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_type)
&& ((device_type != DeviceType::THERMOSTAT) || (emsdevice->device_id() == EMSESP::actual_master_thermostat()))) {
has_value |= emsdevice->generate_values_json(output, tag, (id < 1), output_target); // use nested for id -1 and 0
has_value |= emsdevice->generate_values(output, tag, (id < 1), output_target); // use nested for id -1 and 0
}
}
@@ -1262,7 +1356,6 @@ void EMSESP::send_raw_telegram(const char * data) {
// the services must be loaded in the correct order
void EMSESP::start() {
Serial.begin(115200);
Serial.println();
// start the file system
#ifndef EMSESP_STANDALONE
@@ -1272,29 +1365,34 @@ void EMSESP::start() {
}
#endif
esp8266React.begin(); // loads system settings (network, mqtt, etc)
esp8266React.begin(); // loads core system services settings (network, mqtt, ap, ntp etc)
system_.check_upgrade(); // do any system upgrades
webSettingsService.begin(); // load EMS-ESP Application settings...
system_.get_settings(); // ...and store some of the settings locally for future reference
console_.start(system_.telnet_enabled()); // telnet and serial console, from here we can start logging events
webLogService.start(); // start web log service
webCustomizationService.begin(); // load the customizations
system_.check_upgrade(); // do any system upgrades
// welcome message
LOG_INFO(F("Starting EMS-ESP version %s (hostname: %s)"), EMSESP_APP_VERSION, system_.hostname().c_str());
LOG_INFO(F("Configuring for interface board profile %s"), system_.board_profile().c_str());
// start all the EMS-ESP services
mqtt_.start(); // mqtt init
system_.start(heap_start); // starts commands, led, adc, button, network, syslog & uart
shower_.start(); // initialize shower timer and shower alert
dallassensor_.start(); // Dallas external sensors
analogsensor_.start(); // Analog external sensors
webServer.begin(); // start the web server
// emsdevices.reserve(5); // reserve space for initially 5 devices to avoid mem frag issues
LOG_INFO(F("Last system reset reason Core0: %s, Core1: %s"), system_.reset_reason(0).c_str(), system_.reset_reason(1).c_str());
// Load our library of known devices into stack mem. Names are stored in Flash memory (takes up about 1kb)
device_library_ = {
#include "device_library.h"
};
console_.start(); // telnet and serial console
webSettingsService.begin(); // load EMS-ESP specific settings, like GPIO configurations
mqtt_.start(); // mqtt init
system_.start(heap_start); // starts commands, led, adc, button, network, syslog & uart
shower_.start(); // initialize shower timer and shower alert
dallassensor_.start(); // dallas external sensors
webServer.begin(); // start web server
webLogService.start(); // start web log service
emsdevices.reserve(5); // reserve space for initially 5 devices to avoid mem frag issues
LOG_INFO(F("Last system reset reason Core0: %s, Core1: %s"), system_.reset_reason(0).c_str(), system_.reset_reason(1).c_str());
LOG_INFO(F("EMS Device library loaded with %d records"), device_library_.size());
LOG_INFO(F("EMS device library loaded with %d records"), device_library_.size());
#if defined(EMSESP_STANDALONE)
Mqtt::on_connect(); // simulate an MQTT connection
@@ -1312,6 +1410,7 @@ void EMSESP::loop() {
rxservice_.loop(); // process any incoming Rx telegrams
shower_.loop(); // check for shower on/off
dallassensor_.loop(); // read dallas sensor temperatures
analogsensor_.loop(); // read analog sensor values
publish_all_loop(); // with HA messages in parts to avoid flooding the mqtt queue
mqtt_.loop(); // sends out anything in the MQTT queue

View File

@@ -22,6 +22,10 @@
#include <vector>
#include <string>
#include <functional>
#include <deque>
#include <unordered_map>
#include <list>
#include <ArduinoJson.h>
@@ -38,15 +42,18 @@
#include "web/WebStatusService.h"
#include "web/WebDataService.h"
#include "web/WebSettingsService.h"
#include "web/WebCustomizationService.h"
#include "web/WebAPIService.h"
#include "web/WebLogService.h"
#include "emsdevicevalue.h"
#include "emsdevice.h"
#include "emsfactory.h"
#include "telegram.h"
#include "mqtt.h"
#include "system.h"
#include "dallassensor.h"
#include "analogsensor.h"
#include "console.h"
#include "shower.h"
#include "roomcontrol.h"
@@ -55,7 +62,11 @@
#define WATCH_ID_NONE 0 // no watch id set
#define EMSESP_JSON_SIZE_HA_CONFIG 768 // for HA config payloads, using StaticJsonDocument
#ifndef EMSESP_STANDALONE
#define EMSESP_JSON_SIZE_HA_CONFIG 1024 // for HA config payloads, using StaticJsonDocument
#else
#define EMSESP_JSON_SIZE_HA_CONFIG 2024 // for HA config payloads, using StaticJsonDocument
#endif
#define EMSESP_JSON_SIZE_SMALL 256 // for smaller json docs, using StaticJsonDocument
#define EMSESP_JSON_SIZE_MEDIUM 768 // for medium json docs from ems devices, using StaticJsonDocument
#define EMSESP_JSON_SIZE_LARGE 1024 // for large json docs from ems devices, like boiler or thermostat data, using StaticJsonDocument
@@ -68,7 +79,8 @@
#define EMSESP_JSON_SIZE_XLARGE_DYN 16384 // for very very large json docs, using DynamicJsonDocument
#endif
#define EMSESP_JSON_SIZE_XXLARGE_DYN 16384 // for extra very very large json docs, using DynamicJsonDocument
#define EMSESP_JSON_SIZE_XXLARGE_DYN 16384 // for extra very very large json docs, using DynamicJsonDocument
#define EMSESP_JSON_SIZE_XXXLARGE_DYN 20480 // web output (maybe for 4 hc)
// helpers for callback functions
#define MAKE_PF_CB(__f) [&](std::shared_ptr<const Telegram> t) { __f(t); } // for Process Function callbacks to EMSDevice::process_function_p
@@ -76,6 +88,11 @@
namespace emsesp {
using DeviceValueUOM = emsesp::DeviceValue::DeviceValueUOM;
using DeviceValueType = emsesp::DeviceValue::DeviceValueType;
using DeviceValueState = emsesp::DeviceValue::DeviceValueState;
using DeviceValueTAG = emsesp::DeviceValue::DeviceValueTAG;
class Shower; // forward declaration for compiler
class EMSESP {
@@ -113,6 +130,8 @@ class EMSESP {
static bool device_exists(const uint8_t device_id);
static uint8_t count_devices(const uint8_t device_type);
static uint8_t count_devices();
static uint8_t device_index(const uint8_t device_type, const uint8_t unique_id);
static uint8_t actual_master_thermostat();
static void actual_master_thermostat(const uint8_t device_id);
@@ -130,40 +149,12 @@ class EMSESP {
static void incoming_telegram(uint8_t * data, const uint8_t length);
static const std::vector<DallasSensor::Sensor> sensor_devices() {
return dallassensor_.sensors();
}
static bool have_sensors() {
return (!(dallassensor_.sensors().empty()));
}
static uint32_t sensor_reads() {
return dallassensor_.reads();
}
static uint32_t sensor_fails() {
return dallassensor_.fails();
}
static bool dallas_enabled() {
return (dallassensor_.dallas_enabled());
}
static uint8_t bool_format() {
return bool_format_;
}
static void bool_format(uint8_t format) {
bool_format_ = format;
}
static uint8_t enum_format() {
return enum_format_;
}
static void enum_format(uint8_t format) {
enum_format_ = format;
static bool analog_enabled() {
return (analogsensor_.analog_enabled());
}
enum Watch : uint8_t { WATCH_OFF, WATCH_ON, WATCH_RAW, WATCH_UNKNOWN };
@@ -178,12 +169,14 @@ class EMSESP {
watch_id_ = 0; // reset watch id if watch is disabled
}
}
static uint8_t watch() {
return watch_;
}
static void set_read_id(uint16_t id) {
read_id_ = id;
}
static bool wait_validate() {
return (wait_validate_ != 0);
}
@@ -214,7 +207,7 @@ class EMSESP {
static void fetch_device_values_type(const uint8_t device_type);
static bool valid_device(const uint8_t device_id);
static bool add_device(const uint8_t device_id, const uint8_t product_id, std::string & version, const uint8_t brand);
static bool add_device(const uint8_t device_id, const uint8_t product_id, const char * version, const uint8_t brand);
static void scan_devices();
static void clear_all_devices();
@@ -224,18 +217,20 @@ class EMSESP {
static Mqtt mqtt_;
static System system_;
static DallasSensor dallassensor_;
static AnalogSensor analogsensor_;
static Console console_;
static Shower shower_;
static RxService rxservice_;
static TxService txservice_;
// web controllers
static ESP8266React esp8266React;
static WebSettingsService webSettingsService;
static WebStatusService webStatusService;
static WebDataService webDataService;
static WebAPIService webAPIService;
static WebLogService webLogService;
static ESP8266React esp8266React;
static WebSettingsService webSettingsService;
static WebStatusService webStatusService;
static WebDataService webDataService;
static WebAPIService webAPIService;
static WebLogService webLogService;
static WebCustomizationService webCustomizationService;
static uuid::log::Logger logger();
@@ -245,17 +240,16 @@ class EMSESP {
static uuid::log::Logger logger_;
static std::string device_tostring(const uint8_t device_id);
static void process_UBADevices(std::shared_ptr<const Telegram> telegram);
static void process_version(std::shared_ptr<const Telegram> telegram);
static void publish_response(std::shared_ptr<const Telegram> telegram);
static void publish_all_loop();
static bool command_info(uint8_t device_type, JsonObject & output, const int8_t id, const uint8_t output_target);
static bool command_commands(uint8_t device_type, JsonObject & output, const int8_t id);
static bool command_entities(uint8_t device_type, JsonObject & output, const int8_t id);
static void process_UBADevices(std::shared_ptr<const Telegram> telegram);
static void process_version(std::shared_ptr<const Telegram> telegram);
static void publish_response(std::shared_ptr<const Telegram> telegram);
static void publish_all_loop();
static bool command_info(uint8_t device_type, JsonObject & output, const int8_t id, const uint8_t output_target);
static bool command_commands(uint8_t device_type, JsonObject & output, const int8_t id);
static bool command_entities(uint8_t device_type, JsonObject & output, const int8_t id);
static constexpr uint32_t EMS_FETCH_FREQUENCY = 60000; // check every minute
static uint32_t last_fetch_;
static constexpr uint8_t EMS_WAIT_KM_TIMEOUT = 60; // wait one minute
struct Device_record {
uint8_t product_id;
@@ -275,12 +269,9 @@ class EMSESP {
static uint8_t publish_all_idx_;
static uint8_t unique_id_count_;
static bool trace_raw_;
static uint8_t bool_format_;
static uint8_t enum_format_;
static uint16_t wait_validate_;
static bool wait_km_;
static constexpr uint8_t EMS_WAIT_KM_TIMEOUT = 60; // wait one minute
static uint32_t last_fetch_;
};
} // namespace emsesp

View File

@@ -23,6 +23,7 @@
#include "dallassensor.h"
#include "version.h"
#include "default_settings.h"
#include <ESP8266React.h>
#include <uuid/log.h>

View File

@@ -55,12 +55,12 @@ class EMSFactory {
}
// Construct derived class returning an unique ptr
static auto add(const uint8_t device_type, uint8_t device_id, uint8_t product_id, std::string & version, std::string & name, uint8_t flags, uint8_t brand)
static auto add(const uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, std::string & name, uint8_t flags, uint8_t brand)
-> std::unique_ptr<EMSdevice> {
return std::unique_ptr<EMSdevice>(EMSFactory::makeRaw(device_type, device_id, product_id, version, name, flags, brand));
}
virtual auto construct(uint8_t device_type, uint8_t device_id, uint8_t product_id, std::string & version, std::string & name, uint8_t flags, uint8_t brand) const
virtual auto construct(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, std::string & name, uint8_t flags, uint8_t brand) const
-> EMSdevice * = 0;
private:
@@ -72,7 +72,7 @@ class EMSFactory {
// Construct derived class returning a raw pointer
// find which EMS device it is and use that class
static auto makeRaw(const uint8_t device_type, uint8_t device_id, uint8_t product_id, std::string & version, std::string & name, uint8_t flags, uint8_t brand)
static auto makeRaw(const uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, std::string & name, uint8_t flags, uint8_t brand)
-> EMSdevice * {
auto it = EMSFactory::getRegister().find(device_type);
if (it != EMSFactory::getRegister().end()) {
@@ -90,7 +90,7 @@ class ConcreteEMSFactory : EMSFactory {
EMSFactory::registerFactory(device_type, this);
}
auto construct(uint8_t device_type, uint8_t device_id, uint8_t product_id, std::string & version, std::string & name, uint8_t flags, uint8_t brand) const
auto construct(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, std::string & name, uint8_t flags, uint8_t brand) const
-> EMSdevice * {
return new DerivedClass(device_type, device_id, product_id, version, name, flags, brand);
}

View File

@@ -33,6 +33,35 @@ char * Helpers::hextoa(char * result, const uint8_t value) {
return result;
}
// same as above but to a hex string
std::string Helpers::hextoa(const uint8_t value, bool prefix) {
char buf[3];
if (prefix) {
return std::string("0x") + hextoa(buf, value);
}
return std::string(hextoa(buf, value));
}
// same for 16 bit values
char * Helpers::hextoa(char * result, const uint16_t value) {
if (value <= 0xFF) {
return hextoa(result, (uint8_t)value);
}
hextoa(result, (uint8_t)(value >> 8));
hextoa(&result[2], (uint8_t)(value & 0xFF));
return result;
}
// same as above but to a hex string
std::string Helpers::hextoa(const uint16_t value, bool prefix) {
char buf[5];
if (prefix) {
return std::string("0x") + hextoa(buf, value);
}
return std::string(hextoa(buf, value));
}
#ifdef EMSESP_STANDALONE
// special function to work outside of ESP's libraries
char * Helpers::ultostr(char * ptr, uint32_t value, const uint8_t base) {
@@ -70,11 +99,34 @@ char * Helpers::ultostr(char * ptr, uint32_t value, const uint8_t base) {
}
#endif
/**
* fast atoi returning a std::string
* http://www.strudel.org.uk/itoa/
*
*/
std::string Helpers::itoa(int16_t value) {
std::string buf;
buf.reserve(25); // Pre-allocate enough space.
int quotient = value;
do {
buf += "0123456789abcdef"[std::abs(quotient % 10)];
quotient /= 10;
} while (quotient);
// Append the negative sign
if (value < 0)
buf += '-';
std::reverse(buf.begin(), buf.end());
return buf;
}
/*
* itoa for 2 byte signed (short) integers
* fast itoa and optimized for ESP32
* written by Lukás Chmela, Released under GPLv3. http://www.strudel.org.uk/itoa/ version 0.4
*/
char * Helpers::itoa(char * result, int32_t value, const uint8_t base) {
char * Helpers::itoa(int32_t value, char * result, const uint8_t base) {
// check that the base if valid
if (base < 2 || base > 36) {
*result = '\0';
@@ -82,7 +134,7 @@ char * Helpers::itoa(char * result, int32_t value, const uint8_t base) {
}
char * ptr = result, *ptr1 = result;
int16_t tmp_value;
int32_t tmp_value;
do {
tmp_value = value;
@@ -123,31 +175,30 @@ char * Helpers::smallitoa(char * result, const uint16_t value) {
}
// work out how to display booleans
// for strings only
char * Helpers::render_boolean(char * result, bool value) {
uint8_t bool_format_ = EMSESP::bool_format();
if (bool_format_ == BOOL_FORMAT_ONOFF) {
uint8_t bool_format_ = EMSESP::system_.bool_format();
if (bool_format_ == BOOL_FORMAT_ONOFF_STR) {
strlcpy(result, value ? read_flash_string(F_(on)).c_str() : read_flash_string(F_(off)).c_str(), 5);
} else if (bool_format_ == BOOL_FORMAT_ONOFF_CAP) {
} else if (bool_format_ == BOOL_FORMAT_ONOFF_STR_CAP) {
strlcpy(result, value ? read_flash_string(F_(ON)).c_str() : read_flash_string(F_(OFF)).c_str(), 5);
} else if (bool_format_ == BOOL_FORMAT_TRUEFALSE) {
strlcpy(result, value ? "true" : "false", 7);
} else {
} else if ((bool_format_ == BOOL_FORMAT_10) || (bool_format_ == BOOL_FORMAT_10_STR)) {
strlcpy(result, value ? "1" : "0", 2);
} else {
strlcpy(result, value ? "true" : "false", 7); // default
}
return result;
}
// render for native char strings
// format is not used
char * Helpers::render_value(char * result, const char * value, uint8_t format __attribute__((unused))) {
char * Helpers::render_value(char * result, const char * value, const int8_t format __attribute__((unused))) {
strcpy(result, value);
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, uint8_t format) {
char * Helpers::render_value(char * result, uint8_t value, int8_t format, const uint8_t fahrenheit) {
// special check if its a boolean
if (format == EMS_VALUE_BOOL) {
if (value == EMS_VALUE_BOOL_OFF) {
@@ -164,8 +215,10 @@ char * Helpers::render_value(char * result, uint8_t value, uint8_t format) {
return nullptr;
}
int16_t new_value = fahrenheit ? format ? value * 1.8 + 32 * format * (fahrenheit - 1) : value * 1.8 + 32 * (fahrenheit - 1) : value;
if (!format) {
itoa(result, value, 10); // format = 0
itoa(new_value, result, 10); // format = 0
return result;
}
@@ -173,22 +226,31 @@ char * Helpers::render_value(char * result, uint8_t value, uint8_t format) {
// special case for / 2
if (format == 2) {
strlcpy(result, itoa(s2, value >> 1, 10), 5);
strlcpy(result, itoa(new_value >> 1, s2, 10), 5);
strlcat(result, ".", 5);
strlcat(result, ((value & 0x01) ? "5" : "0"), 5);
strlcat(result, ((new_value & 0x01) ? "5" : "0"), 7);
return result;
} else if (format == 4) {
strlcpy(result, itoa(new_value >> 2, s2, 10), 5);
strlcat(result, ".", 5);
new_value = (new_value & 0x03) * 25;
strlcat(result, itoa(new_value, s2, 10), 7);
return result;
}
strlcpy(result, itoa(s2, value / format, 10), 5);
strlcat(result, ".", 5);
strlcat(result, itoa(s2, value % format, 10), 5);
} else if (format > 0) {
strlcpy(result, itoa(new_value / format, s2, 10), 5);
strlcat(result, ".", 5);
strlcat(result, itoa(new_value % format, s2, 10), 7);
} else {
strlcpy(result, itoa(new_value * format * -1, s2, 10), 5);
}
return result;
}
// float: convert float to char
// format is the precision, 0 to 8
char * Helpers::render_value(char * result, const float value, const uint8_t format) {
char * Helpers::render_value(char * result, const float value, const int8_t format) {
if (format > 8) {
return nullptr;
}
@@ -198,7 +260,7 @@ char * Helpers::render_value(char * result, const float value, const uint8_t for
char * ret = result;
int32_t whole = (int32_t)value;
itoa(result, whole, 10);
itoa(whole, result, 10);
while (*result != '\0') {
result++;
@@ -206,97 +268,103 @@ char * Helpers::render_value(char * result, const float value, const uint8_t for
*result++ = '.';
int32_t decimal = abs((int32_t)((value - whole) * p[format]));
itoa(result, decimal, 10);
itoa(decimal, result, 10);
return ret;
}
// int16: convert short (two bytes) to text string and returns string
// int32: convert signed 32bit to text string and returns string
// format: 0=no division, other divide by the value given and render with a decimal point
char * Helpers::render_value(char * result, const int16_t value, const uint8_t format) {
if (!hasValue(value)) {
return nullptr;
}
char * Helpers::render_value(char * result, const int32_t value, const int8_t format, const uint8_t fahrenheit) {
int32_t new_value = fahrenheit ? format ? value * 1.8 + 32 * format * (fahrenheit - 1) : value * 1.8 + 32 * (fahrenheit - 1) : value;
char s[10] = {0};
// just print it if no conversion required (format = 0)
if (!format) {
itoa(result, value, 10);
strlcpy(result, itoa(new_value, s, 10), sizeof(s)); // format is 0
return result;
}
int16_t new_value = value;
result[0] = '\0';
result[0] = '\0';
// check for negative values
if (new_value < 0) {
strlcpy(result, "-", 10);
strlcpy(result, "-", sizeof(s));
new_value *= -1; // convert to positive
} else {
strlcpy(result, "", 10);
strlcpy(result, "", sizeof(s));
}
// do floating point
char s2[10] = {0};
if (format == 2) {
// divide by 2
strlcat(result, itoa(s2, new_value / 2, 10), 10);
strlcat(result, ".", 10);
strlcat(result, ((new_value & 0x01) ? "5" : "0"), 10);
strlcat(result, itoa(new_value / 2, s, 10), sizeof(s));
strlcat(result, ".", sizeof(s));
strlcat(result, ((new_value & 0x01) ? "5" : "0"), sizeof(s));
} else if (format > 0) {
strlcat(result, itoa(new_value / format, s, 10), sizeof(s));
strlcat(result, ".", sizeof(s));
strlcat(result, itoa(new_value % format, s, 10), sizeof(s));
} else {
strlcat(result, itoa(s2, new_value / format, 10), 10);
strlcat(result, ".", 10);
strlcat(result, itoa(s2, new_value % format, 10), 10);
strlcat(result, itoa(new_value * format * -1, s, 10), sizeof(s));
}
return result;
}
// uint16: convert unsigned short (two bytes) to text string and prints it
// format: 0=no division, other divide by the value given and render with a decimal point
char * Helpers::render_value(char * result, const uint16_t value, const uint8_t format) {
// int16: convert short (two bytes) to text string and prints it
char * Helpers::render_value(char * result, const int16_t value, const int8_t format, const uint8_t fahrenheit) {
if (!hasValue(value)) {
return nullptr;
}
return (render_value(result, (int16_t)value, format)); // use same code, force it to a signed int
return (render_value(result, (int32_t)value, format, fahrenheit)); // use same code, force it to a signed int
}
// uint16: convert unsigned short (two bytes) to text string and prints it
char * Helpers::render_value(char * result, const uint16_t value, const int8_t format, const uint8_t fahrenheit) {
if (!hasValue(value)) {
return nullptr;
}
return (render_value(result, (int32_t)value, format, fahrenheit)); // use same code, force it to a signed int
}
// int8: convert signed byte to text string and prints it
// format: 0=no division, other divide by the value given and render with a decimal point
char * Helpers::render_value(char * result, const int8_t value, const uint8_t format) {
char * Helpers::render_value(char * result, const int8_t value, const int8_t format, const uint8_t fahrenheit) {
if (!hasValue(value)) {
return nullptr;
}
return (render_value(result, (int16_t)value, format)); // use same code, force it to a signed int
return (render_value(result, (int32_t)value, format, fahrenheit)); // use same code, force it to a signed int
}
// uint32: render long (4 byte) unsigned values
// format: 0=no division, other divide by the value given and render with a decimal point
char * Helpers::render_value(char * result, const uint32_t value, const uint8_t format) {
char * Helpers::render_value(char * result, const uint32_t value, const int8_t format, const uint8_t fahrenheit) {
if (!hasValue(value)) {
return nullptr;
}
result[0] = '\0';
char s[20];
result[0] = '\0';
int32_t new_value = fahrenheit ? format ? value * 1.8 + 32 * format * (fahrenheit - 1) : value * 1.8 + 32 * (fahrenheit - 1) : value;
char s[10] = {0};
#ifndef EMSESP_STANDALONE
if (!format) {
strlcpy(result, ltoa(value, s, 10), 20); // format is 0
strlcpy(result, ltoa(new_value, s, 10), sizeof(s)); // format is 0
} else if (format > 0) {
strlcpy(result, ltoa(new_value / format, s, 10), sizeof(s));
strlcat(result, ".", sizeof(s));
strlcat(result, ltoa(new_value % format, s, 10), sizeof(s));
} else {
strlcpy(result, ltoa(value / format, s, 10), 20);
strlcat(result, ".", 20);
strlcat(result, ltoa(value % format, s, 10), 20);
strlcpy(result, ltoa(new_value * format * -1, s, 10), sizeof(s));
}
#else
if (!format) {
strlcpy(result, ultostr(s, value, 10), 20); // format is 0
strlcpy(result, ultostr(s, new_value, 10), sizeof(s)); // format is 0
} else {
strncpy(result, ultostr(s, value / format, 10), 20);
strlcat(result, ".", 20);
strncat(result, ultostr(s, value % format, 10), 20);
strncpy(result, ultostr(s, new_value / format, 10), sizeof(s));
strlcat(result, ".", sizeof(s));
strncat(result, ultostr(s, new_value % format, 10), sizeof(s));
}
#endif
@@ -306,12 +374,12 @@ char * Helpers::render_value(char * result, const uint32_t value, const uint8_t
// creates string of hex values from an arrray of bytes
std::string Helpers::data_to_hex(const uint8_t * data, const uint8_t length) {
if (length == 0) {
return read_flash_string(F("<empty>"));
return "<empty>";
}
std::string str(160, '\0');
char buffer[4];
char * p = &str[0];
char str[160] = {0};
char buffer[4];
char * p = &str[0];
for (uint8_t i = 0; i < length; i++) {
Helpers::hextoa(buffer, data[i]);
*p++ = buffer[0];
@@ -320,17 +388,23 @@ std::string Helpers::data_to_hex(const uint8_t * data, const uint8_t length) {
}
*--p = '\0'; // null terminate just in case, loosing the trailing space
return str;
return std::string(str);
}
// takes a hex string and convert it to an unsigned 32bit number (max 8 hex digits)
// works with only positive numbers
uint32_t Helpers::hextoint(const char * hex) {
if (hex == nullptr) {
return 0;
}
uint32_t val = 0;
// skip leading '0x'
if (hex[0] == '0' && hex[1] == 'x') {
hex += 2;
}
while (*hex) {
// get current character then increment
char byte = *hex++;
@@ -346,29 +420,49 @@ uint32_t Helpers::hextoint(const char * hex) {
// shift 4 to make space for new digit, and add the 4 bits of the new digit
val = (val << 4) | (byte & 0xF);
}
return val;
}
// quick char to long
uint16_t Helpers::atoint(const char * value) {
unsigned int x = 0;
int Helpers::atoint(const char * value) {
int x = 0;
char s = value[0];
if (s == '-') {
++value;
}
while (*value >= '0' && *value <= '9') {
x = (x * 10) + (*value - '0');
++value;
}
if (s == '-') {
return (-x);
}
return x;
}
// rounds a number to 2 decimal places
// example: round2(3.14159) -> 3.14
float Helpers::round2(float value, const uint8_t divider) {
uint8_t div = (divider ? divider : 1); // prevent div-by-zero
if (value >= 0) {
return (int)((value / div) * 100 + 0.5) / 100.0;
// From mvdp:
// The conversion to Fahrenheit is different for absolute temperatures and relative temperatures like hysteresis.
// fahrenheit=0 - off, no conversion
// fahrenheit=1 - relative, 1.8t
// fahrenheit=2 - absolute, 1.8t + 32(fahrenheit-1).
float Helpers::round2(float value, const int8_t divider, const uint8_t fahrenheit) {
float val = (value * 100 + 0.5);
if (divider > 0) {
val = ((value / divider) * 100 + 0.5);
} else if (divider < 0) {
val = value * -100 * divider;
}
if (value < 0) { // negative rounding
val = val - 1;
}
if (fahrenheit) {
val = val * 1.8 + 3200 * (fahrenheit - 1);
}
return (int)((value / div) * 100 - 0.5) / 100.0; // negative values
return ((int32_t)val) / 100.0;
}
// abs of a signed 32-bit integer
@@ -410,13 +504,16 @@ bool Helpers::hasValue(const uint32_t & v) {
}
// checks if we can convert a char string to an int value
bool Helpers::value2number(const char * v, int & value) {
bool Helpers::value2number(const char * v, int & value, const int min, const int max) {
if ((v == nullptr) || (strlen(v) == 0)) {
value = 0;
return false;
}
value = atoi((char *)v);
return true;
if (value >= min && value <= max) {
return true;
}
return false;
}
// checks if we can convert a char string to a float value
@@ -432,6 +529,26 @@ bool Helpers::value2float(const char * v, float & value) {
return false;
}
bool Helpers::value2temperature(const char * v, float & value, bool relative) {
if (value2float(v, value)) {
if (EMSESP::system_.fahrenheit()) {
value = relative ? (value / 1.8) : (value - 32) / 1.8;
}
return true;
}
return false;
}
bool Helpers::value2temperature(const char * v, int & value, const bool relative, const int min, const int max) {
if (value2number(v, value, min, max)) {
if (EMSESP::system_.fahrenheit()) {
value = relative ? (value / 1.8) : (value - 32) / 1.8;
}
return true;
}
return false;
}
// https://stackoverflow.com/questions/313970/how-to-convert-stdstring-to-lower-case
std::string Helpers::toLower(std::string const & s) {
std::string lc = s;
@@ -485,13 +602,26 @@ bool Helpers::value2enum(const char * v, uint8_t & value, const __FlashStringHel
std::string str = toLower(v);
for (value = 0; strs[value]; value++) {
std::string str1 = toLower(read_flash_string(strs[value]));
if ((str1 == read_flash_string(F_(off)) && str == "false") || (str1 == read_flash_string(F_(on)) && str == "true") || (str == str1)
|| (v[0] == ('0' + value) && v[1] == '\0')) {
if ((str1 != "")
&& ((str1 == read_flash_string(F_(off)) && str == "false") || (str1 == read_flash_string(F_(on)) && str == "true") || (str == str1)
|| (v[0] == ('0' + value) && v[1] == '\0'))) {
return true;
}
}
return false;
}
// replace char in char string
void Helpers::replace_char(char * str, char find, char replace) {
int i = 0;
while (str[i] != '\0') {
/*Replace the matched character...*/
if (str[i] == find)
str[i] = replace;
i++;
}
}
} // namespace emsesp

View File

@@ -19,8 +19,6 @@
#ifndef EMSESP_HELPERS_H
#define EMSESP_HELPERS_H
#include <Arduino.h>
#include "telegram.h" // for EMS_VALUE_* settings
#define FJSON(x) x
@@ -32,27 +30,33 @@ using flash_string_vector = std::vector<const __FlashStringHelper *>;
class Helpers {
public:
static char * render_value(char * result, const float value, const uint8_t format); // format is the precision
static char * render_value(char * result, const uint8_t value, const uint8_t format);
static char * render_value(char * result, const int8_t value, const uint8_t format);
static char * render_value(char * result, const uint16_t value, const uint8_t format);
static char * render_value(char * result, const uint32_t value, const uint8_t format);
static char * render_value(char * result, const int16_t value, const uint8_t format);
static char * render_value(char * result, const char * value, uint8_t format);
static char * render_value(char * result, const float value, const int8_t format); // format is the precision
static char * render_value(char * result, const uint8_t value, const int8_t format, const uint8_t fahrenheit = 0);
static char * render_value(char * result, const int8_t value, const int8_t format, const uint8_t fahrenheit = 0);
static char * render_value(char * result, const uint16_t value, const int8_t format, const uint8_t fahrenheit = 0);
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);
static char * hextoa(char * result, const uint16_t value);
static std::string hextoa(const uint8_t value, bool prefix = true); // default prefix with 0x
static std::string hextoa(const uint16_t value, bool prefix = true); // default prefix with 0x
static std::string data_to_hex(const uint8_t * data, const uint8_t length);
static char * smallitoa(char * result, const uint8_t value);
static char * smallitoa(char * result, const uint16_t value);
static char * itoa(char * result, int32_t value, const uint8_t base = 10);
static char * itoa(int32_t value, char * result, const uint8_t base = 10);
static std::string itoa(int16_t value);
static uint32_t hextoint(const char * hex);
static uint16_t atoint(const char * value);
static int atoint(const char * value);
static bool check_abs(const int32_t i);
static uint32_t abs(const int32_t i);
static float round2(float value, const uint8_t divider);
static float round2(float value, const int8_t divider, const uint8_t fahrenheit = 0);
static std::string toLower(std::string const & s);
static std::string toUpper(std::string const & s);
static void replace_char(char * str, char find, char replace);
static bool hasValue(const uint8_t & v, const uint8_t isBool = 0);
static bool hasValue(const int8_t & v);
@@ -61,11 +65,13 @@ class Helpers {
static bool hasValue(const uint32_t & v);
static bool hasValue(char * v);
static bool value2number(const char * v, int & value);
static bool value2number(const char * v, int & value, const int min = -2147483648, const int max = 2147483647);
static bool value2float(const char * v, float & value);
static bool value2bool(const char * v, bool & value);
static bool value2string(const char * v, std::string & value);
static bool value2enum(const char * v, uint8_t & value, const __FlashStringHelper * const * strs);
static bool value2temperature(const char * v, float & value, bool relative = false);
static bool value2temperature(const char * v, int & value, const bool relative = false, const int min = -2147483648, const int max = 2147483647);
#ifdef EMSESP_STANDALONE
static char * ultostr(char * ptr, uint32_t value, const uint8_t base);

639
src/locale_DE.h Normal file
View File

@@ -0,0 +1,639 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
// common words
MAKE_PSTR_WORD(debug)
MAKE_PSTR_WORD(exit)
MAKE_PSTR_WORD(help)
MAKE_PSTR_WORD(log)
MAKE_PSTR_WORD(logout)
MAKE_PSTR_WORD(enabled)
MAKE_PSTR_WORD(disabled)
MAKE_PSTR_WORD(set)
MAKE_PSTR_WORD(show)
MAKE_PSTR_WORD(on)
MAKE_PSTR_WORD(off)
MAKE_PSTR_WORD(ON)
MAKE_PSTR_WORD(OFF)
MAKE_PSTR_WORD(su)
MAKE_PSTR_WORD(name)
MAKE_PSTR_WORD(auto)
MAKE_PSTR_WORD(scan)
MAKE_PSTR_WORD(password)
MAKE_PSTR_WORD(read)
MAKE_PSTR_WORD(version)
MAKE_PSTR_WORD(values)
MAKE_PSTR_WORD(system)
MAKE_PSTR_WORD(fetch)
MAKE_PSTR_WORD(restart)
MAKE_PSTR_WORD(format)
MAKE_PSTR_WORD(raw)
MAKE_PSTR_WORD(watch)
MAKE_PSTR_WORD(syslog)
MAKE_PSTR_WORD(send)
MAKE_PSTR_WORD(telegram)
MAKE_PSTR_WORD(bus_id)
MAKE_PSTR_WORD(tx_mode)
MAKE_PSTR_WORD(ems)
MAKE_PSTR_WORD(devices)
MAKE_PSTR_WORD(shower)
MAKE_PSTR_WORD(mqtt)
MAKE_PSTR_WORD(emsesp)
MAKE_PSTR_WORD(connected)
MAKE_PSTR_WORD(disconnected)
MAKE_PSTR_WORD(passwd)
MAKE_PSTR_WORD(hostname)
MAKE_PSTR_WORD(wifi)
MAKE_PSTR_WORD(reconnect)
MAKE_PSTR_WORD(ssid)
MAKE_PSTR_WORD(heartbeat)
MAKE_PSTR_WORD(users)
MAKE_PSTR_WORD(master)
MAKE_PSTR_WORD(pin)
MAKE_PSTR_WORD(publish)
MAKE_PSTR_WORD(timeout)
MAKE_PSTR_WORD(board_profile)
MAKE_PSTR_WORD(counter)
MAKE_PSTR_WORD(sensorname)
// for commands
MAKE_PSTR_WORD(call)
MAKE_PSTR_WORD(cmd)
MAKE_PSTR_WORD(id)
MAKE_PSTR_WORD(device)
MAKE_PSTR_WORD(data)
MAKE_PSTR_WORD(command)
MAKE_PSTR_WORD(commands)
MAKE_PSTR_WORD(info)
// MAKE_PSTR_WORD(info_short)
MAKE_PSTR_WORD(settings)
MAKE_PSTR_WORD(value)
MAKE_PSTR_WORD(error)
// devices
MAKE_PSTR_WORD(boiler)
MAKE_PSTR_WORD(thermostat)
MAKE_PSTR_WORD(switch)
MAKE_PSTR_WORD(solar)
MAKE_PSTR_WORD(mixer)
MAKE_PSTR_WORD(gateway)
MAKE_PSTR_WORD(controller)
MAKE_PSTR_WORD(connect)
MAKE_PSTR_WORD(heatpump)
MAKE_PSTR_WORD(generic)
MAKE_PSTR_WORD(sensor)
MAKE_PSTR_WORD(unknown)
MAKE_PSTR_WORD(Sensor)
MAKE_PSTR_WORD(other)
// format strings
MAKE_PSTR(master_thermostat_fmt, "Master Thermostat Device ID: %s")
MAKE_PSTR(host_fmt, "Host: %s")
MAKE_PSTR(port_fmt, "Port: %d")
MAKE_PSTR(hostname_fmt, "Hostname: %s")
MAKE_PSTR(board_profile_fmt, "Board Profile: %s")
MAKE_PSTR(mark_interval_fmt, "Mark interval: %lus")
MAKE_PSTR(wifi_ssid_fmt, "WiFi SSID: %s")
MAKE_PSTR(wifi_password_fmt, "WiFi Password: %S")
MAKE_PSTR(ethernet_option_fmt, "Ethernet option: %d")
MAKE_PSTR(tx_mode_fmt, "Tx mode: %d")
MAKE_PSTR(bus_id_fmt, "Bus ID: %02X")
MAKE_PSTR(log_level_fmt, "Log level: %s")
MAKE_STR(productid_fmt, "%s EMS Product ID")
MAKE_PSTR_LIST(enum_syslog_level, F_(off), F("emerg"), F("alert"), F("crit"), F_(error), F("warn"), F("notice"), F_(info), F_(debug), F("trace"), F("all"))
MAKE_PSTR_LIST(enum_watch, F_(off), F_(on), F_(raw), F_(unknown))
// strings
MAKE_PSTR(show_optional, "[devices | users | ems | mqtt | system | commands]")
MAKE_PSTR(EMSESP, "EMS-ESP")
MAKE_PSTR(cmd_optional, "[cmd]")
MAKE_PSTR(ha_optional, "[ha]")
MAKE_PSTR(deep_optional, "[deep]")
MAKE_PSTR(watchid_optional, "[ID]")
MAKE_PSTR(watch_format_optional, "[off | on | raw | unknown]")
MAKE_PSTR(invalid_watch, "Invalid watch type")
MAKE_PSTR(data_mandatory, "\"XX XX ...\"")
MAKE_PSTR(asterisks, "********")
MAKE_PSTR(n_mandatory, "<n>")
MAKE_PSTR(sensorid_optional, "[sensor ID]")
MAKE_PSTR(id_optional, "[id|hc]")
MAKE_PSTR(data_optional, "[data]")
MAKE_PSTR(offset_optional, "[offset]")
MAKE_PSTR(typeid_mandatory, "<type ID>")
MAKE_PSTR(deviceid_mandatory, "<device ID>")
MAKE_PSTR(device_type_optional, "[device]")
MAKE_PSTR(invalid_log_level, "Invalid log level")
MAKE_PSTR(log_level_optional, "[level]")
MAKE_PSTR(name_mandatory, "<name>")
MAKE_PSTR(name_optional, "[name]")
MAKE_PSTR(new_password_prompt1, "Enter new password: ")
MAKE_PSTR(new_password_prompt2, "Retype new password: ")
MAKE_PSTR(password_prompt, "Password: ")
MAKE_PSTR(unset, "<unset>")
// command descriptions
MAKE_PSTR(info_cmd, "list all values")
MAKE_PSTR(commands_cmd, "list all commands")
MAKE_PSTR_WORD(number)
MAKE_PSTR_WORD(enum)
MAKE_PSTR_WORD(text)
MAKE_PSTR_WORD(2)
MAKE_PSTR_WORD(4)
MAKE_PSTR_WORD(10)
MAKE_PSTR_WORD(100)
MAKE_PSTR_WORD(60)
MAKE_PSTR_LIST(div2, F_(2))
MAKE_PSTR_LIST(div4, F_(4))
MAKE_PSTR_LIST(div10, F_(10))
MAKE_PSTR_LIST(div100, F_(100))
MAKE_PSTR_LIST(div60, F_(60))
MAKE_PSTR_LIST(mul15, F("-15"))
// Unit Of Measurement mapping - maps to DeviceValueUOM_s in emsdevice.cpp
// uom - also used with HA see https://github.com/home-assistant/core/blob/d7ac4bd65379e11461c7ce0893d3533d8d8b8cbf/homeassistant/const.py#L384
MAKE_PSTR(blank, " ")
MAKE_PSTR(percent, "%")
MAKE_PSTR(degrees, "°C")
MAKE_PSTR(kwh, "kWh")
MAKE_PSTR(wh, "Wh")
MAKE_PSTR(bar, "bar")
MAKE_PSTR(minutes, "Minuten")
MAKE_PSTR(hours, "Stunden")
MAKE_PSTR(ua, "uA")
MAKE_PSTR(lmin, "l/min")
MAKE_PSTR(kw, "kW")
MAKE_PSTR(w, "W")
MAKE_PSTR(kb, "KB")
MAKE_PSTR(seconds, "seconds")
MAKE_PSTR(dbm, "dBm")
MAKE_PSTR(fahrenheit, "°F")
MAKE_PSTR(mv, "mV")
MAKE_PSTR(sqm, "sqm")
// TAG mapping - maps to DeviceValueTAG_s in emsdevice.cpp
// use empty string if want to suppress showing tags
// mqtt tags must not have spaces
MAKE_PSTR(tag_none, "")
MAKE_PSTR(tag_heartbeat, "")
MAKE_PSTR(tag_boiler_data, "")
MAKE_PSTR(tag_device_data_ww, "Warmwasser")
MAKE_PSTR(tag_thermostat_data, "")
MAKE_PSTR(tag_hc1, "hc1")
MAKE_PSTR(tag_hc2, "hc2")
MAKE_PSTR(tag_hc3, "hc3")
MAKE_PSTR(tag_hc4, "hc4")
MAKE_PSTR(tag_wwc1, "wwc1")
MAKE_PSTR(tag_wwc2, "wwc2")
MAKE_PSTR(tag_wwc3, "wwc3")
MAKE_PSTR(tag_wwc4, "wwc4")
MAKE_PSTR(tag_hs1, "hs1")
MAKE_PSTR(tag_hs2, "hs2")
MAKE_PSTR(tag_hs3, "hs3")
MAKE_PSTR(tag_hs4, "hs4")
MAKE_PSTR(tag_hs5, "hs5")
MAKE_PSTR(tag_hs6, "hs6")
MAKE_PSTR(tag_hs7, "hs7")
MAKE_PSTR(tag_hs8, "hs8")
MAKE_PSTR(tag_hs9, "hs9")
MAKE_PSTR(tag_hs10, "hs10")
MAKE_PSTR(tag_hs11, "hs11")
MAKE_PSTR(tag_hs12, "hs12")
MAKE_PSTR(tag_hs13, "hs13")
MAKE_PSTR(tag_hs14, "hs14")
MAKE_PSTR(tag_hs15, "hs15")
MAKE_PSTR(tag_hs16, "hs16")
// MQTT topic names
// MAKE_PSTR(tag_heartbeat_mqtt, "heartbeat")
MAKE_PSTR(tag_boiler_data_mqtt, "")
MAKE_PSTR(tag_device_data_ww_mqtt, "ww")
// boiler
MAKE_PSTR(time, "Zeit")
MAKE_PSTR(date, "Datum")
MAKE_PSTR_WORD(1x3min)
MAKE_PSTR_WORD(2x3min)
MAKE_PSTR_WORD(3x3min)
MAKE_PSTR_WORD(4x3min)
MAKE_PSTR_WORD(5x3min)
MAKE_PSTR_WORD(6x3min)
MAKE_PSTR_(continuos, "kontinuierlich")
MAKE_PSTR(3wayvalve, "3-wege Ventil")
MAKE_PSTR(chargepump, "Ladepumpe")
MAKE_PSTR(hot, "Heiss")
MAKE_PSTR_WORD(eco)
MAKE_PSTR_WORD(intelligent)
MAKE_PSTR(manual, "Manuell")
MAKE_PSTR_(flow, "Fluss")
MAKE_PSTR_(buffer, "Speicher")
MAKE_PSTR(bufferedflow, "Durchlaufspeicher")
MAKE_PSTR(layeredbuffer, "Schichtspeicher")
MAKE_PSTR_WORD(maintenance)
// boiler lists
MAKE_PSTR_LIST(tpl_date, F("Format: < dd.mm.yyyy >")) // template for text input
MAKE_PSTR_LIST(enum_off_time_date_manual, F_(off), F_(time), F_(date), F_(manual))
MAKE_PSTR_LIST(enum_freq, F_(off), F_(1x3min), F_(2x3min), F_(3x3min), F_(4x3min), F_(5x3min), F_(6x3min), F_(continuos))
MAKE_PSTR_LIST(enum_charge, F_(chargepump), F_(3wayvalve))
MAKE_PSTR_LIST(enum_comfort, F_(hot), F_(eco), F_(intelligent))
MAKE_PSTR_LIST(enum_flow, F_(off), F_(flow), F_(bufferedflow), F_(buffer), F_(layeredbuffer))
MAKE_PSTR_LIST(enum_reset, F_(maintenance), F_(error))
// MAKE_PSTR_LIST(enum_bool, F_(Aus), F_(Ein))
// thermostat
MAKE_PSTR(light, "Leicht")
MAKE_PSTR(medium, "Mittel")
MAKE_PSTR(heavy, "Schwer")
MAKE_PSTR(own_prog, "Eigenprog")
MAKE_PSTR_WORD(start)
MAKE_PSTR(heat, "Heizen")
MAKE_PSTR(hold, "Halten")
MAKE_PSTR(cool, "Kühl")
MAKE_PSTR(end, "Ende")
MAKE_PSTR(german, "Deutsch")
MAKE_PSTR(dutch, "Niederländisch")
MAKE_PSTR(french, "Französisch")
MAKE_PSTR(italian, "Italienisch")
MAKE_PSTR(high, "hoch")
MAKE_PSTR(low, "niedrig")
MAKE_PSTR(radiator, "Heizkörper")
MAKE_PSTR(convector, "Konvektor")
MAKE_PSTR(floor, "Fussboden")
MAKE_PSTR(summer, "Sommer")
MAKE_PSTR_WORD(winter)
MAKE_PSTR(outdoor, "Aussentemperatur")
MAKE_PSTR_WORD(mpc)
MAKE_PSTR(room, "Raum")
MAKE_PSTR(power, "Leistung")
MAKE_PSTR(constant, "konstant")
MAKE_PSTR(simple, "einfach")
MAKE_PSTR(optimized, "optimiert")
MAKE_PSTR(nofrost, "Frostschutz")
MAKE_PSTR(comfort, "Komfort")
MAKE_PSTR(night, "Nacht")
MAKE_PSTR(day, "Tag")
MAKE_PSTR(holiday, "Urlaub")
MAKE_PSTR(reduce, "reduziert")
MAKE_PSTR(noreduce, "unreduziert")
MAKE_PSTR(offset, "Anhebung")
MAKE_PSTR(design, "Auslegung")
MAKE_PSTR_WORD(tempauto)
MAKE_PSTR(minflow, "minfluss")
MAKE_PSTR(maxflow, "maxfluss")
MAKE_PSTR_WORD(rc3x)
MAKE_PSTR_WORD(rc20)
MAKE_PSTR(internal_temperature, "interne Temperatur")
MAKE_PSTR(internal_setpoint, "interner Sollwert")
MAKE_PSTR(external_temperature, "externe Temperatur")
MAKE_PSTR(burner_temperature, "Kesseltemperatur")
MAKE_PSTR(ww_temperature, "Wassertemperatur")
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_switchtime, F("Format: < nn.d.o.hh:mm >"))
MAKE_PSTR_LIST(enum_ibaMainDisplay,
F_(internal_temperature),
F_(internal_setpoint),
F_(external_temperature),
F_(burner_temperature),
F_(ww_temperature),
F_(functioning_mode),
F_(time),
F_(date),
F_(smoke_temperature))
MAKE_PSTR_LIST(enum_ibaLanguage, F_(german), F_(dutch), F_(french), F_(italian))
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_wwMode, F_(off), F_(low), F_(high), F_(auto), F_(own_prog))
MAKE_PSTR_LIST(enum_wwCircMode, F_(off), F_(on), F_(auto), F_(own_prog))
MAKE_PSTR_LIST(enum_wwMode2, F_(off), F_(on), F_(auto))
MAKE_PSTR_LIST(enum_wwCircMode2, F_(off), F_(on), F_(auto))
MAKE_PSTR_LIST(enum_heatingtype, F_(off), F_(radiator), F_(convector), F_(floor))
MAKE_PSTR_LIST(enum_summermode, F_(summer), F_(auto), F_(winter))
MAKE_PSTR_LIST(enum_mode, F_(manual), F_(auto))
MAKE_PSTR_LIST(enum_mode2, F_(off), F_(manual), F_(auto))
MAKE_PSTR_LIST(enum_mode3, F_(night), F_(day), F_(auto))
MAKE_PSTR_LIST(enum_mode4, F_(nofrost), F_(eco), F_(heat), F_(auto)) // JUNKERS
MAKE_PSTR_LIST(enum_mode5, F_(auto), F_(off))
MAKE_PSTR_LIST(enum_modetype, F_(eco), F_(comfort))
MAKE_PSTR_LIST(enum_modetype2, F_(day))
MAKE_PSTR_LIST(enum_modetype3, F_(night), F_(day))
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_controlmode, F_(off), F_(optimized), F_(simple), F_(mpc), F_(room), F_(power), F_(constant))
MAKE_PSTR_LIST(enum_controlmode2, F_(outdoor), F_(room))
MAKE_PSTR_LIST(enum_controlmode3, F_(off), F_(room), F_(outdoor), F("room+outdoor"))
MAKE_PSTR_LIST(enum_control, F_(off), F_(rc20), F_(rc3x))
MAKE_PSTR_LIST(enum_wwProgMode, F("std prog"), F_(own_prog))
MAKE_PSTR_LIST(enum_wwDisinfectDay, F("Mo"), F("Di"), F("Mi"), F("Do"), F("Fr"), F("Sa"), F("So"), F("t<EFBFBD>glich"))
MAKE_PSTR_LIST(enum_wwChargeDuration, F_(off), F("15min"), F("30min"), F("45min"), F("60min"), F("75min"), F("90min"), F("105min"), F("120min"))
// solar list
MAKE_PSTR_LIST(enum_solarmode, F_(constant), F("pwm"), F("analog"))
MAKE_PSTR_LIST(enum_collectortype, F("flat"), F("vacuum"))
// id used to store the device ID, goes into MQTT payload
// empty full name to prevent being shown in web or console
MAKE_PSTR_LIST(ID, F_(id))
// Boiler
// extra commands
MAKE_PSTR_LIST(wwtapactivated, F("wwtapactivated"), F("Aktiviere Warmwasser im Wartungsmodus"))
MAKE_PSTR_LIST(reset, F("reset"), F("Sende 'RESET'"))
// single mqtt topics
MAKE_PSTR_WORD(heating_active)
MAKE_PSTR_WORD(tapwater_active)
MAKE_PSTR_WORD(response)
// mqtt, commands and text
MAKE_PSTR_LIST(heatingActive, F("heatingactive"), F("Heizung aktiv"))
MAKE_PSTR_LIST(tapwaterActive, F("tapwateractive"), F("Warmwasser aktiv"))
MAKE_PSTR_LIST(selFlowTemp, F("selflowtemp"), F("Sollwert Flusstemperatur"))
MAKE_PSTR_LIST(selBurnPow, F("selburnpow"), F("Sollwert Brennerleistung"))
MAKE_PSTR_LIST(heatingPumpMod, F("heatingpumpmod"), F("Heizungspumpe 1 Modulation"))
MAKE_PSTR_LIST(heatingPump2Mod, F("heatingpump2mod"), F("Heizungspumpe 2 Modulation"))
MAKE_PSTR_LIST(outdoorTemp, F("outdoortemp"), F("Aussentemperatur"))
MAKE_PSTR_LIST(curFlowTemp, F("curflowtemp"), F("aktuelle Flusstemperatur"))
MAKE_PSTR_LIST(retTemp, F("rettemp"), F("Rücklauftemperatur"))
MAKE_PSTR_LIST(switchTemp, F("switchtemp"), F("Mischer Schalttemperatur"))
MAKE_PSTR_LIST(sysPress, F("syspress"), F("Systemdruck"))
MAKE_PSTR_LIST(boilTemp, F("boiltemp"), F("Kesseltemperatur"))
MAKE_PSTR_LIST(exhaustTemp, F("exhausttemp"), F("Auslasstemperatur"))
MAKE_PSTR_LIST(burnGas, F("burngas"), F("Gas"))
MAKE_PSTR_LIST(flameCurr, F("flamecurr"), F("Flammstrom"))
MAKE_PSTR_LIST(heatingPump, F("heatingpump"), F("Heizungspumpe"))
MAKE_PSTR_LIST(fanWork, F("fanwork"), F("Gebläse"))
MAKE_PSTR_LIST(ignWork, F("ignwork"), F("Zündung"))
MAKE_PSTR_LIST(heatingActivated, F("heatingactivated"), F("Heizen aktiviert"))
MAKE_PSTR_LIST(heatingTemp, F("heatingtemp"), F("Kesseltemperatur"))
MAKE_PSTR_LIST(pumpModMax, F("pumpmodmax"), F("Kesselpumpen Maximalleistung"))
MAKE_PSTR_LIST(pumpModMin, F("pumpmodmin"), F("Kesselpumpen Minmalleistung"))
MAKE_PSTR_LIST(pumpDelay, F("pumpdelay"), F("Pumpennachlauf"))
MAKE_PSTR_LIST(burnMinPeriod, F("burnminperiod"), F("Antipendelzeit"))
MAKE_PSTR_LIST(burnMinPower, F("burnminpower"), F("minimale Brennerleistung"))
MAKE_PSTR_LIST(burnMaxPower, F("burnmaxpower"), F("maximale Brennerleistung"))
MAKE_PSTR_LIST(boilHystOn, F("boilhyston"), F("Hysterese ein temperatur"))
MAKE_PSTR_LIST(boilHystOff, F("boilhystoff"), F("Hysterese aus temperatur"))
MAKE_PSTR_LIST(setFlowTemp, F("setflowtemp"), F("Sollwert Flusstemperatur"))
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(heatWorkMin, F("heatworkmin"), F("Heizung Laufzeit"))
MAKE_PSTR_LIST(UBAuptime, F("ubauptime"), F("gesamte Laufzeit"))
MAKE_PSTR_LIST(lastCode, F("lastcode"), F("Fehlerspeicher"))
MAKE_PSTR_LIST(serviceCode, F("servicecode"), F("Statusmeldung"))
MAKE_PSTR_LIST(serviceCodeNumber, F("servicecodenumber"), F("Statusmeldungsnummer"))
MAKE_PSTR_LIST(maintenanceMessage, F("maintenancemessage"), F("Wartungsmeldung"))
MAKE_PSTR_LIST(maintenanceDate, F("maintenancedate"), F("Wartungsdatum"))
MAKE_PSTR_LIST(maintenanceType, F_(maintenance), F("Wartungsplan"))
MAKE_PSTR_LIST(maintenanceTime, F("maintenancetime"), F("Wartung in"))
MAKE_PSTR_LIST(upTimeControl, F("uptimecontrol"), F("Betriebszeit total heizen"))
MAKE_PSTR_LIST(upTimeCompHeating, F("uptimecompheating"), F("Betriebszeit Kompressor heizen"))
MAKE_PSTR_LIST(upTimeCompCooling, F("uptimecompcooling"), F("Betriebszeit Kompressor kühlen"))
MAKE_PSTR_LIST(upTimeCompWw, F("uptimecompww"), F("Betriebszeit Kompressor"))
MAKE_PSTR_LIST(heatingStarts, F("heatingstarts"), F("Heizen Starts"))
MAKE_PSTR_LIST(coolingStarts, F("coolingstarts"), F("Kühlen Starts"))
MAKE_PSTR_LIST(nrgConsTotal, F("nrgconstotal"), F("totaler Energieverbrauch"))
MAKE_PSTR_LIST(nrgConsCompTotal, F("nrgconscomptotal"), F("Energieverbrauch Kompressor total"))
MAKE_PSTR_LIST(nrgConsCompHeating, F("nrgconscompheating"), F("Energieverbrauch Kompressor heizen"))
MAKE_PSTR_LIST(nrgConsCompWw, F("nrgconscompww"), F("Energieverbrauch Kompressor"))
MAKE_PSTR_LIST(nrgConsCompCooling, F("nrgconscompcooling"), F("Energieverbrauch Kompressor kühlen"))
MAKE_PSTR_LIST(nrgSuppTotal, F("nrgsupptotal"), F("gesamte Energieabgabe"))
MAKE_PSTR_LIST(nrgSuppHeating, F("nrgsuppheating"), F("gesamte Energieabgabe heizen"))
MAKE_PSTR_LIST(nrgSuppWw, F("nrgsuppww"), F("gesamte Energieabgabe"))
MAKE_PSTR_LIST(nrgSuppCooling, F("nrgsuppcooling"), F("gesamte Energieabgabe kühlen"))
MAKE_PSTR_LIST(auxElecHeatNrgConsTotal, F("auxelecheatnrgconstotal"), F("Energieverbrauch el. Zusatzheizung"))
MAKE_PSTR_LIST(auxElecHeatNrgConsHeating, F("auxelecheatnrgconsheating"), F("Energieverbrauch el. Zusatzheizung heizen"))
MAKE_PSTR_LIST(auxElecHeatNrgConsWW, F("auxelecheatnrgconsww"), F("Energieverbrauch el. Zusatzheizung"))
MAKE_PSTR_LIST(hpPower, F("hppower"), F("Leistung Wärmepumpe"))
MAKE_PSTR_LIST(hpTc0, F("hptc0"), F("Wärmeträgerflüssigkeit Eingang (TC0)"))
MAKE_PSTR_LIST(hpTc1, F("hptc1"), F("Wärmeträgerflüssigkeit Ausgang (TC1)"))
MAKE_PSTR_LIST(hpTc3, F("hptc3"), F("Verflüssigertemperatur (TC3)"))
MAKE_PSTR_LIST(hpTr3, F("hptr3"), F(" Temperaturfühler Kältemittel (Flüssigkeit) (TR3)"))
MAKE_PSTR_LIST(hpTr4, F("hptr4"), F("Verdampfer Eintritt (TR4)"))
MAKE_PSTR_LIST(hpTr5, F("hptr5"), F("Temperaturfühler Kompessoransaugleitung (TR5)"))
MAKE_PSTR_LIST(hpTr6, F("hptr6"), F("Temperaturfühler Kompressorausgangsleitung (TR6)"))
MAKE_PSTR_LIST(hpTr7, F("hptr7"), F("Temperaturfühler Kältemittel (Gas) (TR7)"))
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)"))
MAKE_PSTR_LIST(wWSelTemp, F("wwseltemp"), F("gewählte Temperatur"))
MAKE_PSTR_LIST(wWSetTemp, F("wwsettemp"), F("Solltemperatur"))
MAKE_PSTR_LIST(wWType, F("wwtype"), F("Typ"))
MAKE_PSTR_LIST(wWComfort, F("wwcomfort"), F("Komfort"))
MAKE_PSTR_LIST(wWFlowTempOffset, F("wwflowtempoffset"), F("Flusstemperaturanhebung"))
MAKE_PSTR_LIST(wWMaxPower, F("wwmaxpower"), F("max Leistung"))
MAKE_PSTR_LIST(wWCircPump, F("wwcircpump"), F("Zirkulationspumpe vorhanden"))
MAKE_PSTR_LIST(wWChargeType, F("wwchargetype"), F("Ladungstyp"))
MAKE_PSTR_LIST(wWDisinfectionTemp, F("wwdisinfectiontemp"), F("Desinfectionstemperatur"))
MAKE_PSTR_LIST(wWCircMode, F("wwcircmode"), F("Zirkulationspumpenfrequenz"))
MAKE_PSTR_LIST(wWCirc, F("wwcirc"), F("Zirkulation aktiv"))
MAKE_PSTR_LIST(wWCurTemp, F("wwcurtemp"), F("aktuelle Warmwasser Temperatur intern"))
MAKE_PSTR_LIST(wWCurTemp2, F("wwcurtemp2"), F("aktuelle Warmwaser Temperatur extern"))
MAKE_PSTR_LIST(wWCurFlow, F("wwcurflow"), F("aktueller Durchfluss"))
MAKE_PSTR_LIST(wWStorageTemp1, F("wwstoragetemp1"), F("interne Speichertemperature"))
MAKE_PSTR_LIST(wWStorageTemp2, F("wwstoragetemp2"), F("externer Speichertemperatur"))
MAKE_PSTR_LIST(wWActivated, F("wwactivated"), F("aktiviert"))
MAKE_PSTR_LIST(wWOneTime, F("wwonetime"), F("Einmalladung"))
MAKE_PSTR_LIST(wWDisinfect, F("wwdisinfect"), F("Desinfizieren"))
MAKE_PSTR_LIST(wWCharging, F("wwcharging"), F("Laden"))
MAKE_PSTR_LIST(wWRecharging, F("wwrecharging"), F("Nachladen"))
MAKE_PSTR_LIST(wWTempOK, F("wwtempok"), F("Temperatur ok"))
MAKE_PSTR_LIST(wWActive, F("wwactive"), F("aktiv"))
MAKE_PSTR_LIST(wWHeat, F("wwheat"), F("heizen"))
MAKE_PSTR_LIST(wWSetPumpPower, F("wwsetpumppower"), F("Soll Pumpenleistung"))
MAKE_PSTR_LIST(mixerTemp, F("mixertemp"), F("Mischertemperatur"))
MAKE_PSTR_LIST(tankMiddleTemp, F("tankmiddletemp"), F("Speicher mittel temperatur (TS3)"))
MAKE_PSTR_LIST(wWStarts, F("wwstarts"), F("Anzahl starts"))
MAKE_PSTR_LIST(wWStarts2, F("wwstarts2"), F("Kreis 2 Anzahl Starts"))
MAKE_PSTR_LIST(wWWorkM, F("wwworkm"), F("aktive Zeit"))
MAKE_PSTR_LIST(wWHystOn, F("wwhyston"), F("Hysterese Einschalttemperatur"))
MAKE_PSTR_LIST(wWHystOff, F("wwhystoff"), F("Hysterese Ausschalttemperatur"))
MAKE_PSTR_LIST(wwStarts2, F("wwstarts2"), F("Starts"))
MAKE_PSTR_LIST(wwWorkM, F("wwworkm"), F("aktive Zeit"))
MAKE_PSTR_LIST(wwHystOn, F("wwhyston"), F("Hysterese an"))
MAKE_PSTR_LIST(wwHystOff, F("wwhystoff"), F("Hysterese aus"))
MAKE_PSTR_LIST(wwProgMode, F("wwprogmode"), F("Programmmodus"))
MAKE_PSTR_LIST(wwCircProg, F("wwcircprog"), F("Zirkulationsprogramm"))
// MAKE_PSTR_LIST(wwDisinfect, F("wwdisinfect"), F("Desinfizieren")) // same as in boiler
MAKE_PSTR_LIST(wwDisinfectDay, F("wwdisinfectday"), F("Desinfizierung Tag"))
MAKE_PSTR_LIST(wwDisinfectHour, F("wwdisinfecthour"), F("Desinfizierung Stunde"))
MAKE_PSTR_LIST(wwMaxTemp, F("wwmaxtemp"), F("Maximale Temperatur"))
MAKE_PSTR_LIST(wwOneTimeKey, F("wwonetimekey"), F("Einmalladungstaste"))
MAKE_PSTR_LIST(switchtime, F("switchtime"), F("einzelne Programmschaltzeit"))
// mqtt values / commands
MAKE_PSTR_LIST(dateTime, F("datetime"), F("Datum/Zeit"))
MAKE_PSTR_LIST(errorCode, F("errorcode"), F("Fehlermeldung"))
MAKE_PSTR_LIST(ibaMainDisplay, F("display"), F("Anzeige"))
MAKE_PSTR_LIST(ibaLanguage, F("language"), F("Sprache"))
MAKE_PSTR_LIST(ibaClockOffset, F("clockoffset"), F("Uhrkorrektur"))
MAKE_PSTR_LIST(ibaBuildingType, F("building"), F("Gebäude"))
MAKE_PSTR_LIST(ibaCalIntTemperature, F("intoffset"), F("Korrektur interner Temperatur"))
MAKE_PSTR_LIST(ibaMinExtTemperature, F("minexttemp"), F("min Aussentemperatur"))
MAKE_PSTR_LIST(damping, F("damping"), F("Dämpfung der Außentemperatur"))
MAKE_PSTR_LIST(tempsensor1, F("inttemp1"), F("Temperatursensor 1"))
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(wwMode, F("wwmode"), F("modus"))
MAKE_PSTR_LIST(wwSetTempLow, F("wwsettemplow"), F("untere Solltemperatur"))
MAKE_PSTR_LIST(wwChargeDuration, F("wwchargeduration"), F("charge duration"))
MAKE_PSTR_LIST(wwExtra1, F("wwextra1"), F("Kreis 1 Extra"))
MAKE_PSTR_LIST(wwExtra2, F("wwextra2"), F("Kreis 2 Extra"))
MAKE_PSTR_LIST(selRoomTemp, F("seltemp"), F("Sollwert Raumtemperatur"))
MAKE_PSTR_LIST(roomTemp, F("currtemp"), F("aktuelle Raumtemperatur"))
MAKE_PSTR_LIST(mode, F("mode"), F("modus"))
MAKE_PSTR_LIST(modetype, F("modetype"), F("modus Typ"))
MAKE_PSTR_LIST(daytemp, F("daytemp"), F("Tagestemperatur"))
MAKE_PSTR_LIST(heattemp, F("heattemp"), F("Heizen Temperatur"))
MAKE_PSTR_LIST(nighttemp, F("nighttemp"), F("Nachttemperatur"))
MAKE_PSTR_LIST(ecotemp, F("ecotemp"), F("eco Temperatur"))
MAKE_PSTR_LIST(manualtemp, F("manualtemp"), F("manuelle Temperatur"))
MAKE_PSTR_LIST(tempautotemp, F("tempautotemp"), F("zwischenzeitliche Solltemperatur"))
MAKE_PSTR_LIST(comforttemp, F("comforttemp"), F("Komforttemperatur"))
MAKE_PSTR_LIST(summertemp, F("summertemp"), F("Sommertemperatur"))
MAKE_PSTR_LIST(designtemp, F("designtemp"), F("design-Temperatur"))
MAKE_PSTR_LIST(offsettemp, F("offsettemp"), F("Temperaturanhebung"))
MAKE_PSTR_LIST(minflowtemp, F("minflowtemp"), F("min Flusstemperatur"))
MAKE_PSTR_LIST(maxflowtemp, F("maxflowtemp"), F("max Flusstemperatur"))
MAKE_PSTR_LIST(roominfluence, F("roominfluence"), F("Raumeinfluss"))
MAKE_PSTR_LIST(nofrosttemp, F("nofrosttemp"), F("Frostschutztemperatur"))
MAKE_PSTR_LIST(targetflowtemp, F("targetflowtemp"), F("berechnete Flusstemperatur"))
MAKE_PSTR_LIST(heatingtype, F("heatingtype"), F("Heizungstyp"))
MAKE_PSTR_LIST(summersetmode, F("summersetmode"), F("Einstellung Sommerbetrieb"))
MAKE_PSTR_LIST(controlmode, F("controlmode"), F("Kontrollmodus"))
MAKE_PSTR_LIST(control, F("control"), F("Fernsteuerung"))
MAKE_PSTR_LIST(program, F("program"), F("Programm"))
MAKE_PSTR_LIST(pause, F("pause"), F("Pausenzeit"))
MAKE_PSTR_LIST(party, F("party"), F("Partyzeit"))
MAKE_PSTR_LIST(wwprio, F("wwprio"), F("dhw priority"))
MAKE_PSTR_LIST(holidaytemp, F("holidaytemp"), F("Urlaubstemperatur"))
MAKE_PSTR_LIST(summermode, F("summermode"), F("Sommerbetrieb"))
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(remotetemp, F("remotetemp"), F("Raumtemperatur der Fernsteuerung"))
// heatpump
MAKE_PSTR_LIST(airHumidity, F("airhumidity"), F("relative Luftfeuchte"))
MAKE_PSTR_LIST(dewTemperature, F("dewtemperature"), F("Taupunkttemperatur"))
// mixer
MAKE_PSTR_LIST(flowSetTemp, F("flowsettemp"), F("Sollwert Flusstemperatur"))
MAKE_PSTR_LIST(flowTempHc, F("flowtemphc"), F("Flusstemperatur des hk (TC1)"))
MAKE_PSTR_LIST(pumpStatus, F("pumpstatus"), F("Pumpenstatus des hk (PC1)"))
MAKE_PSTR_LIST(mixerStatus, F("valvestatus"), F("Mischerventil Position (VC1)"))
MAKE_PSTR_LIST(flowTempVf, F("flowtempvf"), F("Flusstemperatur am Kessel (T0/Vf)"))
MAKE_PSTR_LIST(wwPumpStatus, F("pumpstatus"), F("Pumpenstatus des wwk (PC1)"))
MAKE_PSTR_LIST(wwTempStatus, F("tempstatus"), F("Temperaturschalter des wwk (MC1)"))
MAKE_PSTR_LIST(wwTemp, F("wwtemp"), F("aktuelle Temperatur"))
// solar
MAKE_PSTR_LIST(type, F("type"), F("type"))
MAKE_PSTR_LIST(collectorTemp, F("collectortemp"), F("Kollektortemperatur (TS1)"))
MAKE_PSTR_LIST(tankBottomTemp, F("tankbottomtemp"), F("Speicher Bodentemperatur (TS2)"))
MAKE_PSTR_LIST(tank2BottomTemp, F("tank2bottomtemp"), F("2. Speicher Bodentemperatur (TS5)"))
MAKE_PSTR_LIST(heatExchangerTemp, F("heatexchangertemp"), F("wärmetauscher Temperatur (TS6)"))
MAKE_PSTR_LIST(collectorMaxTemp, F("collectormaxtemp"), F("maximale Kollektortemperatur"))
MAKE_PSTR_LIST(collectorMinTemp, F("collectormintemp"), F("minimale Kollektortemperatur"))
MAKE_PSTR_LIST(tankMaxTemp, F("tankmaxtemp"), F("maximale Speichertemperatur"))
MAKE_PSTR_LIST(solarPumpModulation, F("solarpumpmodulation"), F("Pumpenmodulation (PS1)"))
MAKE_PSTR_LIST(cylinderPumpModulation, F("cylinderpumpmodulation"), F("Speicherpumpenmodulation (PS5)"))
MAKE_PSTR_LIST(solarPump, F("solarpump"), F("Pumpe (PS1)"))
MAKE_PSTR_LIST(valveStatus, F("valvestatus"), F("ventilstatus"))
MAKE_PSTR_LIST(tankHeated, F("tankheated"), F("Speichertemperatur erreicht"))
MAKE_PSTR_LIST(collectorShutdown, F("collectorshutdown"), F("Kollektorabschaltung"))
MAKE_PSTR_LIST(pumpWorkTime, F("pumpworktime"), F("Pumpenlaufzeit"))
MAKE_PSTR_LIST(energyLastHour, F("energylasthour"), F("Energie letzte Std"))
MAKE_PSTR_LIST(energyTotal, F("energytotal"), F("Gesamtenergie"))
MAKE_PSTR_LIST(energyToday, F("energytoday"), F("Energie heute"))
MAKE_PSTR_LIST(wwTemp1, F("wwtemp1"), F("Temperatur 1"))
MAKE_PSTR_LIST(wwTemp3, F("wwtemp3"), F("Temperatur 3"))
MAKE_PSTR_LIST(wwTemp4, F("wwtemp4"), F("Temperatur 4"))
MAKE_PSTR_LIST(wwTemp5, F("wwtemp5"), F("Temperatur 5"))
MAKE_PSTR_LIST(wwTemp7, F("wwtemp7"), F("Temperatur 7"))
MAKE_PSTR_LIST(wwPump, F("wwpump"), F("Pumpe"))
MAKE_PSTR_LIST(wwMinTemp, F("wwmintemp"), F("minimale Temperatur"))
MAKE_PSTR_LIST(pumpMinMod, F("minpumpmod"), F("minimale Pumpenmodulation"))
MAKE_PSTR_LIST(maxFlow, F("maxflow"), F("maximaler Durchfluss"))
MAKE_PSTR_LIST(solarPower, F("solarpower"), F("aktuelle Leistung"))
MAKE_PSTR_LIST(solarPumpTurnonDiff, F("turnondiff"), F("Einschaltdifferenz"))
MAKE_PSTR_LIST(solarPumpTurnoffDiff, F("turnoffdiff"), F("Ausschaltdifferenz"))
//SM100
MAKE_PSTR_LIST(heatTransferSystem, F("heattransfersystem"), F("heattransfer system"))
MAKE_PSTR_LIST(externalTank, F("externaltank"), F("external tank"))
MAKE_PSTR_LIST(thermalDisinfect, F("thermaldisinfect"), F("thermal disinfection"))
MAKE_PSTR_LIST(heatMetering, F("heatmetering"), F("heatmetering"))
MAKE_PSTR_LIST(solarIsEnabled, F("solarenabled"), F("Solarmodul aktiviert"))
// telegram 0x035A
MAKE_PSTR_LIST(solarPumpMode, F("solarpumpmode"), F("solar pump mode"))
MAKE_PSTR_LIST(solarPumpKick, F("pumpkick"), F("pumpkick"))
MAKE_PSTR_LIST(plainWaterMode, F("plainwatermode"), F("plain water mode"))
MAKE_PSTR_LIST(doubleMatchFlow, F("doublematchflow"), F("doublematchflow"))
// telegram 0x380
MAKE_PSTR_LIST(climateZone, F("climatezone"), F("climate zone"))
MAKE_PSTR_LIST(collector1Area, F("collector1area"), F("collector 1 area"))
MAKE_PSTR_LIST(collector1Type, F("collector1type"), F("collector 1 type"))
// switch
MAKE_PSTR_LIST(activated, F("activated"), F("aktiviert"))
MAKE_PSTR_LIST(status, F("status"), F("Status"))
MAKE_PSTR_LIST(data11, F("data11"), F("unknown datafield 11"))
MAKE_PSTR_LIST(data12, F("data12"), F("unknown datafield 12"))
MAKE_PSTR_LIST(data8, F("data8"), F("unknown datafield 8"))
MAKE_PSTR_LIST(data0, F("data0"), F("unknown datafield 0"))
MAKE_PSTR_LIST(data1, F("data1"), F("unknown datafield 1"))
MAKE_PSTR_LIST(setting3, F("setting3"), F("unknown setting 3"))
MAKE_PSTR_LIST(setting4, F("setting4"), F("unknown setting 4"))

View File

@@ -46,7 +46,7 @@ MAKE_PSTR_WORD(restart)
MAKE_PSTR_WORD(format)
MAKE_PSTR_WORD(raw)
MAKE_PSTR_WORD(watch)
MAKE_PSTR_WORD(syslog_level)
MAKE_PSTR_WORD(syslog)
MAKE_PSTR_WORD(send)
MAKE_PSTR_WORD(telegram)
MAKE_PSTR_WORD(bus_id)
@@ -70,18 +70,22 @@ MAKE_PSTR_WORD(pin)
MAKE_PSTR_WORD(publish)
MAKE_PSTR_WORD(timeout)
MAKE_PSTR_WORD(board_profile)
MAKE_PSTR_WORD(counter)
MAKE_PSTR_WORD(sensorname)
// for commands
MAKE_PSTR_WORD(call)
MAKE_PSTR_WORD(cmd)
MAKE_PSTR_WORD(id)
MAKE_PSTR_WORD(hc)
MAKE_PSTR_WORD(wwc)
MAKE_PSTR_WORD(device)
MAKE_PSTR_WORD(data)
MAKE_PSTR_WORD(command)
MAKE_PSTR_WORD(commands)
MAKE_PSTR_WORD(info)
MAKE_PSTR_WORD(settings)
MAKE_PSTR_WORD(customizations)
MAKE_PSTR_WORD(value)
MAKE_PSTR_WORD(error)
MAKE_PSTR_WORD(entities)
@@ -97,9 +101,9 @@ MAKE_PSTR_WORD(controller)
MAKE_PSTR_WORD(connect)
MAKE_PSTR_WORD(heatpump)
MAKE_PSTR_WORD(generic)
MAKE_PSTR_WORD(dallassensor)
MAKE_PSTR_WORD(analogsensor)
MAKE_PSTR_WORD(unknown)
MAKE_PSTR_WORD(Dallassensor)
MAKE_PSTR_WORD(dallassensor)
// format strings
MAKE_PSTR(master_thermostat_fmt, "Master Thermostat device ID: %s")
@@ -110,16 +114,18 @@ MAKE_PSTR(board_profile_fmt, "Board Profile: %s")
MAKE_PSTR(mark_interval_fmt, "Mark interval: %lus")
MAKE_PSTR(wifi_ssid_fmt, "WiFi SSID: %s")
MAKE_PSTR(wifi_password_fmt, "WiFi Password: %S")
MAKE_PSTR(ethernet_option_fmt, "Ethernet option: %d")
MAKE_PSTR(tx_mode_fmt, "Tx mode: %d")
MAKE_PSTR(bus_id_fmt, "Bus ID: %02X")
MAKE_PSTR(log_level_fmt, "Log level: %s")
MAKE_STR(productid_fmt, "%s EMS Product ID")
MAKE_STR(productid_fmt, "%s EMS ProductID")
MAKE_PSTR_LIST(enum_syslog_level, F_(off), F("emerg"), F("alert"), F("crit"), F_(error), F("warn"), F("notice"), F_(info), F_(debug), F("trace"), F("all"))
MAKE_PSTR_LIST(enum_watch, F_(off), F_(on), F_(raw), F_(unknown))
MAKE_PSTR_LIST(enum_sensortype, F("none"), F("digital in"), F("counter"), F("adc"))
// strings
MAKE_PSTR(show_optional, "[devices | users | ems | mqtt | system | commands]")
MAKE_PSTR(EMSESP, "EMS-ESP")
MAKE_PSTR(cmd_optional, "[cmd]")
MAKE_PSTR(ha_optional, "[ha]")
@@ -134,6 +140,7 @@ MAKE_PSTR(sensorid_optional, "[sensor ID]")
MAKE_PSTR(id_optional, "[id|hc]")
MAKE_PSTR(data_optional, "[data]")
MAKE_PSTR(offset_optional, "[offset]")
MAKE_PSTR(length_optional, "[length]")
MAKE_PSTR(typeid_mandatory, "<type ID>")
MAKE_PSTR(deviceid_mandatory, "<device ID>")
MAKE_PSTR(device_type_optional, "[device]")
@@ -156,19 +163,23 @@ MAKE_PSTR_WORD(enum)
MAKE_PSTR_WORD(text)
MAKE_PSTR_WORD(2)
MAKE_PSTR_WORD(4)
MAKE_PSTR_WORD(10)
MAKE_PSTR_WORD(100)
MAKE_PSTR_WORD(60)
MAKE_PSTR_LIST(div2, F_(2))
MAKE_PSTR_LIST(div4, F_(4))
MAKE_PSTR_LIST(div10, F_(10))
MAKE_PSTR_LIST(div100, F_(100))
MAKE_PSTR_LIST(div60, F_(60))
MAKE_PSTR_LIST(mul10, F("*10"))
MAKE_PSTR_LIST(mul15, F("*15"))
MAKE_PSTR_LIST(div100, F_(100))
MAKE_PSTR_LIST(mul5, F("-5"))
MAKE_PSTR_LIST(mul10, F("-10"))
MAKE_PSTR_LIST(mul15, F("-15"))
// Unit Of Measurement mapping - maps to DeviceValueUOM_s in emsdevice.cpp
// uom - also used with HA see https://github.com/home-assistant/core/blob/d7ac4bd65379e11461c7ce0893d3533d8d8b8cbf/homeassistant/const.py#L384
MAKE_PSTR(blank, " ")
MAKE_PSTR(percent, "%")
MAKE_PSTR(degrees, "°C")
MAKE_PSTR(kwh, "kWh")
@@ -176,6 +187,7 @@ MAKE_PSTR(wh, "Wh")
MAKE_PSTR(bar, "bar")
MAKE_PSTR(minutes, "minutes")
MAKE_PSTR(hours, "hours")
MAKE_PSTR(days, "days")
MAKE_PSTR(ua, "uA")
MAKE_PSTR(lmin, "l/min")
MAKE_PSTR(kw, "kW")
@@ -183,24 +195,28 @@ MAKE_PSTR(w, "W")
MAKE_PSTR(kb, "KB")
MAKE_PSTR(seconds, "seconds")
MAKE_PSTR(dbm, "dBm")
MAKE_PSTR(fahrenheit, "°F")
MAKE_PSTR(mv, "mV")
MAKE_PSTR(sqm, "sqm")
MAKE_PSTR(times, "times")
MAKE_PSTR(oclock, "o'clock")
MAKE_PSTR(days, "days")
// TAG mapping - maps to DeviceValueTAG_s in emsdevice.cpp
// use empty string if want to suppress showing tags
// tags must not have spaces
// mqtt tags must not have spaces
MAKE_PSTR(tag_none, "")
MAKE_PSTR(tag_heartbeat, "")
MAKE_PSTR(tag_boiler_data, "")
MAKE_PSTR(tag_device_data_ww, "ww")
MAKE_PSTR(tag_device_data_ww, "dhw")
MAKE_PSTR(tag_thermostat_data, "")
MAKE_PSTR(tag_hc1, "hc1")
MAKE_PSTR(tag_hc2, "hc2")
MAKE_PSTR(tag_hc3, "hc3")
MAKE_PSTR(tag_hc4, "hc4")
MAKE_PSTR(tag_hc5, "hc5")
MAKE_PSTR(tag_hc6, "hc6")
MAKE_PSTR(tag_hc7, "hc7")
MAKE_PSTR(tag_hc8, "hc8")
MAKE_PSTR(tag_wwc1, "wwc1")
MAKE_PSTR(tag_wwc2, "wwc2")
MAKE_PSTR(tag_wwc3, "wwc3")
@@ -223,6 +239,7 @@ MAKE_PSTR(tag_hs15, "hs15")
MAKE_PSTR(tag_hs16, "hs16")
// MQTT topic names
// MAKE_PSTR(tag_heartbeat_mqtt, "heartbeat")
MAKE_PSTR(tag_boiler_data_mqtt, "")
MAKE_PSTR(tag_device_data_ww_mqtt, "ww")
@@ -242,22 +259,24 @@ MAKE_PSTR_WORD(hot)
MAKE_PSTR_WORD(eco)
MAKE_PSTR_WORD(intelligent)
MAKE_PSTR_WORD(flow)
MAKE_PSTR_WORD(manual)
MAKE_PSTR_WORD(buffer)
MAKE_PSTR(bufferedflow, "buffered flow")
MAKE_PSTR(layeredbuffer, "layered buffer")
MAKE_PSTR_WORD(maintenance)
// boiler lists
MAKE_PSTR_LIST(enum_off_time_date, F_(off), F_(time), F_(date))
MAKE_PSTR_LIST(tpl_date, F("Format: < dd.mm.yyyy >")) // template for text input
MAKE_PSTR_LIST(enum_off_time_date_manual, F_(off), F_(time), F_(date), F_(manual))
MAKE_PSTR_LIST(enum_freq, F_(off), F_(1x3min), F_(2x3min), F_(3x3min), F_(4x3min), F_(5x3min), F_(6x3min), F_(continuous))
MAKE_PSTR_LIST(enum_charge, F_(chargepump), F_(3wayvalve))
MAKE_PSTR_LIST(enum_comfort, F_(hot), F_(eco), F_(intelligent))
MAKE_PSTR_LIST(enum_flow, F_(off), F_(flow), F_(bufferedflow), F_(buffer), F_(layeredbuffer))
MAKE_PSTR_LIST(enum_reset, F_(maintenance), F_(error))
MAKE_PSTR_LIST(enum_bool, F_(off), F_(on))
MAKE_PSTR_LIST(enum_reset, F("-"), F_(maintenance), F_(error))
// MAKE_PSTR_LIST(enum_bool, F_(off), F_(on))
//heatpump
MAKE_PSTR_LIST(enum_hpactivity, F("none"), F("heating"), F("cooling"), F("warm water"), F("pool"))
MAKE_PSTR_LIST(enum_hpactivity, F("none"), F("heating"), F("cooling"), F("hot water"), F("pool"))
// mixer
MAKE_PSTR_LIST(enum_shunt, F("stopped"), F("opening"), F("closing"), F("open"), F("close"))
@@ -292,7 +311,6 @@ MAKE_PSTR_WORD(simple)
MAKE_PSTR_WORD(optimized)
MAKE_PSTR_WORD(nofrost)
MAKE_PSTR_WORD(comfort)
MAKE_PSTR_WORD(manual)
MAKE_PSTR_WORD(night)
MAKE_PSTR_WORD(day)
MAKE_PSTR_WORD(holiday)
@@ -315,6 +333,10 @@ 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_switchtime, F("Format: < p:nn.d.o.hh:mm > prog, no, day, on, time"))
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,
F_(internal_temperature),
F_(internal_setpoint),
@@ -335,15 +357,14 @@ MAKE_PSTR_LIST(enum_wwMode2, F_(off), F_(on), F_(auto))
MAKE_PSTR_LIST(enum_wwMode3, F_(on), F_(off), F_(auto))
MAKE_PSTR_LIST(enum_heatingtype, F_(off), F_(radiator), F_(convector), F_(floor))
MAKE_PSTR_LIST(enum_summermode, F_(summer), F_(auto), F_(winter))
MAKE_PSTR_LIST(enum_summer, F_(winter), F_(summer))
MAKE_PSTR_LIST(enum_mode, F_(manual), F_(auto)) // RC100, RC300, RC310
MAKE_PSTR_LIST(enum_mode2, F_(off), F_(manual), F_(auto)) // RC20
MAKE_PSTR_LIST(enum_mode3, F_(night), F_(day), F_(auto)) // RC35, RC30
MAKE_PSTR_LIST(enum_mode3, F_(night), F_(day), F_(auto)) // RC35, RC30, RC25
MAKE_PSTR_LIST(enum_mode4, F_(nofrost), F_(eco), F_(heat), F_(auto)) // JUNKERS
MAKE_PSTR_LIST(enum_mode5, F_(auto), F_(off)) // CRF
MAKE_PSTR_LIST(enum_mode6, F_(off), F_(night), F_(day)) // RC10
MAKE_PSTR_LIST(enum_hamode, F_(off), F_(heat), F_(auto), F_(heat), F_(off), F_(heat), F_(auto), F_(auto), F_(auto), F_(auto))
MAKE_PSTR_LIST(enum_mode6, F_(nofrost), F_(night), F_(day)) // RC10
MAKE_PSTR_LIST(enum_modetype, F_(eco), F_(comfort))
MAKE_PSTR_LIST(enum_modetype2, F_(day))
@@ -366,20 +387,20 @@ MAKE_PSTR_LIST(enum_progMode2, F("own_1"), F("family"), F("morning"), F("evening
MAKE_PSTR_LIST(enum_progMode3, F("family"), F("morning"), F("evening"), F("am"), F("pm"), F("midday"), F("singles"), F("seniors"))
MAKE_PSTR_LIST(enum_progMode4, F("prog_a"), F("prog_b"), F("prog_c"), F("prog_d"), F("prog_e"), F("prog_f"))
MAKE_PSTR_LIST(enum_switchmode, F_(off), F_(eco), F_(comfort), F_(heat))
// solar list
MAKE_PSTR_LIST(enum_solarmode, F_(constant), F("pwm"), F("analog"))
MAKE_PSTR_LIST(enum_collectortype, F("flat"), F("vacuum"))
// MQTT topic for homeassistant. Must include /
MAKE_PSTR(homeassistant, "homeassistant/")
MAKE_PSTR_LIST(enum_cylprio, F("cyl_1"), F("cyl_2"))
// id used to store the device ID. empty full name so only gets displayed in the MQTT payload
MAKE_PSTR_LIST(ID, F_(id))
// Boiler
// extra commands, with no json output
MAKE_PSTR_LIST(wwtapactivated, F("wwtapactivated"), F("turn on/off DHW by going into maintenance mode"))
MAKE_PSTR_LIST(reset, F("reset"), F("reset messages"))
MAKE_PSTR_LIST(wwtapactivated, F("wwtapactivated"), F("turn on/off"))
MAKE_PSTR_LIST(reset, F("reset"), F("reset"))
// single mqtt topics
MAKE_PSTR_WORD(heating_active)
@@ -388,7 +409,7 @@ MAKE_PSTR_WORD(response)
// mqtt, commands and text
MAKE_PSTR_LIST(heatingActive, F("heatingactive"), F("heating active"))
MAKE_PSTR_LIST(tapwaterActive, F("tapwateractive"), F("warm water active"))
MAKE_PSTR_LIST(tapwaterActive, F("tapwateractive"), F("tapwater active"))
MAKE_PSTR_LIST(selFlowTemp, F("selflowtemp"), F("selected flow temperature"))
MAKE_PSTR_LIST(selBurnPow, F("selburnpow"), F("burner selected max power"))
MAKE_PSTR_LIST(heatingPumpMod, F("heatingpumpmod"), F("heating pump modulation"))
@@ -401,10 +422,12 @@ MAKE_PSTR_LIST(sysPress, F("syspress"), F("system pressure"))
MAKE_PSTR_LIST(boilTemp, F("boiltemp"), F("actual boiler temperature"))
MAKE_PSTR_LIST(exhaustTemp, F("exhausttemp"), F("exhaust temperature"))
MAKE_PSTR_LIST(burnGas, F("burngas"), F("gas"))
MAKE_PSTR_LIST(burnGas2, F("burngas2"), F("gas stage 2"))
MAKE_PSTR_LIST(flameCurr, F("flamecurr"), F("flame current"))
MAKE_PSTR_LIST(heatingPump, F("heatingpump"), F("heating pump"))
MAKE_PSTR_LIST(fanWork, F("fanwork"), F("fan"))
MAKE_PSTR_LIST(ignWork, F("ignwork"), F("ignition"))
MAKE_PSTR_LIST(oilPreHeat, F("oilpreheat"), F("oil preheating"))
MAKE_PSTR_LIST(heatingActivated, F("heatingactivated"), F("heating activated"))
MAKE_PSTR_LIST(heatingTemp, F("heatingtemp"), F("heating temperature"))
MAKE_PSTR_LIST(pumpModMax, F("pumpmodmax"), F("burner pump max power"))
@@ -426,40 +449,41 @@ MAKE_PSTR_LIST(lastCode, F("lastcode"), F("last error code"))
MAKE_PSTR_LIST(serviceCode, F("servicecode"), F("service code"))
MAKE_PSTR_LIST(serviceCodeNumber, F("servicecodenumber"), F("service code number"))
MAKE_PSTR_LIST(maintenanceMessage, F("maintenancemessage"), F("maintenance message"))
MAKE_PSTR_LIST(maintenanceDate, F("maintenancedate"), F("maintenance set date"))
MAKE_PSTR_LIST(maintenanceDate, F("maintenancedate"), F("next maintenance date"))
MAKE_PSTR_LIST(maintenanceType, F_(maintenance), F("maintenance scheduled"))
MAKE_PSTR_LIST(maintenanceTime, F("maintenancetime"), F("maintenance set time"))
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(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 warm water"))
MAKE_PSTR_LIST(upTimeCompWw, F("uptimecompww"), F("operating time compressor dhw"))
MAKE_PSTR_LIST(upTimeCompPool, F("uptimecomppool"), F("operating time compressor pool"))
MAKE_PSTR_LIST(totalcompStarts, F("totalcompstarts"), F("total compressor control starts"))
MAKE_PSTR_LIST(totalCompStarts, F("totalcompstarts"), F("total compressor control starts"))
MAKE_PSTR_LIST(heatingStarts, F("heatingstarts"), F("heating control starts"))
MAKE_PSTR_LIST(coolingStarts, F("coolingstarts"), F("cooling control starts"))
MAKE_PSTR_LIST(wwStarts2, F("wwstarts2"), F("warm water 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(nrgConsCompHeating, F("nrgconscompheating"), F("energy consumption compressor heating"))
MAKE_PSTR_LIST(nrgConsCompWw, F("nrgconscompww"), F("energy consumption compressor warm water"))
MAKE_PSTR_LIST(nrgConsCompWw, F("nrgconscompww"), F("energy consumption compressor dhw"))
MAKE_PSTR_LIST(nrgConsCompCooling, F("nrgconscompcooling"), F("energy consumption compressor cooling"))
MAKE_PSTR_LIST(nrgConsCompPool, F("nrgconscomppool"), F("energy consumption compressor pool"))
MAKE_PSTR_LIST(nrgSuppTotal, F("nrgsupptotal"), F("total energy supplied"))
MAKE_PSTR_LIST(nrgSuppHeating, F("nrgsuppheating"), F("total energy supplied heating"))
MAKE_PSTR_LIST(nrgSuppWw, F("nrgsuppww"), F("total energy warm supplied warm water"))
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(auxElecHeatNrgConsHeating, F("auxelecheatnrgconsheating"), F("auxiliary electrical heater energy consumption heating"))
MAKE_PSTR_LIST(auxElecHeatNrgConsWW, F("auxelecheatnrgconsww"), F("auxiliary electrical heater energy consumption warm water"))
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"))
MAKE_PSTR_LIST(hpPower, F("hppower"), F("Compressor power output"))
MAKE_PSTR_LIST(hpCompOn, F("hpcompon"), F("HP Compressor"))
MAKE_PSTR_LIST(hpHeatingOn, F("hpheatingon"), F("HP Heating"))
MAKE_PSTR_LIST(hpCoolingOn, F("hpcoolingon"), F("HP Cooling"))
MAKE_PSTR_LIST(hpWwOn, F("hpwwon"), F("HP Warm water"))
MAKE_PSTR_LIST(hpWwOn, F("hpwwon"), F("HP dhw"))
MAKE_PSTR_LIST(hpPoolOn, F("hppoolon"), F("HP Pool"))
MAKE_PSTR_LIST(hpBrinePumpSpd, F("hpbrinepumpspd"), F("Brine Pump Speed"))
MAKE_PSTR_LIST(hpCompSpd, F("hpcompspd"), F("Compressor Speed"))
@@ -481,12 +505,8 @@ MAKE_PSTR_LIST(hpTr7, F("hptr7"), F("refrigerant temperature gas side (condenser
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)"))
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)"))
// the following are warm water for the boiler and automatically tagged with 'ww'
// 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"))
MAKE_PSTR_LIST(wwSelTempOff, F("wwseltempoff"), F("selected temperature for off"))
@@ -499,7 +519,7 @@ MAKE_PSTR_LIST(wwMaxPower, F("wwmaxpower"), F("max power"))
MAKE_PSTR_LIST(wwCircPump, F("wwcircpump"), F("circulation pump available"))
MAKE_PSTR_LIST(wwChargeType, F("wwchargetype"), F("charging type"))
MAKE_PSTR_LIST(wwDisinfectionTemp, F("wwdisinfectiontemp"), F("disinfection temperature"))
MAKE_PSTR_LIST(wwCircMode, F("wwcircmode"), F("circulation pump frequency"))
MAKE_PSTR_LIST(wwCircMode, F("wwcircmode"), F("circulation pump mode")) // also used in thermostat
MAKE_PSTR_LIST(wwCirc, F("wwcirc"), F("circulation active"))
MAKE_PSTR_LIST(wwCurTemp, F("wwcurtemp"), F("current intern temperature"))
MAKE_PSTR_LIST(wwCurTemp2, F("wwcurtemp2"), F("current extern temperature"))
@@ -508,61 +528,62 @@ MAKE_PSTR_LIST(wwStorageTemp1, F("wwstoragetemp1"), F("storage intern temperatur
MAKE_PSTR_LIST(wwStorageTemp2, F("wwstoragetemp2"), F("storage extern temperature"))
MAKE_PSTR_LIST(wwActivated, F("wwactivated"), F("activated"))
MAKE_PSTR_LIST(wwOneTime, F("wwonetime"), F("one time charging"))
MAKE_PSTR_LIST(wwDisinfect, F("wwdisinfect"), F("disinfection"))
MAKE_PSTR_LIST(wwDisinfecting, F("wwdisinfecting"), F("disinfecting"))
MAKE_PSTR_LIST(wwCharging, F("wwcharging"), F("charging"))
MAKE_PSTR_LIST(wwRecharging, F("wwrecharging"), F("recharging"))
MAKE_PSTR_LIST(wwTempOK, F("wwtempok"), F("temperature ok"))
MAKE_PSTR_LIST(wwActive, F("wwactive"), F("active"))
MAKE_PSTR_LIST(wwHeat, F("wwheat"), F("heating"))
MAKE_PSTR_LIST(ww3wayValve, F("ww3wayvalve"), F("3way valve active"))
MAKE_PSTR_LIST(wwSetPumpPower, F("wwsetpumppower"), F("set pump power"))
MAKE_PSTR_LIST(wwMixerTemp, F("wwmixertemp"), F("mixer temperature"))
MAKE_PSTR_LIST(wwTankMiddleTemp, F("wwtankmiddletemp"), F("tank middle temperature (TS3)"))
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(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"))
MAKE_PSTR_LIST(wwProgMode, F("wwprogmode"), F("program mode"))
MAKE_PSTR_LIST(wwCircProg, F("wwcircprog"), F("circulation program mode"))
// MAKE_PSTR_LIST(wwDisinfect, F("wwdisinfect"), F("disinfection")) // same as in boiler
MAKE_PSTR_LIST(wwDisinfectDay, F("wwdisinfectday"), F("disinfection day"))
MAKE_PSTR_LIST(wwDisinfectHour, F("wwdisinfecthour"), F("disinfection hour"))
MAKE_PSTR_LIST(wwDisinfectTime, F("wwdisinfecttime"), F("disinfection time"))
MAKE_PSTR_LIST(wwProgMode, F("wwprogmode"), F("program"))
MAKE_PSTR_LIST(wwCircProg, F("wwcircprog"), F("circulation program"))
MAKE_PSTR_LIST(wwMaxTemp, F("wwmaxtemp"), F("maximum temperature"))
MAKE_PSTR_LIST(wwOneTimeKey, F("wwonetimekey"), F("one time key function"))
// thermostat
// commands, with no long name so they only appear in the MQTT payloads
MAKE_PSTR_LIST(temp, F("temp"))
MAKE_PSTR_LIST(hatemp, F("hatemp"))
MAKE_PSTR_LIST(hamode, F("hamode"))
// mqtt values / commands
MAKE_PSTR_LIST(switchtime, F("switchtime"), F("single 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"))
MAKE_PSTR_LIST(wwcircswitchtime, F("wwcircswitchtime"), F("circulation program switchtime"))
MAKE_PSTR_LIST(dateTime, F("datetime"), F("date/time"))
MAKE_PSTR_LIST(errorCode, F("errorcode"), F("error code"))
MAKE_PSTR_LIST(ibaMainDisplay, F("display"), F("display"))
MAKE_PSTR_LIST(ibaLanguage, F("language"), F("language"))
MAKE_PSTR_LIST(ibaClockOffset, F("clockoffset"), F("clock offset"))
MAKE_PSTR_LIST(ibaBuildingType, F("building"), F("building"))
MAKE_PSTR_LIST(ibaBuildingType, F("building"), F("building type"))
MAKE_PSTR_LIST(heatingPID, F("heatingpid"), F("heating PID"))
MAKE_PSTR_LIST(ibaCalIntTemperature, F("intoffset"), F("offset internal temperature"))
MAKE_PSTR_LIST(ibaCalIntTemperature, F("intoffset"), F("internal temperature offset"))
MAKE_PSTR_LIST(ibaMinExtTemperature, F("minexttemp"), F("minimal external temperature"))
MAKE_PSTR_LIST(backlight, F("backlight"), F("key backlight"))
MAKE_PSTR_LIST(damping, F("damping"), F("damping outdoor temperature"))
MAKE_PSTR_LIST(tempsensor1, F("inttemp1"), F("temperature sensor 1"))
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"))
// thermostat ww
MAKE_PSTR_LIST(wwMode, F("wwmode"), F("mode"))
MAKE_PSTR_LIST(wwSetTempLow, F("wwsettemplow"), F("set low temperature"))
MAKE_PSTR_LIST(wwCharge, F("wwcharge"), F("charge"))
MAKE_PSTR_LIST(wwChargeDuration, F("wwchargeduration"), F("charge duration"))
MAKE_PSTR_LIST(wwDisinfect, F("wwdisinfect"), F("disinfection"))
MAKE_PSTR_LIST(wwDisinfectDay, F("wwdisinfectday"), F("disinfection day"))
MAKE_PSTR_LIST(wwDisinfectHour, F("wwdisinfecthour"), F("disinfection hour"))
MAKE_PSTR_LIST(wwDisinfectTime, F("wwdisinfecttime"), F("disinfection time"))
MAKE_PSTR_LIST(wwExtra1, F("wwextra1"), F("circuit 1 extra"))
MAKE_PSTR_LIST(wwExtra2, F("wwextra2"), F("circuit 2 extra"))
MAKE_PSTR_LIST(setpoint_roomTemp, F("seltemp"), F("selected room temperature"))
MAKE_PSTR_LIST(curr_roomTemp, F("currtemp"), F("current room temperature"))
MAKE_PSTR_LIST(wwDailyHeating, F("wwdailyheating"), F("daily heating"))
MAKE_PSTR_LIST(wwDailyHeatTime, F("wwdailyheattime"), F("daily heating time"))
// thermostat hc
MAKE_PSTR_LIST(selRoomTemp, F("seltemp"), F("selected room temperature"))
MAKE_PSTR_LIST(roomTemp, F("currtemp"), F("current room temperature"))
MAKE_PSTR_LIST(mode, F("mode"), F("mode"))
MAKE_PSTR_LIST(modetype, F("modetype"), F("mode type"))
MAKE_PSTR_LIST(fastheatup, F("fastheatup"), F("fast heatup"))
@@ -571,7 +592,7 @@ MAKE_PSTR_LIST(heattemp, F("heattemp"), F("heat temperature"))
MAKE_PSTR_LIST(nighttemp, F("nighttemp"), F("night temperature"))
MAKE_PSTR_LIST(ecotemp, F("ecotemp"), F("eco temperature"))
MAKE_PSTR_LIST(manualtemp, F("manualtemp"), F("manual temperature"))
MAKE_PSTR_LIST(tempautotemp, F("tempautotemp"), F("temporary room temperature automode"))
MAKE_PSTR_LIST(tempautotemp, F("tempautotemp"), F("temporary set temperature automode"))
MAKE_PSTR_LIST(comforttemp, F("comforttemp"), F("comfort temperature"))
MAKE_PSTR_LIST(summertemp, F("summertemp"), F("summer temperature"))
MAKE_PSTR_LIST(designtemp, F("designtemp"), F("design temperature"))
@@ -591,12 +612,11 @@ MAKE_PSTR_LIST(vacations, F("vacations"), F("vacation dates"))
MAKE_PSTR_LIST(program, F("program"), F("program"))
MAKE_PSTR_LIST(pause, F("pause"), F("pause time"))
MAKE_PSTR_LIST(party, F("party"), F("party time"))
MAKE_PSTR_LIST(wwprio, F("wwprio"), F("warm water priority"))
MAKE_PSTR_LIST(wwprio, F("wwprio"), F("dhw priority"))
MAKE_PSTR_LIST(holidaytemp, F("holidaytemp"), F("holiday temperature"))
MAKE_PSTR_LIST(summermode, F("summermode"), F("summer mode"))
MAKE_PSTR_LIST(holidaymode, F("holidaymode"), F("holiday mode"))
MAKE_PSTR_LIST(flowtempoffset, F("flowtempoffset"), F("flow temperature offset"))
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(remotetemp, F("remotetemp"), F("room temperature from remote"))
@@ -609,67 +629,123 @@ MAKE_PSTR_LIST(dewTemperature, F("dewtemperature"), F("dew point temperature"))
// mixer
MAKE_PSTR_LIST(flowSetTemp, F("flowsettemp"), F("setpoint flow temperature"))
MAKE_PSTR_LIST(flowTempHc, F("flowtemphc"), F("flow temperature in assigned hc (TC1)"))
MAKE_PSTR_LIST(pumpStatus, F("pumpstatus"), F("pump status in assigned hc (PC1)"))
MAKE_PSTR_LIST(mixerStatus, F("valvestatus"), F("mixing valve actuator in assigned hc (VC1)"))
MAKE_PSTR_LIST(flowTempHc, F("flowtemphc"), F("flow temperature (TC1)"))
MAKE_PSTR_LIST(pumpStatus, F("pumpstatus"), F("pump status (PC1)"))
MAKE_PSTR_LIST(mixerStatus, F("valvestatus"), F("mixing valve actuator (VC1)"))
MAKE_PSTR_LIST(flowTempVf, F("flowtempvf"), F("flow temperature in header (T0/Vf)"))
MAKE_PSTR_LIST(mixerSetTime, F("valvesettime"), F("time to set valve"))
// mixer prefixed with wwc
MAKE_PSTR_LIST(wwPumpStatus, F("pumpstatus"), F("pump status in assigned wwc (PC1)"))
MAKE_PSTR_LIST(wwTempStatus, F("wwtempstatus"), F("temperature switch in assigned wwc (MC1)"))
MAKE_PSTR_LIST(wwTemp, F("wwtemp"), F("current temperature"))
// mixer pool
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)"))
// solar
MAKE_PSTR_LIST(type, F("type"), F("type"))
MAKE_PSTR_LIST(collectorTemp, F("collectortemp"), F("collector temperature (TS1)"))
MAKE_PSTR_LIST(tankBottomTemp, F("tankbottomtemp"), F("tank bottom temperature (TS2)"))
MAKE_PSTR_LIST(tank2BottomTemp, F("tank2bottomtemp"), F("second tank bottom temperature (TS5)"))
MAKE_PSTR_LIST(collector2Temp, F("collector2temp"), F("collector 2 temperature (TS7)"))
MAKE_PSTR_LIST(cylBottomTemp, F("cylbottomtemp"), F("cylinder bottom temperature (TS2)"))
MAKE_PSTR_LIST(cyl2BottomTemp, F("cyl2bottomtemp"), F("second cylinder bottom temperature (TS5)"))
MAKE_PSTR_LIST(heatExchangerTemp, F("heatexchangertemp"), F("heat exchanger temperature (TS6)"))
MAKE_PSTR_LIST(cylMiddleTemp, F("cylmiddletemp"), F("cylinder middle temperature (TS3)"))
MAKE_PSTR_LIST(retHeatAssist, F("retheatassist"), F("return temperature heat assistance (TS4)"))
// correct name for M1? value not found, try this:
MAKE_PSTR_LIST(m1Valve, F("heatassistvalve"), F("heat assistance valve (M1)"))
MAKE_PSTR_LIST(m1Power, F("heatassistpower"), F("heat assistance valve power (M1)"))
MAKE_PSTR_LIST(collectorMaxTemp, F("collectormaxtemp"), F("maximum collector temperature"))
MAKE_PSTR_LIST(collectorMinTemp, F("collectormintemp"), F("minimum collector temperature"))
MAKE_PSTR_LIST(tankMaxTemp, F("tankmaxtemp"), F("maximum tank temperature"))
MAKE_PSTR_LIST(solarPumpModulation, F("solarpumpmodulation"), F("pump modulation (PS1)"))
MAKE_PSTR_LIST(cylinderPumpModulation, F("cylinderpumpmodulation"), F("cylinder pump modulation (PS5)"))
MAKE_PSTR_LIST(cylMaxTemp, F("cylmaxtemp"), F("maximum cylinder temperature"))
// MAKE_PSTR_LIST(cyl2MaxTemp, F("cyl2maxtemp"), F("maximum cylinder 2 temperature"))
MAKE_PSTR_LIST(solarPumpMod, F("solarpumpmod"), F("pump modulation (PS1)"))
MAKE_PSTR_LIST(cylPumpMod, F("cylpumpmod"), F("cylinder pump modulation (PS5)"))
MAKE_PSTR_LIST(solarPump, F("solarpump"), F("pump (PS1)"))
MAKE_PSTR_LIST(solarPump2, F("solarpump2"), F("pump 2 (PS4)"))
MAKE_PSTR_LIST(solarPump2Mod, F("solarpump2mod"), F("pump 2 modulation (PS4)"))
MAKE_PSTR_LIST(valveStatus, F("valvestatus"), F("valve status"))
MAKE_PSTR_LIST(tankHeated, F("tankheated"), F("tank heated"))
MAKE_PSTR_LIST(cylHeated, F("cylheated"), F("cyl heated"))
MAKE_PSTR_LIST(collectorShutdown, F("collectorshutdown"), F("collector shutdown"))
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(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"))
MAKE_PSTR_LIST(solarPumpTurnonDiff, F("turnondiff"), F("pump turn on difference"))
MAKE_PSTR_LIST(solarPumpTurnoffDiff, F("turnoffdiff"), F("pump turn off difference"))
MAKE_PSTR_LIST(pump2MinMod, F("pump2minmod"), F("minimum pump 2 modulation"))
MAKE_PSTR_LIST(solarPump2TurnonDiff, F("turnondiff2"), F("pump 2 turn on difference"))
MAKE_PSTR_LIST(solarPump2TurnoffDiff, F("turnoffdiff2"), F("pump 2 turn off difference"))
// solar ww
MAKE_PSTR_LIST(wwTemp1, F("wwtemp1"), F("temperature 1"))
MAKE_PSTR_LIST(wwTemp3, F("wwtemp3"), F("temperature 3"))
MAKE_PSTR_LIST(wwTemp4, F("wwtemp4"), F("temperature 4"))
MAKE_PSTR_LIST(wwTemp5, F("wwtemp5"), F("temperature 5"))
MAKE_PSTR_LIST(wwTemp7, F("wwtemp7"), F("temperature 7"))
MAKE_PSTR_LIST(wwPump, F("wwpump"), F("pump"))
// solar ww and mixer wwc
MAKE_PSTR_LIST(wwMinTemp, F("wwmintemp"), F("minimum temperature"))
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"))
MAKE_PSTR_LIST(solarPumpTurnonDiff, F("turnondiff"), F("pump turn on difference"))
MAKE_PSTR_LIST(solarPumpTurnoffDiff, F("turnoffdiff"), F("pump turn off difference"))
MAKE_PSTR_LIST(wwRedTemp, F("wwredtemp"), F("reduced temperature"))
MAKE_PSTR_LIST(wwDailyTemp, F("wwdailytemp"), F("daily temperature"))
MAKE_PSTR_LIST(wwKeepWarm, F("wwkeepwarm"), F("keep warm"))
MAKE_PSTR_LIST(wwStatus2, F("wwstatus2"), F("status 2"))
MAKE_PSTR_LIST(enum_wwStatus2, F(""), F(""), F(""), F("no_heat"), F(""), F(""), F("heatrequest"), F(""), F("disinfecting"), F("hold"))
MAKE_PSTR_LIST(wwPumpMod, F("wwpumpmod"), F("pump modulation"))
MAKE_PSTR_LIST(wwFlow, F("wwflow"), F("flow rate"))
// extra mixer ww
MAKE_PSTR_LIST(wwRequiredTemp, F("wwrequiredtemp"), F("required temperature"))
MAKE_PSTR_LIST(wwDiffTemp, F("wwdifftemp"), F("start differential temperature"))
// Solar SM100
//SM100
MAKE_PSTR_LIST(heatTransferSystem, F("heattransfersystem"), F("heattransfer system"))
MAKE_PSTR_LIST(externalTank, F("externaltank"), F("external tank"))
MAKE_PSTR_LIST(externalCyl, F("externalcyl"), F("external cylinder"))
MAKE_PSTR_LIST(thermalDisinfect, F("thermaldisinfect"), F("thermal disinfection"))
MAKE_PSTR_LIST(heatMetering, F("heatmetering"), F("heatmetering"))
MAKE_PSTR_LIST(solarIsEnabled, F("solarenabled"), F("solarmodule enabled"))
// telegram 0x035A
MAKE_PSTR_LIST(solarPumpMode, F("solarpumpmode"), F("solar pump mode"))
MAKE_PSTR_LIST(solarPumpMode, F("solarpumpmode"), F("pump mode"))
MAKE_PSTR_LIST(solarPumpKick, F("pumpkick"), F("pumpkick"))
MAKE_PSTR_LIST(plainWaterMode, F("plainwatermode"), F("plain water mode"))
MAKE_PSTR_LIST(doubleMatchFlow, F("doublematchflow"), F("doublematchflow"))
MAKE_PSTR_LIST(solarPump2Mode, F("pump2mode"), F("pump 2 mode"))
MAKE_PSTR_LIST(solarPump2Kick, F("pump2kick"), F("pumpkick 2"))
// telegram 0x035F
MAKE_PSTR_LIST(cylPriority, F("cylpriority"), F("cylinder priority"))
// telegram 0x380
MAKE_PSTR_LIST(climateZone, F("climatezone"), F("climate zone"))
MAKE_PSTR_LIST(collector1Area, F("collector1area"), F("collector 1 area"))
MAKE_PSTR_LIST(collector1Type, F("collector1type"), F("collector 1 type"))
MAKE_PSTR_LIST(collector2Area, F("collector2area"), F("collector 2 area"))
MAKE_PSTR_LIST(collector2Type, F("collector2type"), F("collector 2 type"))
// telegram 0x0363 heatCounter
MAKE_PSTR_LIST(heatCntFlowTemp, F("heatcntflowtemp"), F("heat counter flow temperature"))
MAKE_PSTR_LIST(heatCntRetTemp, F("heatcntrettemp"), F("heat counter return temperature"))
MAKE_PSTR_LIST(heatCnt, F("heatcnt"), F("heat counter impulses"))
MAKE_PSTR_LIST(swapFlowTemp, F("swapflowtemp"), F("swap flow temperature (TS14)"))
MAKE_PSTR_LIST(swapRetTemp, F("swaprettemp"), F("swap return temperature (TS15)"))
// switch
MAKE_PSTR_LIST(activated, F("activated"), F("activated"))
MAKE_PSTR_LIST(status, F("status"), F("status"))
// unknown fields to track (SM10)
MAKE_PSTR_LIST(data11, F("data11"), F("unknown datafield 11"))
MAKE_PSTR_LIST(data12, F("data12"), F("unknown datafield 12"))
MAKE_PSTR_LIST(data8, F("data8"), F("unknown datafield 8"))
MAKE_PSTR_LIST(data0, F("data0"), F("unknown datafield 0"))
MAKE_PSTR_LIST(data1, F("data1"), F("unknown datafield 1"))
MAKE_PSTR_LIST(setting3, F("setting3"), F("unknown setting 3"))
MAKE_PSTR_LIST(setting4, F("setting4"), F("unknown setting 4"))
// RF sensor, id 0x40, telegram 0x435
MAKE_PSTR_LIST(RFTemp, F("rftemp"), F("RF room temperature sensor"));

View File

@@ -19,6 +19,7 @@
#include "mqtt.h"
#include "emsesp.h"
#include "version.h"
#include "emsdevice.h"
namespace emsesp {
@@ -35,21 +36,39 @@ uint32_t Mqtt::publish_time_mixer_;
uint32_t Mqtt::publish_time_sensor_;
uint32_t Mqtt::publish_time_other_;
bool Mqtt::mqtt_enabled_;
uint8_t Mqtt::ha_climate_format_;
bool Mqtt::ha_enabled_;
uint8_t Mqtt::nested_format_;
std::string Mqtt::discovery_prefix_;
bool Mqtt::send_response_;
bool Mqtt::publish_single_;
std::deque<Mqtt::QueuedMqttMessage> Mqtt::mqtt_messages_;
std::vector<Mqtt::MQTTSubFunction> Mqtt::mqtt_subfunctions_;
uint16_t Mqtt::mqtt_publish_fails_ = 0;
uint32_t Mqtt::mqtt_publish_fails_ = 0;
bool Mqtt::connecting_ = false;
bool Mqtt::initialized_ = false;
uint8_t Mqtt::connectcount_ = 0;
uint16_t Mqtt::mqtt_message_id_ = 0;
uint32_t Mqtt::mqtt_message_id_ = 0;
char will_topic_[Mqtt::MQTT_TOPIC_MAX_SIZE]; // because MQTT library keeps only char pointer
// Home Assistant specific
// icons from https://materialdesignicons.com used with the UOMs (unit of measurements)
MAKE_PSTR_WORD(measurement)
MAKE_PSTR_WORD(total_increasing)
MAKE_PSTR(icondegrees, "mdi:coolant-temperature") // DeviceValueUOM::DEGREES
MAKE_PSTR(iconpercent, "mdi:percent-outline") // DeviceValueUOM::PERCENT
MAKE_PSTR(icontime, "mdi:clock-outline") // DeviceValueUOM::SECONDS MINUTES & HOURS
MAKE_PSTR(iconkb, "mdi:memory") // DeviceValueUOM::KB
MAKE_PSTR(iconlmin, "mdi:water-boiler") // DeviceValueUOM::LMIN
MAKE_PSTR(iconkwh, "mdi:transmission-tower") // DeviceValueUOM::KWH & WH
MAKE_PSTR(iconua, "mdi:lightning-bolt-circle") // DeviceValueUOM::UA
MAKE_PSTR(iconbar, "mdi:gauge") // DeviceValueUOM::BAR
MAKE_PSTR(iconkw, "mdi:omega") // DeviceValueUOM::KW & W
MAKE_PSTR(icondbm, "mdi:wifi-strength-2") // DeviceValueUOM::DBM
MAKE_PSTR(iconnum, "mdi:counter") // DeviceValueUOM::NONE
MAKE_PSTR(icondevice, "mdi:home-automation") // for devices in HA
uuid::log::Logger Mqtt::logger_{F_(mqtt), uuid::log::Facility::DAEMON};
// subscribe to an MQTT topic, and store the associated callback function
@@ -81,6 +100,12 @@ void Mqtt::subscribe(const uint8_t device_type, const std::string & topic, mqtt_
queue_subscribe_message(topic);
}
// subscribe without storing to subfunctions
void Mqtt::subscribe(const std::string & topic) {
// add to MQTT queue as a subscribe operation
queue_subscribe_message(topic);
}
// resubscribe to all MQTT topics
// if it's already in the queue, ignore it
void Mqtt::resubscribe() {
@@ -147,7 +172,7 @@ void Mqtt::loop() {
if (publish_time_other_ && (currentMillis - last_publish_other_ > publish_time_other_)) {
last_publish_other_ = (currentMillis / publish_time_other_) * publish_time_other_;
EMSESP::publish_other_values();
EMSESP::publish_other_values(); // switch and heatpump
} else
if (publish_time_sensor_ && (currentMillis - last_publish_sensor_ > publish_time_sensor_)) {
@@ -182,7 +207,9 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) {
for (const auto & message : mqtt_messages_) {
auto content = message.content_;
char topic[MQTT_TOPIC_MAX_SIZE];
if ((strncmp(content->topic.c_str(), "homeassistant/", 13) != 0)) {
// prefix base, only if it's not a discovery topic
if (content->topic.compare(0, discovery_prefix().size(), discovery_prefix()) == 0) {
snprintf(topic, sizeof(topic), "%s/%s", Mqtt::base().c_str(), content->topic.c_str());
} else {
snprintf(topic, sizeof(topic), "%s", content->topic.c_str());
@@ -303,12 +330,12 @@ void Mqtt::show_topic_handlers(uuid::console::Shell & shell, const uint8_t devic
return;
}
shell.print(F(" Subscribed MQTT topics: "));
for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
if (mqtt_subfunction.device_type_ == device_type) {
shell.printf(F("%s "), mqtt_subfunction.topic_.c_str());
}
}
// shell.print(F(" Subscribed MQTT topics: "));
// for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
// if (mqtt_subfunction.device_type_ == device_type) {
// shell.printf(F("%s "), mqtt_subfunction.topic_.c_str());
// }
// }
shell.println();
}
@@ -360,14 +387,15 @@ void Mqtt::reset_mqtt() {
void Mqtt::load_settings() {
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & mqttSettings) {
mqtt_base_ = mqttSettings.base.c_str(); // Convert String to std::string
mqtt_qos_ = mqttSettings.mqtt_qos;
mqtt_retain_ = mqttSettings.mqtt_retain;
mqtt_enabled_ = mqttSettings.enabled;
ha_enabled_ = mqttSettings.ha_enabled;
ha_climate_format_ = mqttSettings.ha_climate_format;
nested_format_ = mqttSettings.nested_format;
send_response_ = mqttSettings.send_response;
mqtt_base_ = mqttSettings.base.c_str(); // Convert String to std::string
mqtt_qos_ = mqttSettings.mqtt_qos;
mqtt_retain_ = mqttSettings.mqtt_retain;
mqtt_enabled_ = mqttSettings.enabled;
ha_enabled_ = mqttSettings.ha_enabled;
nested_format_ = mqttSettings.nested_format;
publish_single_ = mqttSettings.publish_single;
send_response_ = mqttSettings.send_response;
discovery_prefix_ = mqttSettings.discovery_prefix.c_str();
// convert to milliseconds
publish_time_boiler_ = mqttSettings.publish_time_boiler * 1000;
@@ -465,6 +493,9 @@ void Mqtt::set_publish_time_sensor(uint16_t publish_time) {
}
bool Mqtt::get_publish_onchange(uint8_t device_type) {
if (publish_single_ && !ha_enabled_) {
return false;
}
if (device_type == EMSdevice::DeviceType::BOILER) {
if (!publish_time_boiler_) {
return true;
@@ -584,7 +615,7 @@ void Mqtt::ha_status() {
JsonObject dev = doc.createNestedObject("dev");
dev["name"] = F_(EMSESP); // "EMS-ESP"
dev["sw"] = EMSESP_APP_VERSION;
dev["sw"] = "v" + std::string(EMSESP_APP_VERSION);
dev["mf"] = FJSON("proddy");
dev["mdl"] = F_(EMSESP); // "EMS-ESP"
JsonArray ids = dev.createNestedArray("ids");
@@ -596,39 +627,18 @@ void Mqtt::ha_status() {
// create the sensors - must match the MQTT payload keys
if (!EMSESP::system_.ethernet_connected()) {
publish_ha_sensor_config(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("WiFi RSSI"), EMSdevice::DeviceType::SYSTEM, F("rssi"), DeviceValueUOM::DBM);
publish_ha_sensor_config(DeviceValueType::INT,
DeviceValueTAG::TAG_HEARTBEAT,
F("WiFi strength"),
EMSdevice::DeviceType::SYSTEM,
F("wifistrength"),
DeviceValueUOM::PERCENT);
publish_system_ha_sensor_config(DeviceValueType::INT, F("WiFi RSSI"), F("rssi"), DeviceValueUOM::DBM);
publish_system_ha_sensor_config(DeviceValueType::INT, F("WiFi strength"), F("wifistrength"), DeviceValueUOM::PERCENT);
}
publish_ha_sensor_config(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Uptime"), EMSdevice::DeviceType::SYSTEM, F("uptime"), DeviceValueUOM::NONE);
publish_ha_sensor_config(DeviceValueType::INT,
DeviceValueTAG::TAG_HEARTBEAT,
F("Uptime (sec)"),
EMSdevice::DeviceType::SYSTEM,
F("uptime_sec"),
DeviceValueUOM::SECONDS);
publish_ha_sensor_config(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Free memory"), EMSdevice::DeviceType::SYSTEM, F("freemem"), DeviceValueUOM::KB);
publish_ha_sensor_config(DeviceValueType::INT,
DeviceValueTAG::TAG_HEARTBEAT,
F("MQTT fails"),
EMSdevice::DeviceType::SYSTEM,
F("mqttfails"),
DeviceValueUOM::TIMES);
publish_ha_sensor_config(DeviceValueType::INT,
DeviceValueTAG::TAG_HEARTBEAT,
F("Rx received"),
EMSdevice::DeviceType::SYSTEM,
F("rxreceived"),
DeviceValueUOM::TIMES);
publish_ha_sensor_config(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Rx fails"), EMSdevice::DeviceType::SYSTEM, F("rxfails"), DeviceValueUOM::TIMES);
publish_ha_sensor_config(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Tx reads"), EMSdevice::DeviceType::SYSTEM, F("txreads"), DeviceValueUOM::TIMES);
publish_ha_sensor_config(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Tx writes"), EMSdevice::DeviceType::SYSTEM, F("txwrites"), DeviceValueUOM::TIMES);
publish_ha_sensor_config(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Tx fails"), EMSdevice::DeviceType::SYSTEM, F("txfails"), DeviceValueUOM::TIMES);
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::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);
publish_system_ha_sensor_config(DeviceValueType::INT, F("Rx fails"), F("rxfails"), DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::INT, F("Tx reads"), F("txreads"), DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::INT, F("Tx writes"), F("txwrites"), DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::INT, F("Tx fails"), F("txfails"), DeviceValueUOM::NONE);
}
// add sub or pub task to the queue.
@@ -639,16 +649,6 @@ std::shared_ptr<const MqttMessage> Mqtt::queue_message(const uint8_t operation,
return nullptr;
}
// if it's a publish and the payload is empty, stop
/*
if ((operation == Operation::PUBLISH) && (payload.empty())) {
#ifdef EMSESP_DEBUG
LOG_WARNING("[DEBUG] Publish empty payload - quitting");
#endif
return nullptr;
}
*/
// take the topic and prefix the base, unless its for HA
std::shared_ptr<MqttMessage> message;
message = std::make_shared<MqttMessage>(operation, topic, payload, retain);
@@ -668,6 +668,7 @@ std::shared_ptr<const MqttMessage> Mqtt::queue_message(const uint8_t operation,
// if the queue is full, make room but removing the last one
if (mqtt_messages_.size() >= MAX_MQTT_MESSAGES) {
mqtt_messages_.pop_front();
LOG_WARNING(F("Queue overflow, removing one message"));
}
mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message));
@@ -744,12 +745,12 @@ void Mqtt::publish_ha(const std::string & topic) {
return;
}
std::string fulltopic = read_flash_string(F_(homeassistant)) + topic;
std::string fulltopic = Mqtt::discovery_prefix() + topic;
#if defined(EMSESP_DEBUG)
LOG_DEBUG(F("[DEBUG] Publishing empty HA topic=%s"), fulltopic.c_str());
#endif
publish(fulltopic); // call it immediately, don't queue it
publish(fulltopic);
}
// publish a Home Assistant config topic and payload, with retain flag off.
@@ -762,7 +763,7 @@ void Mqtt::publish_ha(const std::string & topic, const JsonObject & payload) {
payload_text.reserve(measureJson(payload) + 1);
serializeJson(payload, payload_text); // convert json to string
std::string fulltopic = read_flash_string(F_(homeassistant)) + topic;
std::string fulltopic = Mqtt::discovery_prefix() + topic;
#if defined(EMSESP_STANDALONE)
LOG_DEBUG(F("Publishing HA topic=%s, payload=%s"), fulltopic.c_str(), payload_text.c_str());
#elif defined(EMSESP_DEBUG)
@@ -785,7 +786,7 @@ void Mqtt::process_queue() {
auto message = mqtt_message.content_;
char topic[MQTT_TOPIC_MAX_SIZE];
if (message->topic.find(read_flash_string(F_(homeassistant))) == 0) {
if (message->topic.find(discovery_prefix_) == 0) {
strcpy(topic, message->topic.c_str()); // leave topic as it is
} else {
snprintf(topic, MQTT_TOPIC_MAX_SIZE, "%s/%s", mqtt_base_.c_str(), message->topic.c_str());
@@ -851,27 +852,74 @@ void Mqtt::process_queue() {
mqtt_messages_.pop_front(); // remove the message from the queue
}
void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdevice::DeviceValueType
uint8_t tag, // EMSdevice::DeviceValueTAG
const __FlashStringHelper * name,
const uint8_t device_type, // EMSdevice::DeviceType
const __FlashStringHelper * entity,
const uint8_t uom) { // EMSdevice::DeviceValueUOM (0=NONE)
publish_ha_sensor_config(type, tag, name, device_type, entity, uom, false, false);
// publish HA sensor for System using the heartbeat tag
void Mqtt::publish_system_ha_sensor_config(uint8_t type, const __FlashStringHelper * name, const __FlashStringHelper * entity, const uint8_t uom) {
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
JsonObject dev_json = doc.createNestedObject("dev");
JsonArray ids = dev_json.createNestedArray("ids");
ids.add("ems-esp");
publish_ha_sensor_config(type, DeviceValueTAG::TAG_HEARTBEAT, name, EMSdevice::DeviceType::SYSTEM, entity, uom, false, false, false, nullptr, 0, 0, 0, dev_json);
}
// create's a ha sensor config topic from a device value object
// and also takes a flag to see whether it will also create the main HA device config
void Mqtt::publish_ha_sensor_config(DeviceValue & dv, const std::string & model, const std::string & brand, const bool remove, const bool create_device_config) {
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> dev_json;
// HA config for a sensor and binary_sensor entity
// always create the ids
JsonArray ids = dev_json.createNestedArray("ids");
char ha_device[40];
std::string device_type_name = EMSdevice::device_type_2_device_name(dv.device_type);
snprintf(ha_device, sizeof(ha_device), "ems-esp-%s", device_type_name.c_str());
ids.add(ha_device);
if (create_device_config) {
device_type_name[0] = toupper(device_type_name[0]); // capitalize
dev_json["name"] = "EMS-ESP " + device_type_name;
dev_json["mf"] = brand;
dev_json["mdl"] = model;
dev_json["via_device"] = "ems-esp";
}
// calculate the min and max
int16_t dv_set_min, dv_set_max;
(void)dv.get_min_max(dv_set_min, dv_set_max);
publish_ha_sensor_config(dv.type,
dv.tag,
dv.full_name,
dv.device_type,
dv.short_name,
dv.uom,
remove,
create_device_config,
dv.has_cmd,
dv.options,
dv.options_size,
dv_set_min,
dv_set_max,
dev_json.as<JsonObject>());
}
// MQTT discovery configs
// entity must match the key/value pair in the *_data topic
// note: some string copying here into chars, it looks messy but does help with heap fragmentation issues
void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdevice::DeviceValueType
uint8_t tag, // EMSdevice::DeviceValueTAG
const __FlashStringHelper * name,
const uint8_t device_type, // EMSdevice::DeviceType
const __FlashStringHelper * entity,
const uint8_t uom, // EMSdevice::DeviceValueUOM (0=NONE)
const bool remove, // true if we want to remove this topic
const bool has_cmd) {
// note: some extra string copying done here, it looks messy but does help with heap fragmentation issues
void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdevice::DeviceValueType
uint8_t tag, // EMSdevice::DeviceValueTAG
const __FlashStringHelper * name, // fullname
const uint8_t device_type, // EMSdevice::DeviceType
const __FlashStringHelper * entity, // shortname
const uint8_t uom, // EMSdevice::DeviceValueUOM (0=NONE)
const bool remove, // true if we want to remove this topic
const bool create_device_config, // true if need to create main device config
const bool has_cmd,
const __FlashStringHelper * const * options,
uint8_t options_size,
const int16_t dv_set_min,
const int16_t dv_set_max,
const JsonObject & dev_json) {
// ignore if name (fullname) is empty
if (name == nullptr) {
return;
@@ -890,35 +938,119 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdevi
}
// build unique identifier which will be used in the topic, replacing all . with _ as not to break HA
std::string uniq(50, '\0');
snprintf(&uniq[0], uniq.capacity() + 1, "%s_%s", device_name, new_entity);
std::replace(uniq.begin(), uniq.end(), '.', '_');
char uniq[101];
snprintf(uniq, sizeof(uniq), "%s_%s", device_name, new_entity);
Helpers::replace_char(uniq, '.', '_');
// create the topic
// use_ha_sensor is true if we're using the Sensor Entity https://developers.home-assistant.io/docs/core/entity/sensor
bool use_ha_sensor = false;
// 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 (type == DeviceValueType::BOOL) {
snprintf(topic, sizeof(topic), "binary_sensor/%s/%s/config", mqtt_base_.c_str(), uniq.c_str()); // binary sensor
// if it's a command then we can use Number, Switch. Otherwise stick to Sensor
if (has_cmd) {
switch (type) {
case DeviceValueType::INT:
case DeviceValueType::UINT:
case DeviceValueType::SHORT:
case DeviceValueType::USHORT:
case DeviceValueType::ULONG:
// 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/
snprintf(topic, sizeof(topic), "switch/%s/%s/config", mqtt_base_.c_str(), uniq);
break;
case DeviceValueType::ENUM:
// select - https://www.home-assistant.io/integrations/select.mqtt
snprintf(topic, sizeof(topic), "select/%s/%s/config", mqtt_base_.c_str(), uniq);
break;
default:
// plain old sensor
snprintf(topic, sizeof(topic), "sensor/%s/%s/config", mqtt_base_.c_str(), uniq);
break;
}
} else {
snprintf(topic, sizeof(topic), "sensor/%s/%s/config", mqtt_base_.c_str(), uniq.c_str()); // normal HA sensor, not a boolean one
// plain old read only device entity
if (type == DeviceValueType::BOOL) {
snprintf(topic, sizeof(topic), "binary_sensor/%s/%s/config", mqtt_base_.c_str(), uniq); // binary sensor
} else {
use_ha_sensor = true;
snprintf(topic, sizeof(topic), "sensor/%s/%s/config", mqtt_base_.c_str(), uniq); // normal HA sensor, not a boolean one
}
}
// if we're asking to remove this topic, send an empty payload
// if we're asking to remove this topic, send an empty payload and exit
// https://github.com/emsesp/EMS-ESP32/issues/196
if (remove) {
LOG_WARNING(F("Lost device value for %s. Removing HA config"), uniq.c_str());
LOG_DEBUG(F("Removing HA config for %s"), uniq);
publish_ha(topic);
return;
}
bool have_tag = !EMSdevice::tag_to_string(tag).empty();
// nested_format is 1 if nested, otherwise 2 for single topics
bool is_nested = (nested_format_ == 1);
bool have_tag = !EMSdevice::tag_to_string(tag).empty();
bool is_nested = (nested_format_ == 1); // nested_format is 1 if nested, otherwise 2 for single topics
// build the payload
DynamicJsonDocument doc(EMSESP_JSON_SIZE_HA_CONFIG);
doc["~"] = mqtt_base_;
doc["uniq_id"] = uniq;
const char * ic_ha = "ic"; // icon - only set this if there is no device class
const char * sc_ha = "state_class"; // state class
const char * uom_ha = "unit_of_meas"; // unit of measure
// handle commands, which are device entities that are writable
// we add the command topic parameter
// note: there is no way to handle strings in HA so datetimes (e.g. set_datetime, set_holiday, set_wwswitchtime etc) are excluded
if (has_cmd) {
// command topic back to EMS-ESP
char command_topic[105];
snprintf(command_topic, sizeof(command_topic), "~/%s", uniq);
Helpers::replace_char(command_topic, '_', '/');
doc["command_topic"] = command_topic;
// for enums, add options
if (type == DeviceValueType::ENUM) {
JsonArray option_list = doc.createNestedArray("options");
for (uint8_t i = 0; i < options_size; i++) {
option_list.add(options[i]);
}
} else if (type != DeviceValueType::STRING) {
// Must be Numeric....
// mode can be auto, slider or box. Because its fiddly and error prone, force conversion to box
// but... this is not currently supported in HA MQTT Number yet!
// doc["mode"] = "box";
}
// set min and max values, if we have a valid range
if (dv_set_min != 0 || dv_set_max != 0) {
doc["min"] = dv_set_min;
doc["max"] = dv_set_max;
if ((uom == DeviceValueUOM::DEGREES) || (uom == DeviceValueUOM::DEGREES_R)) {
doc["step"] = 0.5;
}
}
// set icons
// since these don't have a device class we need to add the icon ourselves
switch (uom) {
case DeviceValueUOM::DEGREES:
case DeviceValueUOM::DEGREES_R:
doc[ic_ha] = F_(icondegrees);
break;
case DeviceValueUOM::PERCENT:
doc[ic_ha] = F_(iconpercent);
break;
default:
break;
}
}
// state topic
char stat_t[MQTT_TOPIC_MAX_SIZE];
snprintf(stat_t, sizeof(stat_t), "~/%s", tag_to_topic(device_type, tag).c_str());
@@ -936,7 +1068,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdevi
// value template
// if its nested mqtt format then use the appended entity name, otherwise take the original
char val_tpl[50];
char val_tpl[75];
if (is_nested) {
snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", new_entity);
} else {
@@ -944,126 +1076,109 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdevi
}
doc["val_tpl"] = val_tpl;
// look at the device value type
// special case to handle booleans
// applies to both Binary Sensor (read only) and a Switch (for a command)
// always render boolean as strings true & false
// and has no unit of measure or icon
if (type == DeviceValueType::BOOL) {
// how to render boolean. HA only accepts String values
char result[10];
doc[F("payload_on")] = Helpers::render_boolean(result, true);
doc[F("payload_off")] = Helpers::render_boolean(result, false);
doc[sc_ha] = F_(measurement);
} else {
// set default state and device class for HA
auto set_state_class = State_class::NONE;
auto set_device_class = Device_class::NONE;
// unit of measure and map the HA icon
// always set the uom
if (uom != DeviceValueUOM::NONE) {
doc["unit_of_meas"] = EMSdevice::uom_to_string(uom);
doc[uom_ha] = EMSdevice::uom_to_string(uom);
}
}
// this next section is building using the Sensor Entity
// https://developers.home-assistant.io/docs/core/entity/sensor
// for read only sensors. It uses a device class to determine icon
// and state class
if (use_ha_sensor) {
const char * dc_ha = "device_class"; // device class
switch (uom) {
case DeviceValueUOM::DEGREES:
doc["ic"] = F_(icondegrees);
set_device_class = Device_class::TEMPERATURE;
case DeviceValueUOM::DEGREES_R:
doc[sc_ha] = F_(measurement);
doc[dc_ha] = F("temperature"); // no icon needed
break;
case DeviceValueUOM::PERCENT:
doc["ic"] = F_(iconpercent);
set_device_class = Device_class::POWER_FACTOR;
doc[sc_ha] = F_(measurement);
doc[dc_ha] = F("power_factor"); // no icon needed
break;
case DeviceValueUOM::SECONDS:
case DeviceValueUOM::MINUTES:
case DeviceValueUOM::HOURS:
doc["ic"] = F_(icontime);
doc[ic_ha] = F_(icontime);
if (type == DeviceValueType::TIME) {
doc[sc_ha] = F_(total_increasing);
} else {
doc[sc_ha] = F_(measurement);
}
break;
case DeviceValueUOM::KB:
doc["ic"] = F_(iconkb);
doc[ic_ha] = F_(iconkb);
break;
case DeviceValueUOM::LMIN:
doc["ic"] = F_(iconlmin);
doc[ic_ha] = F_(iconlmin);
doc[sc_ha] = F_(measurement);
break;
case DeviceValueUOM::WH:
if (entity == FL_(energyToday)[0]) {
doc[sc_ha] = F_(total_increasing);
} else {
doc[sc_ha] = F_(measurement);
}
doc[dc_ha] = F("energy"); // no icon needed
break;
case DeviceValueUOM::KWH:
doc["ic"] = F_(iconkwh);
set_state_class = State_class::TOTAL_INCREASING;
set_device_class = Device_class::ENERGY;
doc[sc_ha] = F_(total_increasing);
doc[dc_ha] = F("energy"); // no icon needed
break;
case DeviceValueUOM::UA:
doc["ic"] = F_(iconua);
doc[ic_ha] = F_(iconua);
doc[sc_ha] = F_(measurement);
break;
case DeviceValueUOM::BAR:
doc["ic"] = F_(iconbar);
set_device_class = Device_class::PRESSURE;
doc[sc_ha] = F_(measurement);
doc[dc_ha] = F("pressure");
break;
case DeviceValueUOM::W:
case DeviceValueUOM::KW:
doc["ic"] = F_(iconkw);
set_state_class = State_class::MEASUREMENT;
set_device_class = Device_class::POWER;
doc[sc_ha] = F_(measurement);
doc[dc_ha] = F("power");
break;
case DeviceValueUOM::DBM:
doc["ic"] = F_(icondbm);
set_device_class = Device_class::SIGNAL_STRENGTH;
doc[sc_ha] = F_(measurement);
doc[dc_ha] = F("signal_strength");
break;
case DeviceValueUOM::NONE:
if (type == DeviceValueType::INT || type == DeviceValueType::UINT || type == DeviceValueType::SHORT || type == DeviceValueType::USHORT
|| type == DeviceValueType::ULONG) {
doc["ic"] = F_(iconnum);
// for device entities which have numerical values, with no UOM
if ((type != DeviceValueType::STRING)
&& (type == DeviceValueType::INT || type == DeviceValueType::UINT || type == DeviceValueType::SHORT || type == DeviceValueType::USHORT
|| type == DeviceValueType::ULONG)) {
doc[ic_ha] = F_(iconnum); // set icon
// determine if its a measurement or total increasing
// most of the values are measurement. for example Tx Reads will increment but can be reset to 0 after a restart
// all the starts are increasing, and they are ULONGs
if (type == DeviceValueType::ULONG) {
doc[sc_ha] = F_(total_increasing);
} else {
doc[sc_ha] = F_(measurement); // default to measurement
}
}
break;
case DeviceValueUOM::TIMES:
set_state_class = State_class::TOTAL_INCREASING;
doc["ic"] = F_(iconnum);
break;
default:
break;
}
// see if we need to set the state_class and device_class
// ignore any commands
if (!has_cmd) {
// state class
if (set_state_class == State_class::MEASUREMENT) {
doc["state_class"] = F("measurement");
} else if (set_state_class == State_class::TOTAL_INCREASING) {
doc["state_class"] = F("total_increasing");
}
// device class
switch (set_device_class) {
case Device_class::ENERGY:
doc["device_class"] = F("energy");
break;
case Device_class::POWER:
doc["device_class"] = F("power");
break;
case Device_class::POWER_FACTOR:
doc["device_class"] = F("power_factor");
break;
case Device_class::PRESSURE:
doc["device_class"] = F("pressure");
break;
case Device_class::SIGNAL_STRENGTH:
doc["device_class"] = F("signal_strength");
break;
case Device_class::TEMPERATURE:
doc["device_class"] = F("temperature");
break;
default:
break;
}
}
}
JsonObject dev = doc.createNestedObject("dev");
JsonArray ids = dev.createNestedArray("ids");
// for System commands we'll use the ID EMS-ESP
if (device_type == EMSdevice::DeviceType::SYSTEM) {
ids.add("ems-esp");
} else {
char ha_device[40];
snprintf(ha_device, sizeof(ha_device), "ems-esp-%s", device_name);
ids.add(ha_device);
}
// add the dev json object to the end
doc["dev"] = dev_json;
publish_ha(topic, doc.as<JsonObject>());
}

View File

@@ -19,22 +19,13 @@
#ifndef EMSESP_MQTT_H_
#define EMSESP_MQTT_H_
#include <Arduino.h>
#include <ArduinoJson.h>
#include <string>
#include <vector>
#include <deque>
#include <functional>
#include <AsyncMqttClient.h>
#include "helpers.h"
#include "system.h"
#include "console.h"
#include "command.h"
#include <uuid/log.h>
#include "emsdevicevalue.h"
using uuid::console::Shell;
@@ -88,15 +79,12 @@ class Mqtt {
};
// for Home Assistant
enum class State_class { NONE, MEASUREMENT, TOTAL_INCREASING };
enum class Device_class { NONE, TEMPERATURE, POWER_FACTOR, ENERGY, PRESSURE, POWER, SIGNAL_STRENGTH };
static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 128; // note this should really match the user setting in mqttSettings.maxTopicLength
static void on_connect();
static void subscribe(const uint8_t device_type, const std::string & topic, mqtt_sub_function_p cb);
static void subscribe(const std::string & topic);
static void resubscribe();
static void publish(const std::string & topic, const std::string & payload);
@@ -112,21 +100,25 @@ class Mqtt {
static void publish_ha(const __FlashStringHelper * topic, const JsonObject & payload);
static void publish_ha(const std::string & topic);
static void publish_ha_sensor_config(uint8_t type,
uint8_t tag,
const __FlashStringHelper * name,
const uint8_t device_type,
const __FlashStringHelper * entity,
const uint8_t uom,
const bool remove,
const bool has_cmd);
static void
publish_ha_sensor_config(DeviceValue & dv, const std::string & model, const std::string & brand, const bool remove, const bool create_device_config = false);
static void publish_ha_sensor_config(uint8_t type,
uint8_t tag,
const __FlashStringHelper * name,
const uint8_t device_type,
const __FlashStringHelper * entity,
const uint8_t uom);
static void publish_ha_sensor_config(uint8_t type,
uint8_t tag,
const __FlashStringHelper * name,
const uint8_t device_type,
const __FlashStringHelper * entity,
const uint8_t uom,
const bool remove,
const bool create_device_config,
const bool has_cmd,
const __FlashStringHelper * const * options,
uint8_t options_size,
const int16_t dv_set_min,
const int16_t dv_set_max,
const JsonObject & dev_json);
static void publish_system_ha_sensor_config(uint8_t type, const __FlashStringHelper * name, const __FlashStringHelper * entity, const uint8_t uom);
static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type);
static void show_mqtt(uuid::console::Shell & shell);
@@ -165,11 +157,19 @@ class Mqtt {
return mqtt_base_;
}
// returns the discovery MQTT topic prefix and adds a /
static std::string discovery_prefix() {
if (discovery_prefix_.empty()) {
return std::string{};
}
return discovery_prefix_ + "/";
}
static void base(const char * base) {
mqtt_base_ = base;
}
static uint16_t publish_count() {
static uint32_t publish_count() {
return mqtt_message_id_;
}
@@ -179,21 +179,25 @@ class Mqtt {
static void reset_mqtt();
static uint8_t ha_climate_format() {
return ha_climate_format_;
}
// nested_format is 1 if nested, otherwise 2 for single topics
static uint8_t nested_format() {
return nested_format_;
}
static void nested_format(uint8_t nested_format) {
nested_format_ = nested_format;
static bool is_nested() {
return nested_format_ == 1;
}
static void ha_climate_format(uint8_t ha_climate_format) {
ha_climate_format_ = ha_climate_format;
static bool publish_single() {
return publish_single_;
}
static void publish_single(bool publish_single) {
publish_single_ = publish_single;
}
static void nested_format(uint8_t nested_format) {
nested_format_ = nested_format;
}
static bool ha_enabled() {
@@ -227,7 +231,7 @@ class Mqtt {
static const std::string tag_to_topic(uint8_t device_type, uint8_t tag);
struct QueuedMqttMessage {
const uint16_t id_;
const uint32_t id_;
const std::shared_ptr<const MqttMessage> content_;
uint8_t retry_count_;
uint16_t packet_id_;
@@ -247,7 +251,7 @@ class Mqtt {
static uuid::log::Logger logger_;
static AsyncMqttClient * mqttClient_;
static uint16_t mqtt_message_id_;
static uint32_t mqtt_message_id_;
static constexpr uint32_t MQTT_PUBLISH_WAIT = 100; // delay between sending publishes, to account for large payloads
static constexpr uint8_t MQTT_PUBLISH_MAX_RETRY = 3; // max retries for giving up on publishing
@@ -285,7 +289,7 @@ class Mqtt {
static bool connecting_;
static bool initialized_;
static uint16_t mqtt_publish_fails_;
static uint32_t mqtt_publish_fails_;
static uint8_t connectcount_;
// settings, copied over
@@ -300,9 +304,10 @@ class Mqtt {
static uint32_t publish_time_other_;
static uint32_t publish_time_sensor_;
static bool mqtt_enabled_;
static uint8_t ha_climate_format_;
static bool ha_enabled_;
static uint8_t nested_format_;
static std::string discovery_prefix_;
static bool publish_single_;
static bool send_response_;
};

View File

@@ -80,15 +80,6 @@ void Shower::loop() {
LOG_DEBUG(F("[Shower] finished with duration %d"), duration_);
}
}
#if defined(EMSESP_DEBUG)
else {
if (shower_state_) {
Mqtt::publish("message", "shower state is ON");
} else {
Mqtt::publish("message", "shower state is OFF");
}
}
#endif
// reset everything
timer_start_ = 0;
@@ -133,9 +124,17 @@ void Shower::shower_alert_start() {
void Shower::publish_shower_data() {
StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> doc;
char result[10];
doc["shower_timer"] = Helpers::render_boolean(result, shower_timer_);
doc["shower_alert"] = Helpers::render_boolean(result, shower_alert_);
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
doc["shower_timer"] = shower_timer_;
doc["shower_alert"] = shower_alert_;
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
doc["shower_timer"] = shower_timer_ ? 1 : 0;
doc["shower_alert"] = shower_alert_ ? 1 : 0;
} else {
char result[10];
doc["shower_timer"] = Helpers::render_boolean(result, shower_timer_);
doc["shower_alert"] = Helpers::render_boolean(result, shower_alert_);
}
// only publish shower duration if there is a value
if (duration_ > SHOWER_MIN_DURATION) {
@@ -165,6 +164,7 @@ void Shower::set_shower_state(bool state, bool force) {
}
old_shower_state_ = shower_state_; // copy current state
// always publish as a string
char s[7];
Mqtt::publish(F("shower_active"), Helpers::render_boolean(s, shower_state_)); // https://github.com/emsesp/EMS-ESP/issues/369
@@ -175,13 +175,16 @@ void Shower::set_shower_state(bool state, bool force) {
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
doc["name"] = FJSON("Shower Active");
doc["uniq_id"] = FJSON("shower_active");
doc["~"] = Mqtt::base(); // default ems-esp
doc["~"] = Mqtt::base();
doc["stat_t"] = FJSON("~/shower_active");
// always render boolean as strings for HA
char result[10];
doc[F("payload_on")] = Helpers::render_boolean(result, true);
doc[F("payload_off")] = Helpers::render_boolean(result, false);
JsonObject dev = doc.createNestedObject("dev");
JsonArray ids = dev.createNestedArray("ids");
JsonObject dev = doc.createNestedObject("dev");
JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp");
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];

View File

@@ -54,21 +54,40 @@ bool System::restart_requested_ = false;
// send on/off to a gpio pin
// value: true = HIGH, false = LOW
// e.g. http://ems-esp/api?device=system&cmd=pin&data=1&id=2
bool System::command_pin(const char * value, const int8_t id) {
#ifndef EMSESP_STANDALONE
if (!is_valid_gpio(id)) {
LOG_INFO(F("invalid GPIO number"));
LOG_INFO(F("Invalid GPIO number"));
return false;
}
bool v = false;
if (Helpers::value2bool(value, v)) {
bool v = false;
std::string v1 = {7, '\0'};
int v2 = 0;
if (id == 25 && Helpers::value2number(value, v2)) {
if (v2 >= 0 && v2 <= 255) {
dacWrite(id, v2);
return true;
}
} else if (Helpers::value2bool(value, v)) {
pinMode(id, OUTPUT);
digitalWrite(id, v);
LOG_INFO(F("GPIO %d set to %s"), id, v ? "HIGH" : "LOW");
return true;
} else if (Helpers::value2string(value, v1)) {
if (v1 == "input" || v1 == "in" || v1 == "-1") {
pinMode(id, INPUT);
v = digitalRead(id);
LOG_INFO(F("GPIO %d set input, state %s"), id, v ? "HIGH" : "LOW");
return true;
}
}
LOG_INFO(F("GPIO %d: invalid value"), id);
#endif
return false;
}
@@ -80,7 +99,7 @@ bool System::command_send(const char * value, const int8_t id) {
// fetch device values
bool System::command_fetch(const char * value, const int8_t id) {
std::string value_s(14, '\0');
std::string value_s;
if (Helpers::value2string(value, value_s)) {
if (value_s == "all") {
LOG_INFO(F("Requesting data from EMS devices"));
@@ -107,7 +126,7 @@ bool System::command_fetch(const char * value, const int8_t id) {
// mqtt publish
bool System::command_publish(const char * value, const int8_t id) {
std::string value_s(14, '\0');
std::string value_s;
if (Helpers::value2string(value, value_s)) {
if (value_s == "ha") {
EMSESP::publish_all(true); // includes HA
@@ -126,16 +145,17 @@ bool System::command_publish(const char * value, const int8_t id) {
EMSESP::publish_device_values(EMSdevice::DeviceType::MIXER);
return true;
} else if (value_s == "other") {
EMSESP::publish_other_values();
EMSESP::publish_other_values(); // switch and heat pump
return true;
} else if (value_s == read_flash_string(F_(dallassensor))) {
} else if ((value_s == read_flash_string(F_(dallassensor))) || (value_s == read_flash_string(F_(analogsensor)))) {
EMSESP::publish_sensor_values(true);
return true;
}
}
EMSESP::publish_all(); // ignore value and id
EMSESP::publish_all();
LOG_INFO(F("Publishing all data to MQTT"));
return true;
}
@@ -163,6 +183,9 @@ bool System::command_watch(const char * value, const int8_t id) {
EMSESP::watch_id(0);
}
EMSESP::watch(w);
if (Mqtt::publish_single()) {
Mqtt::publish(F("system/watch"), read_flash_string(FL_(enum_watch)[w]).c_str());
}
return true;
}
uint16_t i = Helpers::hextoint(value);
@@ -171,6 +194,12 @@ bool System::command_watch(const char * value, const int8_t id) {
if (EMSESP::watch() == EMSESP::Watch::WATCH_OFF) {
EMSESP::watch(EMSESP::Watch::WATCH_ON);
}
if (Mqtt::publish_single()) {
char s[10];
snprintf(s, sizeof(s), "0x%04X", i);
Mqtt::publish(F("system/watch"), s);
// Mqtt::publish(F("system/watch"), read_flash_string(FL_(enum_watch)[EMSESP::watch()]).c_str());
}
return true;
}
return false;
@@ -188,7 +217,7 @@ void System::system_restart() {
// saves all settings
void System::wifi_reconnect() {
LOG_INFO(F("Wifi reconnecting..."));
LOG_INFO(F("WiFi reconnecting..."));
Shell::loop_all();
EMSESP::console_.loop();
delay(1000); // wait a second
@@ -196,7 +225,6 @@ void System::wifi_reconnect() {
EMSESP::esp8266React.getNetworkSettingsService()->callUpdateHandlers("local"); // in case we've changed ssid or password
}
// format fs
// format the FS. Wipes everything.
void System::format(uuid::console::Shell & shell) {
auto msg = F("Formatting file system. This will reset all settings to their defaults");
@@ -234,7 +262,7 @@ void System::syslog_start() {
syslog_.hostname(hostname().c_str());
// register the command
Command::add(EMSdevice::DeviceType::SYSTEM, F_(syslog_level), System::command_syslog_level, F("changes syslog level"), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(syslog), System::command_syslog_level, F("change the syslog level"), CommandFlag::ADMIN_ONLY);
} else if (was_enabled) {
// in case service is still running, this flushes the queue
@@ -244,30 +272,50 @@ void System::syslog_start() {
syslog_.mark_interval(0);
syslog_.destination("");
}
if (Mqtt::publish_single()) {
Mqtt::publish(F("system/syslog"), syslog_enabled_ ? read_flash_string(FL_(enum_syslog_level)[syslog_level_ + 1]).c_str() : "off");
if (EMSESP::watch_id() == 0 || EMSESP::watch() == 0) {
Mqtt::publish(F("system/watch"), read_flash_string(FL_(enum_watch)[EMSESP::watch()]).c_str());
} else {
char s[10];
snprintf(s, sizeof(s), "0x%04X", EMSESP::watch_id());
Mqtt::publish(F("system/watch"), s);
}
}
#endif
}
// read all the settings except syslog from the config files and store locally
// read some specific system settings to store locally for faster access
void System::get_settings() {
EMSESP::webSettingsService.read([&](WebSettings & settings) {
// Button
pbutton_gpio_ = settings.pbutton_gpio;
// ADC
pbutton_gpio_ = settings.pbutton_gpio;
analog_enabled_ = settings.analog_enabled;
low_clock_ = settings.low_clock;
hide_led_ = settings.hide_led;
led_gpio_ = settings.led_gpio;
board_profile_ = settings.board_profile;
telnet_enabled_ = settings.telnet_enabled;
// Sysclock
low_clock_ = settings.low_clock;
rx_gpio_ = settings.rx_gpio;
tx_gpio_ = settings.tx_gpio;
dallas_gpio_ = settings.dallas_gpio;
// LED
hide_led_ = settings.hide_led;
led_gpio_ = settings.led_gpio;
syslog_enabled_ = settings.syslog_enabled;
syslog_level_ = settings.syslog_level;
syslog_mark_interval_ = settings.syslog_mark_interval;
syslog_host_ = settings.syslog_host;
syslog_port_ = settings.syslog_port;
// Board profile
board_profile_ = settings.board_profile;
fahrenheit_ = settings.fahrenheit;
bool_format_ = settings.bool_format;
enum_format_ = settings.enum_format;
readonly_mode_ = settings.readonly_mode;
// Ethernet PHY
phy_type_ = settings.phy_type;
phy_type_ = settings.phy_type;
eth_power_ = settings.eth_power;
eth_phy_addr_ = settings.eth_phy_addr;
eth_clock_mode_ = settings.eth_clock_mode;
});
}
@@ -328,7 +376,7 @@ void System::start(uint32_t heap_start) {
#ifndef EMSESP_STANDALONE
// disable bluetooth module
periph_module_disable(PERIPH_BT_MODULE);
// periph_module_disable(PERIPH_BT_MODULE);
if (low_clock_) {
setCpuFrequencyMhz(160);
}
@@ -336,12 +384,10 @@ void System::start(uint32_t heap_start) {
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) {
hostname(networkSettings.hostname.c_str()); // sets the hostname
LOG_INFO(F("System name: %s"), hostname().c_str());
});
commands_init(); // console & api commands
led_init(false); // init LED
adc_init(false); // analog ADC
button_init(false); // the special button
network_init(false); // network
syslog_start(); // start Syslog
@@ -349,21 +395,6 @@ void System::start(uint32_t heap_start) {
EMSESP::init_uart(); // start UART
}
// adc and bluetooth
void System::adc_init(bool refresh) {
if (refresh) {
get_settings();
}
#ifndef EMSESP_STANDALONE
// disable ADC
/*
if (!analog_enabled_) {
adc_power_release(); // turn off ADC to save power if not needed
}
*/
#endif
}
// button single click
void System::button_OnClick(PButton & b) {
LOG_DEBUG(F("Button pressed - single click"));
@@ -403,9 +434,9 @@ void System::button_init(bool refresh) {
if (is_valid_gpio(pbutton_gpio_)) {
if (!myPButton_.init(pbutton_gpio_, HIGH)) {
LOG_INFO(F("Multi-functional button not detected"));
LOG_DEBUG(F("Multi-functional button not detected"));
} else {
LOG_INFO(F("Multi-functional button enabled"));
LOG_DEBUG(F("Multi-functional button enabled"));
}
} else {
LOG_WARNING(F("Invalid button GPIO. Check config."));
@@ -424,8 +455,8 @@ void System::led_init(bool refresh) {
}
if ((led_gpio_ != 0) && is_valid_gpio(led_gpio_)) {
pinMode(led_gpio_, OUTPUT); // 0 means disabled
digitalWrite(led_gpio_, hide_led_ ? !LED_ON : LED_ON);
pinMode(led_gpio_, OUTPUT); // 0 means disabled
digitalWrite(led_gpio_, !LED_ON); // start with LED off
}
}
@@ -462,9 +493,6 @@ void System::loop() {
led_monitor(); // check status and report back using the LED
system_check(); // check system health
if (analog_enabled_) {
measure_analog();
}
// send out heartbeat
uint32_t currentMillis = uuid::get_uptime();
@@ -509,29 +537,30 @@ bool System::heartbeat_json(JsonObject & output) {
output["bus_status"] = FJSON("disconnected");
}
output["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
output["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
output["uptime_sec"] = uuid::get_uptime_sec();
output["rxreceived"] = EMSESP::rxservice_.telegram_count();
output["rxfails"] = EMSESP::rxservice_.telegram_error_count();
output["txreads"] = EMSESP::txservice_.telegram_read_count();
output["txwrites"] = EMSESP::txservice_.telegram_write_count();
output["txfails"] = EMSESP::txservice_.telegram_fail_count();
output["txfails"] = EMSESP::txservice_.telegram_read_fail_count() + EMSESP::txservice_.telegram_write_fail_count();
if (Mqtt::enabled()) {
output["mqttfails"] = Mqtt::publish_fails();
output["mqttfails"] = Mqtt::publish_fails();
}
if (EMSESP::dallas_enabled()) {
output["dallasfails"] = EMSESP::sensor_fails();
output["apicalls"] = WebAPIService::api_count(); // + WebAPIService::api_fails();
output["apifails"] = WebAPIService::api_fails();
if (EMSESP::dallas_enabled() || EMSESP::analog_enabled()) {
output["sensorreads"] = EMSESP::dallassensor_.reads() + EMSESP::analogsensor_.reads();
output["sensorfails"] = EMSESP::dallassensor_.fails() + EMSESP::analogsensor_.fails();
}
#ifndef EMSESP_STANDALONE
output["freemem"] = ESP.getFreeHeap() / 1000L; // kilobytes
#endif
if (analog_enabled_) {
output["adc"] = analog_;
}
#ifndef EMSESP_STANDALONE
if (!ethernet_connected_) {
int8_t rssi = WiFi.RSSI();
@@ -550,43 +579,14 @@ void System::send_heartbeat() {
return;
}
StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> doc;
JsonObject json = doc.to<JsonObject>();
StaticJsonDocument<EMSESP_JSON_SIZE_MEDIUM> doc;
JsonObject json = doc.to<JsonObject>();
if (heartbeat_json(json)) {
Mqtt::publish(F_(heartbeat), doc.as<JsonObject>()); // send to MQTT with retain off. This will add to MQTT queue.
Mqtt::publish(F_(heartbeat), json); // send to MQTT with retain off. This will add to MQTT queue.
}
}
// measure and moving average adc
void System::measure_analog() {
static uint32_t measure_last_ = 0;
if (!measure_last_ || (uint32_t)(uuid::get_uptime() - measure_last_) >= SYSTEM_MEASURE_ANALOG_INTERVAL) {
measure_last_ = uuid::get_uptime();
#if defined(EMSESP_STANDALONE)
uint16_t a = 0;
#else
uint16_t a = analogReadMilliVolts(ADC1_CHANNEL_0_GPIO_NUM);
#endif
static uint32_t sum_ = 0;
if (!analog_) { // init first time
analog_ = a;
sum_ = a * 512;
} else { // simple moving average filter
sum_ = (sum_ * 511) / 512 + a;
analog_ = sum_ / 512;
}
}
}
// sets rate of led flash
void System::set_led_speed(uint32_t speed) {
led_flash_speed_ = speed;
led_monitor();
}
// initializes network
void System::network_init(bool refresh) {
if (refresh) {
@@ -600,72 +600,57 @@ void System::network_init(bool refresh) {
return;
}
uint8_t phy_addr; // I²C-address of Ethernet PHY (0 or 1 for LAN8720, 31 for TLK110)
int power; // Pin# of the enable signal for the external crystal oscillator (-1 to disable for internal APLL source)
int mdc; // Pin# of the I²C clock signal for the Ethernet PHY
int mdio; // Pin# of the I²C IO signal for the Ethernet PHY
eth_phy_type_t type; // Type of the Ethernet PHY (LAN8720 or TLK110)
eth_clock_mode_t clock_mode; // ETH_CLOCK_GPIO0_IN or ETH_CLOCK_GPIO0_OUT, ETH_CLOCK_GPIO16_OUT, ETH_CLOCK_GPIO17_OUT for 50Hz inverted clock
if (phy_type_ == PHY_type::PHY_TYPE_LAN8720) {
phy_addr = 1;
power = 16;
mdc = 23;
mdio = 18;
type = ETH_PHY_LAN8720;
clock_mode = ETH_CLOCK_GPIO0_IN;
} else if (phy_type_ == PHY_type::PHY_TYPE_TLK110) {
phy_addr = 31;
power = -1;
mdc = 23;
mdio = 18;
type = ETH_PHY_TLK110;
clock_mode = ETH_CLOCK_GPIO0_IN;
} else {
return; // no valid profile
}
// special case for Olimex ESP32-EVB (LAN8720) (different power and phy_addr)
if (board_profile_.equals("OLIMEX")) {
phy_addr = 0;
power = -1;
mdc = 23;
mdio = 18;
type = ETH_PHY_LAN8720;
clock_mode = ETH_CLOCK_GPIO0_IN;
}
// configure Ethernet
int mdc = 23; // Pin# of the I²C clock signal for the Ethernet PHY - hardcoded
int mdio = 18; // Pin# of the I²C IO signal for the Ethernet PHY - hardcoded
uint8_t phy_addr = eth_phy_addr_; // I²C-address of Ethernet PHY (0 or 1 for LAN8720, 31 for TLK110)
int8_t power = eth_power_; // Pin# of the enable signal for the external crystal oscillator (-1 to disable for internal APLL source)
eth_phy_type_t type = (phy_type_ == PHY_type::PHY_TYPE_LAN8720) ? ETH_PHY_LAN8720 : ETH_PHY_TLK110; // Type of the Ethernet PHY (LAN8720 or TLK110)
// clock mode
// ETH_CLOCK_GPIO0_IN = 0 RMII clock input to GPIO0
// ETH_CLOCK_GPIO0_OUT = 1 RMII clock output from GPIO0
// ETH_CLOCK_GPIO16_OUT = 2 RMII clock output from GPIO16
// ETH_CLOCK_GPIO17_OUT = 3 RMII clock output from GPIO17, for 50hz inverted clock
eth_clock_mode_t clock_mode = (eth_clock_mode_t)eth_clock_mode_;
ETH.begin(phy_addr, power, mdc, mdio, type, clock_mode);
}
// check health of system, done every few seconds
// check health of system, done every 5 seconds
void System::system_check() {
if (!last_system_check_ || ((uint32_t)(uuid::get_uptime() - last_system_check_) >= SYSTEM_CHECK_FREQUENCY)) {
last_system_check_ = uuid::get_uptime();
#ifndef EMSESP_STANDALONE
// check if we have a valid network connection
if (!ethernet_connected() && (WiFi.status() != WL_CONNECTED)) {
set_led_speed(LED_WARNING_BLINK_FAST);
system_healthy_ = false;
return;
}
#endif
// not healthy if bus not connected
if (!EMSbus::bus_connected()) {
if (system_healthy_) {
LOG_ERROR(F("Error: No connection to the EMS bus"));
}
system_healthy_ = false;
set_led_speed(LED_WARNING_BLINK); // flash every 1/2 second from now on
healthcheck_ |= HEALTHCHECK_NO_NETWORK;
} else {
// if it was unhealthy but now we're better, make sure the LED is solid again cos we've been healed
if (!system_healthy_) {
system_healthy_ = true;
send_heartbeat();
healthcheck_ &= ~HEALTHCHECK_NO_NETWORK;
}
// check if we have a bus connection
if (!EMSbus::bus_connected()) {
healthcheck_ |= HEALTHCHECK_NO_BUS;
} else {
healthcheck_ &= ~HEALTHCHECK_NO_BUS;
}
// see if the healthcheck state has changed
static uint8_t last_healthcheck_ = 0;
if (healthcheck_ != last_healthcheck_) {
last_healthcheck_ = healthcheck_;
// see if we're better now
if (healthcheck_ == 0) {
// everything is healthy, show LED permanently on or off depending on setting
if (led_gpio_) {
digitalWrite(led_gpio_, hide_led_ ? !LED_ON : LED_ON);
}
send_heartbeat();
} else {
// turn off LED so we're ready to the flashes
if (led_gpio_) {
digitalWrite(led_gpio_, !LED_ON);
}
}
}
}
@@ -676,45 +661,94 @@ void System::commands_init() {
Command::add(EMSdevice::DeviceType::SYSTEM,
F_(pin),
System::command_pin,
F("sets a GPIO on/off"),
F("set a GPIO on/off"),
CommandFlag::MQTT_SUB_FLAG_NOSUB | CommandFlag::ADMIN_ONLY); // dont create a MQTT topic for this
Command::add(EMSdevice::DeviceType::SYSTEM, F_(send), System::command_send, F("sends a telegram"), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), System::command_fetch, F("refreshes all EMS values"), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(restart), System::command_restart, F("restarts EMS-ESP"), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(send), System::command_send, F("send a telegram"), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), System::command_fetch, F("refresh all EMS values"), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(restart), System::command_restart, F("restart EMS-ESP"), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(watch), System::command_watch, F("watch incoming telegrams"));
// Command::add(EMSdevice::DeviceType::SYSTEM, F_(syslog), System::command_syslog_level, F("set syslog level"), CommandFlag::ADMIN_ONLY);
if (Mqtt::enabled()) {
Command::add(EMSdevice::DeviceType::SYSTEM, F_(publish), System::command_publish, F("forces a MQTT publish"));
Command::add(EMSdevice::DeviceType::SYSTEM, F_(publish), System::command_publish, F("force a MQTT publish"));
}
// these commands will return data in JSON format
Command::add(EMSdevice::DeviceType::SYSTEM, F_(info), System::command_info, F("show system status"));
Command::add(EMSdevice::DeviceType::SYSTEM, F_(settings), System::command_settings, F("shows system settings"));
Command::add(EMSdevice::DeviceType::SYSTEM, F_(commands), System::command_commands, F("shows system commands"));
Command::add(EMSdevice::DeviceType::SYSTEM, F_(settings), System::command_settings, F("fetch system settings"));
Command::add(EMSdevice::DeviceType::SYSTEM, F_(customizations), System::command_customizations, F("fetch system customizations"));
Command::add(EMSdevice::DeviceType::SYSTEM, F_(commands), System::command_commands, F("fetch system commands"));
#if defined(EMSESP_DEBUG)
Command::add(EMSdevice::DeviceType::SYSTEM, F("test"), System::command_test, F("runs a specific test"));
Command::add(EMSdevice::DeviceType::SYSTEM, F("test"), System::command_test, F("run a specific test"));
#endif
// MQTT subscribe "ems-esp/system/#"
Mqtt::subscribe(EMSdevice::DeviceType::SYSTEM, "system/#", nullptr); // use empty function callback
}
// flashes the LED
// uses LED to show system health
void System::led_monitor() {
if (!led_gpio_) {
return;
// we only need to run the LED healthcheck if there are errors
if (!healthcheck_) {
return; // all good
}
static uint32_t led_last_blink_ = 0;
static uint32_t led_long_timer_ = 1; // 1 will kick it off immediately
static uint32_t led_short_timer_ = 0;
static uint8_t led_flash_step_ = 0; // 0 means we're not in the short flash timer
auto current_time = uuid::get_uptime();
if (!led_last_blink_ || (uint32_t)(uuid::get_uptime() - led_last_blink_) >= led_flash_speed_) {
led_last_blink_ = uuid::get_uptime();
// first long pause before we start flashing
if (led_long_timer_ && (uint32_t)(current_time - led_long_timer_) >= HEALTHCHECK_LED_LONG_DUARATION) {
// Serial.println("starting the flash check");
led_short_timer_ = current_time; // start the short timer
led_long_timer_ = 0; // stop long timer
led_flash_step_ = 1; // enable the short flash timer
}
// if bus_not_connected or network not connected, start flashing
if (!system_healthy_) {
digitalWrite(led_gpio_, !digitalRead(led_gpio_));
// the flash timer which starts after the long pause
if (led_flash_step_ && (uint32_t)(current_time - led_short_timer_) >= HEALTHCHECK_LED_FLASH_DUARATION) {
led_long_timer_ = 0; // stop the long timer
led_short_timer_ = current_time;
static bool led_on_ = false;
if (++led_flash_step_ == 8) {
// reset the whole sequence
// Serial.println("resetting flash check");
led_long_timer_ = uuid::get_uptime();
led_flash_step_ = 0;
digitalWrite(led_gpio_, !LED_ON); // LED off
} else if (led_flash_step_ % 2) {
// handle the step events (on odd numbers 3,5,7,etc). see if we need to turn on a LED
// 1 flash is the EMS bus is not connected
// 2 flashes if the network (wifi or ethernet) is not connected
// 3 flashes is both the bus and the network are not connected. Then you know you're truly f*cked.
if ((led_flash_step_ == 3)
&& (((healthcheck_ & HEALTHCHECK_NO_NETWORK) == HEALTHCHECK_NO_NETWORK) || ((healthcheck_ & HEALTHCHECK_NO_BUS) == HEALTHCHECK_NO_BUS))) {
led_on_ = true;
}
if ((led_flash_step_ == 5) && ((healthcheck_ & HEALTHCHECK_NO_NETWORK) == HEALTHCHECK_NO_NETWORK)) {
led_on_ = true;
}
if ((led_flash_step_ == 7) && ((healthcheck_ & HEALTHCHECK_NO_NETWORK) == HEALTHCHECK_NO_NETWORK)
&& ((healthcheck_ & HEALTHCHECK_NO_BUS) == HEALTHCHECK_NO_BUS)) {
led_on_ = true;
}
if (led_on_ && led_gpio_) {
digitalWrite(led_gpio_, LED_ON);
}
} else {
// turn the led off after the flash, on even number count
if (led_on_ && led_gpio_) {
digitalWrite(led_gpio_, !LED_ON);
led_on_ = false;
}
}
}
}
@@ -761,19 +795,19 @@ void System::show_system(uuid::console::Shell & shell) {
switch (WiFi.status()) {
case WL_IDLE_STATUS:
shell.printfln(F("WiFi: Idle"));
shell.printfln(F("Network: Idle"));
break;
case WL_NO_SSID_AVAIL:
shell.printfln(F("WiFi: Network not found"));
shell.printfln(F("Network: Network not found"));
break;
case WL_SCAN_COMPLETED:
shell.printfln(F("WiFi: Network scan complete"));
shell.printfln(F("Network: Network scan complete"));
break;
case WL_CONNECTED:
shell.printfln(F("WiFi: Connected"));
shell.printfln(F("Network: connected"));
shell.printfln(F("SSID: %s"), WiFi.SSID().c_str());
shell.printfln(F("BSSID: %s"), WiFi.BSSIDstr().c_str());
shell.printfln(F("RSSI: %d dBm (%d %%)"), WiFi.RSSI(), wifi_quality(WiFi.RSSI()));
@@ -788,28 +822,28 @@ void System::show_system(uuid::console::Shell & shell) {
break;
case WL_CONNECT_FAILED:
shell.printfln(F("WiFi: Connection failed"));
shell.printfln(F("WiFi Network: Connection failed"));
break;
case WL_CONNECTION_LOST:
shell.printfln(F("WiFi: Connection lost"));
shell.printfln(F("WiFi Network: Connection lost"));
break;
case WL_DISCONNECTED:
shell.printfln(F("WiFi: Disconnected"));
shell.printfln(F("WiFi Network: Disconnected"));
break;
case WL_NO_SHIELD:
default:
shell.printfln(F("WiFi: Unknown"));
shell.printfln(F("WiFi Network: Unknown"));
break;
}
shell.println();
// show Ethernet
// show Ethernet if connected
if (ethernet_connected_) {
shell.printfln(F("Ethernet: Connected"));
shell.printfln(F("Wired Network: connected"));
shell.printfln(F("MAC address: %s"), ETH.macAddress().c_str());
shell.printfln(F("Hostname: %s"), ETH.getHostname());
shell.printfln(F("IPv4 address: %s/%s"), uuid::printable_to_string(ETH.localIP()).c_str(), uuid::printable_to_string(ETH.subnetMask()).c_str());
@@ -818,8 +852,6 @@ void System::show_system(uuid::console::Shell & shell) {
if (ETH.localIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000") {
shell.printfln(F("IPv6 address: %s"), uuid::printable_to_string(ETH.localIPv6()).c_str());
}
} else {
shell.printfln(F("Ethernet: disconnected"));
}
shell.println();
@@ -856,18 +888,20 @@ bool System::command_commands(const char * value, const int8_t id, JsonObject &
// export all settings to JSON text
// http://ems-esp/api/system/settings
// value and id are ignored
// note: ssid and passwords are excluded
bool System::command_settings(const char * value, const int8_t id, JsonObject & output) {
JsonObject node;
output["label"] = "settings";
node = output.createNestedObject("System");
JsonObject node = output.createNestedObject("System");
node["version"] = EMSESP_APP_VERSION;
// hide ssid from this list
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & settings) {
node = output.createNestedObject("Network");
node["hostname"] = settings.hostname;
node["static_ip_config"] = settings.staticIPConfig;
node["enableIPv6"] = settings.enableIPv6;
node["low_bandwidth"] = settings.bandwidth20;
node["disable_sleep"] = settings.nosleep;
JsonUtils::writeIP(node, "local_ip", settings.localIP);
JsonUtils::writeIP(node, "gateway_ip", settings.gatewayIP);
JsonUtils::writeIP(node, "subnet_mask", settings.subnetMask);
@@ -878,35 +912,41 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject &
#ifndef EMSESP_STANDALONE
EMSESP::esp8266React.getAPSettingsService()->read([&](APSettings & settings) {
node = output.createNestedObject("AP");
node["provision_mode"] = settings.provisionMode;
const char * pM[] = {"always", "disconnected", "never"};
node["provision_mode"] = pM[settings.provisionMode];
node["security"] = settings.password.length() ? "wpa2" : "open";
node["ssid"] = settings.ssid;
node["local_ip"] = settings.localIP.toString();
node["gateway_ip"] = settings.gatewayIP.toString();
node["subnet_mask"] = settings.subnetMask.toString();
node["channel"] = settings.channel;
node["ssid_hidden"] = settings.ssidHidden;
node["max_clients"] = settings.maxClients;
});
#endif
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
node = output.createNestedObject("MQTT");
node["enabled"] = settings.enabled;
#ifndef EMSESP_STANDALONE
node["host"] = settings.host;
node["port"] = settings.port;
node["username"] = settings.username;
node["client_id"] = settings.clientId;
node["keep_alive"] = settings.keepAlive;
node["clean_session"] = settings.cleanSession;
#endif
node = output.createNestedObject("MQTT");
node["enabled"] = settings.enabled;
node["host"] = settings.host;
node["port"] = settings.port;
node["username"] = settings.username;
node["client_id"] = settings.clientId;
node["keep_alive"] = settings.keepAlive;
node["clean_session"] = settings.cleanSession;
node["base"] = settings.base;
node["discovery_prefix"] = settings.discovery_prefix;
node["nested_format"] = settings.nested_format;
node["ha_enabled"] = settings.ha_enabled;
node["mqtt_qos"] = settings.mqtt_qos;
node["mqtt_retain"] = settings.mqtt_retain;
node["publish_time_boiler"] = settings.publish_time_boiler;
node["publish_time_thermostat"] = settings.publish_time_thermostat;
node["publish_time_solar"] = settings.publish_time_solar;
node["publish_time_mixer"] = settings.publish_time_mixer;
node["publish_time_other"] = settings.publish_time_other;
node["publish_time_sensor"] = settings.publish_time_sensor;
node["ha_climate_format"] = settings.ha_climate_format;
node["ha_enabled"] = settings.ha_enabled;
node["mqtt_qos"] = settings.mqtt_qos;
node["mqtt_retain"] = settings.mqtt_retain;
node["publish_single"] = settings.publish_single;
node["send_response"] = settings.send_response;
});
@@ -949,15 +989,73 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject &
node["dallas_gpio"] = settings.dallas_gpio;
node["pbutton_gpio"] = settings.pbutton_gpio;
node["led_gpio"] = settings.led_gpio;
node["phy_type"] = settings.phy_type;
node["hide_led"] = settings.hide_led;
node["notoken_api"] = settings.notoken_api;
node["hide_led"] = settings.hide_led;
node["notoken_api"] = settings.notoken_api;
node["readonly_mode"] = settings.readonly_mode;
node["fahrenheit"] = settings.fahrenheit;
node["dallas_parasite"] = settings.dallas_parasite;
node["dallas_format"] = settings.dallas_format;
node["bool_format"] = settings.bool_format;
node["enum_format"] = settings.enum_format;
node["analog_enabled"] = settings.analog_enabled;
node["telnet_enabled"] = settings.telnet_enabled;
node["phy_type"] = settings.phy_type;
node["eth_power"] = settings.eth_power;
node["eth_phy_addr"] = settings.eth_phy_addr;
node["eth_clock_mode"] = settings.eth_clock_mode;
});
return true;
}
// http://ems-esp/api/system/customizations
bool System::command_customizations(const char * value, const int8_t id, JsonObject & output) {
output["label"] = "customizations";
JsonObject node = output.createNestedObject("Customizations");
// hide ssid from this list
EMSESP::webCustomizationService.read([&](WebCustomization & settings) {
// sensors
JsonArray sensorsJson = node.createNestedArray("sensors");
for (const auto & sensor : settings.sensorCustomizations) {
JsonObject sensorJson = sensorsJson.createNestedObject();
sensorJson["id_str"] = sensor.id_str; // key, is
sensorJson["name"] = sensor.name; // n
sensorJson["offset"] = sensor.offset; // o
}
JsonArray analogJson = node.createNestedArray("analogs");
for (const AnalogCustomization & sensor : settings.analogCustomizations) {
JsonObject sensorJson = analogJson.createNestedObject();
sensorJson["gpio"] = sensor.id;
sensorJson["name"] = sensor.name;
if (EMSESP::system_.enum_format() == ENUM_FORMAT_INDEX) {
sensorJson["type"] = sensor.type;
} else {
sensorJson["type"] = FL_(enum_sensortype)[sensor.type];
}
if (sensor.type == AnalogSensor::AnalogType::ADC) {
sensorJson["offset"] = sensor.offset;
sensorJson["factor"] = sensor.factor;
sensorJson["uom"] = EMSdevice::uom_to_string(sensor.uom);
}
}
// exclude entities
JsonArray exclude_entitiesJson = node.createNestedArray("exclude_entities");
for (const auto & entityCustomization : settings.entityCustomizations) {
JsonObject entityJson = exclude_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);
}
}
});
return true;
@@ -981,7 +1079,11 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
node["reset reason"] = EMSESP::system_.reset_reason(0) + " / " + EMSESP::system_.reset_reason(1);
if (EMSESP::dallas_enabled()) {
node["Dallas sensors"] = EMSESP::sensor_devices().size();
node["Temperature sensors"] = EMSESP::dallassensor_.no_sensors();
}
if (EMSESP::analog_enabled()) {
node["Analog sensors"] = EMSESP::analogsensor_.no_sensors();
}
#ifndef EMSESP_STANDALONE
@@ -1001,7 +1103,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
node["IPv6 address"] = uuid::printable_to_string(WiFi.localIPv6());
}
} else if (EMSESP::system_.ethernet_connected()) {
node["connection"] = F("Ethernet");
node["connection"] = F("Wired");
node["hostname"] = ETH.getHostname();
node["MAC"] = ETH.macAddress();
node["IPv4 address"] = uuid::printable_to_string(ETH.localIP()) + "/" + uuid::printable_to_string(ETH.subnetMask());
@@ -1021,7 +1123,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
node["bus status"] = (F("disconnected"));
break;
case EMSESP::BUS_STATUS_TX_ERRORS:
node["bus status"] = (F("connected, instable tx"));
node["bus status"] = (F("connected, tx issues - try a different tx-mode"));
break;
case EMSESP::BUS_STATUS_CONNECTED:
default:
@@ -1030,27 +1132,39 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
}
if (EMSESP::bus_status() != EMSESP::BUS_STATUS_OFFLINE) {
node["bus protocol"] = EMSbus::is_ht3() ? F("HT3") : F("Buderus");
node["telegrams received"] = EMSESP::rxservice_.telegram_count();
node["read requests sent"] = EMSESP::txservice_.telegram_read_count();
node["write requests sent"] = EMSESP::txservice_.telegram_write_count();
node["incomplete telegrams"] = EMSESP::rxservice_.telegram_error_count();
node["tx fails"] = EMSESP::txservice_.telegram_fail_count();
node["rx line quality"] = EMSESP::rxservice_.quality();
node["tx line quality"] = EMSESP::txservice_.quality();
node["bus protocol"] = EMSbus::is_ht3() ? F("HT3") : F("Buderus");
node["bus telegrams received (rx)"] = EMSESP::rxservice_.telegram_count();
node["bus reads (tx)"] = EMSESP::txservice_.telegram_read_count();
node["bus writes (tx)"] = EMSESP::txservice_.telegram_write_count();
node["bus incomplete telegrams"] = EMSESP::rxservice_.telegram_error_count();
node["bus reads failed"] = EMSESP::txservice_.telegram_read_fail_count();
node["bus writes failed"] = EMSESP::txservice_.telegram_write_fail_count();
node["bus rx line quality"] = EMSESP::rxservice_.quality();
node["bus tx line quality"] = (EMSESP::txservice_.read_quality() + EMSESP::txservice_.read_quality()) / 2;
if (Mqtt::enabled()) {
node["MQTT"] = Mqtt::connected() ? F_(connected) : F_(disconnected);
node["MQTT status"] = Mqtt::connected() ? F_(connected) : F_(disconnected);
node["MQTT publishes"] = Mqtt::publish_count();
node["MQTT publish fails"] = Mqtt::publish_fails();
}
node["Temperature sensors"] = EMSESP::dallassensor_.no_sensors();
if (EMSESP::dallas_enabled()) {
node["Dallas reads"] = EMSESP::sensor_reads();
node["Dallas fails"] = EMSESP::sensor_fails();
node["Temperature sensor reads"] = EMSESP::dallassensor_.reads();
node["Temperature sensor fails"] = EMSESP::dallassensor_.fails();
}
node["Analog sensors"] = EMSESP::analogsensor_.no_sensors();
if (EMSESP::analog_enabled()) {
node["Analog sensor reads"] = EMSESP::analogsensor_.reads();
node["Analog sensor fails"] = EMSESP::analogsensor_.fails();
}
node["API calls"] = WebAPIService::api_count();
node["API fails"] = WebAPIService::api_fails();
#ifndef EMSESP_STANDALONE
if (EMSESP::system_.syslog_enabled_) {
node["syslog IP"] = syslog_.ip();
node["syslog active"] = syslog_.started();
node["syslog_started"] = syslog_.started();
node["syslog_level"] = FL_(enum_syslog_level)[syslog_.log_level() + 1];
node["syslog_ip"] = syslog_.ip();
node["syslog_queue"] = syslog_.queued();
}
#endif
}
@@ -1064,9 +1178,17 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
obj["type"] = emsdevice->device_type_name();
obj["name"] = emsdevice->to_string();
char result[200];
(void)emsdevice->show_telegram_handlers(result);
(void)emsdevice->show_telegram_handlers(result, EMSdevice::Handlers::RECEIVED);
if (result[0] != '\0') {
obj["handlers"] = result; // don't show hanlders if there aren't any
obj["handlers_received"] = result; // don't show handlers if there aren't any
}
(void)emsdevice->show_telegram_handlers(result, EMSdevice::Handlers::FETCHED);
if (result[0] != '\0') {
obj["handlers_fetched"] = result;
}
(void)emsdevice->show_telegram_handlers(result, EMSdevice::Handlers::PENDING);
if (result[0] != '\0') {
obj["handlers_pending"] = result;
}
}
}
@@ -1084,29 +1206,55 @@ bool System::command_test(const char * value, const int8_t id) {
#endif
// takes a board profile and populates a data array with GPIO configurations
// data = led, dallas, rx, tx, button, phy_type
// returns false if profile is not found
bool System::load_board_profile(std::vector<uint8_t> & data, const std::string & board_profile) {
//
// data = led, dallas, rx, tx, button, phy_type, eth_power, eth_phy_addr, eth_clock_mode
//
// clock modes:
// ETH_CLOCK_GPIO0_IN = 0 RMII clock input to GPIO0
// ETH_CLOCK_GPIO0_OUT = 1 RMII clock output from GPIO0
// ETH_CLOCK_GPIO16_OUT = 2 RMII clock output from GPIO16
// ETH_CLOCK_GPIO17_OUT = 3 RMII clock output from GPIO17, for 50hz inverted cloc
bool System::load_board_profile(std::vector<int8_t> & data, const std::string & board_profile) {
if (board_profile == "S32") {
data = {2, 18, 23, 5, 0, PHY_type::PHY_TYPE_NONE}; // BBQKees Gateway S32
data = {2, 18, 23, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // BBQKees Gateway S32
} else if (board_profile == "E32") {
data = {2, 4, 5, 17, 33, PHY_type::PHY_TYPE_LAN8720}; // BBQKees Gateway E32
data = {2, 4, 5, 17, 33, PHY_type::PHY_TYPE_LAN8720, 16, 1, 0}; // BBQKees Gateway E32
} else if (board_profile == "MH-ET") {
data = {2, 18, 23, 5, 0, PHY_type::PHY_TYPE_NONE}; // MH-ET Live D1 Mini
data = {2, 18, 23, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // MH-ET Live D1 Mini
} else if (board_profile == "NODEMCU") {
data = {2, 18, 23, 5, 0, PHY_type::PHY_TYPE_NONE}; // NodeMCU 32S
data = {2, 18, 23, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // NodeMCU 32S
} else if (board_profile == "LOLIN") {
data = {2, 18, 17, 16, 0, PHY_type::PHY_TYPE_NONE}; // Lolin D32
data = {2, 18, 17, 16, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // Lolin D32
} else if (board_profile == "OLIMEX") {
data = {0, 0, 36, 4, 34, PHY_type::PHY_TYPE_LAN8720}; // Olimex ESP32-EVB (uses U1TXD/U1RXD/BUTTON, no LED or Dallas)
data = {0, 0, 36, 4, 34, PHY_type::PHY_TYPE_LAN8720, -1, 0, 0}; // Olimex ESP32-EVB (uses U1TXD/U1RXD/BUTTON, no LED or Dallas)
} else if (board_profile == "OLIMEXPOE") {
data = {0, 0, 36, 4, 34, PHY_type::PHY_TYPE_LAN8720, 12, 0, 3}; // Olimex ESP32-POE
} else if (board_profile == "CUSTOM") {
// send back current values
data = {(int8_t)EMSESP::system_.led_gpio_,
(int8_t)EMSESP::system_.dallas_gpio_,
(int8_t)EMSESP::system_.rx_gpio_,
(int8_t)EMSESP::system_.tx_gpio_,
(int8_t)EMSESP::system_.pbutton_gpio_,
(int8_t)EMSESP::system_.phy_type_,
EMSESP::system_.eth_power_,
(int8_t)EMSESP::system_.eth_phy_addr_,
(int8_t)EMSESP::system_.eth_clock_mode_};
} else {
data = {EMSESP_DEFAULT_LED_GPIO,
EMSESP_DEFAULT_DALLAS_GPIO,
EMSESP_DEFAULT_RX_GPIO,
EMSESP_DEFAULT_TX_GPIO,
EMSESP_DEFAULT_PBUTTON_GPIO,
EMSESP_DEFAULT_PHY_TYPE};
return (board_profile == "CUSTOM");
// unknown, use defaults
data = {
EMSESP_DEFAULT_LED_GPIO,
EMSESP_DEFAULT_DALLAS_GPIO,
EMSESP_DEFAULT_RX_GPIO,
EMSESP_DEFAULT_TX_GPIO,
EMSESP_DEFAULT_PBUTTON_GPIO,
EMSESP_DEFAULT_PHY_TYPE,
-1, // power
0, // phy_addr,
0 // clock_mode
};
return false;
}
return true;
@@ -1147,7 +1295,7 @@ const std::string System::reset_reason(uint8_t cpu) {
case 13:
return ("RTC watch dog reset: CPU");
case 14:
return ("APP CPU reseted by PRO CPU");
return ("APP CPU reset by PRO CPU");
case 15:
return ("Brownout reset");
case 16:

View File

@@ -28,7 +28,6 @@
#include "telegram.h"
#ifndef EMSESP_STANDALONE
#include "driver/adc.h"
#include <esp_wifi.h>
#include <esp_bt.h>
#include <ETH.h>
@@ -63,6 +62,7 @@ class System {
static bool command_info(const char * value, const int8_t id, JsonObject & output);
static bool command_settings(const char * value, const int8_t id, JsonObject & output);
static bool command_customizations(const char * value, const int8_t id, JsonObject & output);
static bool command_commands(const char * value, const int8_t id, JsonObject & output);
const std::string reset_reason(uint8_t cpu);
@@ -80,13 +80,12 @@ class System {
void send_heartbeat();
void led_init(bool refresh);
void adc_init(bool refresh);
void network_init(bool refresh);
void button_init(bool refresh);
void commands_init();
static bool is_valid_gpio(uint8_t pin);
static bool load_board_profile(std::vector<uint8_t> & data, const std::string & board_profile);
static bool load_board_profile(std::vector<int8_t> & data, const std::string & board_profile);
static void restart_requested(bool restart_requested) {
restart_requested_ = restart_requested;
@@ -96,12 +95,47 @@ class System {
return restart_requested_;
}
bool telnet_enabled() {
return telnet_enabled_;
}
bool analog_enabled() {
return analog_enabled_;
}
uint16_t analog() {
return analog_;
bool readonly_mode() {
return readonly_mode_;
}
void readonly_mode(bool readonly_mode) {
readonly_mode_ = readonly_mode;
}
uint8_t bool_format() {
return bool_format_;
}
// see default_settings.h
// BOOL_FORMAT_ONOFF_STR = 1,
// BOOL_FORMAT_ONOFF_STR_CAP = 2
// BOOL_FORMAT_TRUEFALSE_STR = 3
// BOOL_FORMAT_TRUEFALSE = 4
// BOOL_FORMAT_10_STR = 5
// BOOL_FORMAT_10 = 6
void bool_format(uint8_t format) {
bool_format_ = format;
}
uint8_t enum_format() {
return enum_format_;
}
void enum_format(uint8_t format) {
enum_format_ = format;
}
std::string board_profile() {
return std::string(board_profile_.c_str());
}
std::string hostname() {
@@ -128,6 +162,18 @@ class System {
#endif
}
void fahrenheit(bool b) {
fahrenheit_ = b;
}
bool fahrenheit() {
return fahrenheit_;
}
void healthcheck(uint8_t healthcheck) {
healthcheck_ = healthcheck;
}
void show_system(uuid::console::Shell & shell);
void wifi_reconnect();
void show_users(uuid::console::Shell & shell);
@@ -148,46 +194,59 @@ class System {
static constexpr uint32_t BUTTON_LongPressDelay = 750; // Hold period for a long press event (in ms)
static constexpr uint32_t BUTTON_VLongPressDelay = 9000; // Hold period for a very long press event (in ms)
static constexpr uint32_t SYSTEM_CHECK_FREQUENCY = 5000; // check every 5 seconds
static constexpr uint32_t LED_WARNING_BLINK = 1000; // pulse to show no connection, 1 sec
static constexpr uint32_t LED_WARNING_BLINK_FAST = 100; // flash quickly for boot up sequence
static constexpr uint32_t SYSTEM_HEARTBEAT_INTERVAL = 60000; // in milliseconds, how often the MQTT heartbeat is sent (1 min)
static constexpr uint32_t SYSTEM_MEASURE_ANALOG_INTERVAL = 500;
static constexpr uint8_t LED_ON = HIGH; // LED
// healthcheck
static constexpr uint32_t SYSTEM_CHECK_FREQUENCY = 5000; // do a system check every 5 seconds
static constexpr uint32_t HEALTHCHECK_LED_LONG_DUARATION = 1500;
static constexpr uint32_t HEALTHCHECK_LED_FLASH_DUARATION = 150;
static constexpr uint8_t HEALTHCHECK_NO_BUS = (1 << 0); // 1
static constexpr uint8_t HEALTHCHECK_NO_NETWORK = (1 << 1); // 2
static constexpr uint32_t SYSTEM_HEARTBEAT_INTERVAL = 60000; // in milliseconds, how often the MQTT heartbeat is sent (1 min)
static constexpr uint8_t LED_ON = HIGH; // LED on
#ifndef EMSESP_STANDALONE
static uuid::syslog::SyslogService syslog_;
#endif
void led_monitor();
void set_led_speed(uint32_t speed);
void system_check();
void measure_analog();
int8_t wifi_quality(int8_t dBm);
bool system_healthy_ = false;
uint32_t led_flash_speed_ = LED_WARNING_BLINK_FAST; // default boot flashes quickly
uint32_t last_heartbeat_ = 0;
uint32_t last_system_check_ = 0;
bool upload_status_ = false; // true if we're in the middle of a OTA firmware upload
bool ethernet_connected_ = false;
uint16_t analog_;
uint8_t healthcheck_ = HEALTHCHECK_NO_NETWORK | HEALTHCHECK_NO_BUS; // start with all flags set, no wifi and no ems bus connection
uint32_t last_heartbeat_ = 0;
uint32_t last_system_check_ = 0;
// settings
std::string hostname_ = "ems-esp";
bool upload_status_ = false; // true if we're in the middle of a OTA firmware upload
bool ethernet_connected_ = false;
// EMS-ESP settings
// copies from WebSettings class in WebSettingsService.h
std::string hostname_ = FACTORY_WIFI_HOSTNAME;
bool hide_led_;
uint8_t led_gpio_;
bool syslog_enabled_ = false;
bool analog_enabled_;
bool low_clock_;
String board_profile_;
uint8_t pbutton_gpio_;
uint8_t phy_type_;
uint8_t rx_gpio_;
uint8_t tx_gpio_;
uint8_t dallas_gpio_;
bool telnet_enabled_;
bool syslog_enabled_;
int8_t syslog_level_;
uint32_t syslog_mark_interval_;
String syslog_host_;
uint16_t syslog_port_;
bool fahrenheit_;
uint8_t bool_format_;
uint8_t enum_format_;
bool readonly_mode_;
// ethernet
uint8_t phy_type_;
int8_t eth_power_;
uint8_t eth_phy_addr_;
uint8_t eth_clock_mode_;
};
} // namespace emsesp

View File

@@ -37,6 +37,7 @@ const uint8_t ems_crc_table[] = {0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E,
0xF9, 0xFB, 0xFD, 0xFF, 0xF1, 0xF3, 0xF5, 0xF7, 0xE9, 0xEB, 0xED, 0xEF, 0xE1, 0xE3, 0xE5, 0xE7};
uint32_t EMSbus::last_bus_activity_ = 0; // timestamp of last time a valid Rx came in
uint32_t EMSbus::bus_uptime_start_ = 0; // timestamp of when the bus was started
bool EMSbus::bus_connected_ = false; // start assuming the bus hasn't been connected
uint8_t EMSbus::ems_mask_ = EMS_MASK_UNSET; // unset so its triggered when booting, the its 0x00=buderus, 0x80=junker/ht3
uint8_t EMSbus::ems_bus_id_ = EMSESP_DEFAULT_EMS_BUS_ID;
@@ -262,7 +263,7 @@ void TxService::start() {
read_request(EMSdevice::EMS_TYPE_UBADevices, EMSdevice::EMS_DEVICE_ID_BOILER);
}
// sends a 1 byte poll which is our own device ID
// sends a 1 byte poll which is our own deviceID
void TxService::send_poll() {
//LOG_DEBUG(F("Ack %02X"),ems_bus_id() ^ ems_mask());
if (tx_mode()) {
@@ -359,13 +360,18 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
}
}
uint8_t length = message_p;
telegram_last_ = std::make_shared<Telegram>(*telegram); // make a copy of the telegram
uint8_t length = message_p;
telegram_raw[length] = calculate_crc(telegram_raw, length); // generate and append CRC to the end
length++; // add one since we want to now include the CRC
length++; // add one since we want to now include the CRC
// if we're in simulation mode, don't send anything, just quit
if (EMSESP::system_.readonly_mode() && (telegram->operation == Telegram::Operation::TX_WRITE)) {
LOG_INFO(F("[readonly] Sending write Tx telegram: %s"), Helpers::data_to_hex(telegram_raw, length - 1).c_str());
tx_state(Telegram::Operation::NONE);
return;
}
LOG_DEBUG(F("Sending %s Tx [#%d], telegram: %s"),
(telegram->operation == Telegram::Operation::TX_WRITE) ? F("write") : F("read"),
@@ -373,12 +379,19 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
Helpers::data_to_hex(telegram_raw, length - 1).c_str()); // exclude the last CRC byte
set_post_send_query(tx_telegram.validateid_);
// send the telegram to the UART Tx
//
// this is the core send command to the UART
//
uint16_t status = EMSuart::transmit(telegram_raw, length);
if (status == EMS_TX_STATUS_ERR) {
LOG_ERROR(F("Failed to transmit Tx via UART."));
increment_telegram_fail_count(); // another Tx fail
if (telegram->operation == Telegram::Operation::TX_READ) {
increment_telegram_read_fail_count(); // another Tx fail
} else {
increment_telegram_write_fail_count(); // another Tx fail
}
tx_state(Telegram::Operation::NONE); // nothing send, tx not in wait state
return;
}
@@ -577,9 +590,13 @@ void TxService::send_raw(const char * telegram_data) {
void TxService::retry_tx(const uint8_t operation, const uint8_t * data, const uint8_t length) {
// have we reached the limit? if so, reset count and give up
if (++retry_count_ > MAXIMUM_TX_RETRIES) {
reset_retry_count(); // give up
increment_telegram_fail_count(); // another Tx fail
EMSESP::wait_validate(0); // do not wait for validation
reset_retry_count(); // give up
if (operation == Telegram::Operation::TX_READ) {
increment_telegram_read_fail_count(); // another Tx fail
} else {
increment_telegram_write_fail_count(); // another Tx fail
}
EMSESP::wait_validate(0); // do not wait for validation
LOG_ERROR(F("Last Tx %s operation failed after %d retries. Ignoring request: %s"),
(operation == Telegram::Operation::TX_WRITE) ? F("Write") : F("Read"),

View File

@@ -34,7 +34,7 @@
#include "helpers.h"
#define MAX_RX_TELEGRAMS 10 // size of Rx queue
#define MAX_TX_TELEGRAMS 30 // size of Tx queue
#define MAX_TX_TELEGRAMS 50 // size of Tx queue
// default values for null values
static constexpr uint8_t EMS_VALUE_BOOL = 0xFF; // used to mark that something is a boolean
@@ -166,6 +166,7 @@ class EMSbus {
ems_bus_id_ = ems_bus_id;
}
// checks every 30 seconds if the EMS bus is still alive
static bool bus_connected() {
#ifndef EMSESP_STANDALONE
if ((uuid::get_uptime() - last_bus_activity_) > EMS_BUS_TIMEOUT) {
@@ -179,8 +180,22 @@ class EMSbus {
// sets the flag for EMS bus connected
static void last_bus_activity(uint32_t timestamp) {
// record the first time we connected to the BUS, as this will be our uptime
if (!last_bus_activity_) {
bus_uptime_start_ = timestamp;
}
last_bus_activity_ = timestamp;
bus_connected_ = true;
bus_connected_ = true;
}
// return bus uptime in seconds
static uint32_t bus_uptime() {
if (!bus_uptime_start_) {
return 0; // not yet initialized
}
return (uint32_t)((uuid::get_uptime() - bus_uptime_start_) / 1000ULL);
}
static uint8_t tx_state() {
@@ -196,6 +211,7 @@ class EMSbus {
static constexpr uint32_t EMS_BUS_TIMEOUT = 30000; // timeout in ms before recognizing the ems bus is offline (30 seconds)
static uint32_t last_bus_activity_; // timestamp of last time a valid Rx came in
static uint32_t bus_uptime_start_; // timestamp of first time we connected to the bus
static bool bus_connected_; // start assuming the bus hasn't been connected
static uint8_t ems_mask_; // unset=0xFF, buderus=0x00, junkers/ht3=0x80
static uint8_t ems_bus_id_; // the bus id, which configurable and stored in settings
@@ -224,6 +240,7 @@ class RxService : public EMSbus {
return telegram_error_count_;
}
// returns a %
uint8_t quality() const {
if (telegram_error_count_ == 0) {
return 100; // all good, 100%
@@ -308,49 +325,61 @@ class TxService : public EMSbus {
uint32_t telegram_read_count() const {
return telegram_read_count_;
}
uint32_t telegram_write_count() const {
return telegram_write_count_;
}
void telegram_read_count(uint8_t telegram_read_count) {
void telegram_read_count(uint32_t telegram_read_count) {
telegram_read_count_ = telegram_read_count;
}
void telegram_write_count(uint32_t telegram_write_count) {
telegram_write_count_ = telegram_write_count;
}
void increment_telegram_read_count() {
telegram_read_count_++;
}
uint32_t telegram_fail_count() const {
return telegram_fail_count_;
}
void telegram_fail_count(uint8_t telegram_fail_count) {
telegram_fail_count_ = telegram_fail_count;
}
uint8_t quality() const {
if (telegram_fail_count_ == 0) {
return 100; // all good, 100%
}
if (telegram_fail_count_ >= telegram_read_count_) {
return 100;
}
return (100 - (uint8_t)(((float)telegram_fail_count_ / telegram_read_count_ * 100)));
}
void increment_telegram_fail_count() {
telegram_fail_count_++;
}
uint32_t telegram_write_count() const {
return telegram_write_count_;
}
void telegram_write_count(uint8_t telegram_write_count) {
telegram_write_count_ = telegram_write_count;
}
void increment_telegram_write_count() {
telegram_write_count_++;
}
uint32_t telegram_read_fail_count() const {
return telegram_read_fail_count_;
}
uint32_t telegram_write_fail_count() const {
return telegram_write_fail_count_;
}
void telegram_fail_count(uint32_t telegram_fail_count) {
telegram_read_fail_count_ = telegram_fail_count;
telegram_write_fail_count_ = telegram_fail_count;
}
uint8_t read_quality() const {
if (telegram_read_fail_count_ == 0) {
return 100; // all good, 100%
}
return (100 - (uint8_t)((telegram_read_fail_count_ * 100 / (telegram_read_fail_count_ + telegram_read_count_))));
}
uint8_t write_quality() const {
if (telegram_write_fail_count_ == 0) {
return 100; // all good, 100%
}
return (100 - (uint8_t)((telegram_write_fail_count_ * 100 / (telegram_write_fail_count_ + telegram_write_count_))));
}
void increment_telegram_read_fail_count() {
telegram_read_fail_count_++;
}
void increment_telegram_write_fail_count() {
telegram_write_fail_count_++;
}
struct QueuedTxTelegram {
const uint16_t id_;
const std::shared_ptr<const Telegram> telegram_;
@@ -381,9 +410,10 @@ class TxService : public EMSbus {
private:
std::deque<QueuedTxTelegram> tx_telegrams_; // the Tx queue
uint32_t telegram_read_count_ = 0; // # Tx successful reads
uint32_t telegram_write_count_ = 0; // # Tx successful writes
uint32_t telegram_fail_count_ = 0; // # Tx unsuccessful transmits
uint32_t telegram_read_count_ = 0; // # Tx successful reads
uint32_t telegram_write_count_ = 0; // # Tx successful writes
uint32_t telegram_read_fail_count_ = 0; // # Tx unsuccessful transmits
uint32_t telegram_write_fail_count_ = 0; // # Tx unsuccessful transmits
std::shared_ptr<Telegram> telegram_last_;
uint16_t telegram_last_post_send_query_; // which type ID to query after a successful send, to read back the values just written
@@ -392,9 +422,7 @@ class TxService : public EMSbus {
uint8_t tx_telegram_id_ = 0; // queue counter
void send_telegram(const QueuedTxTelegram & tx_telegram);
// void send_telegram(const uint8_t * data, const uint8_t length);
};
} // namespace emsesp

View File

@@ -53,6 +53,38 @@ bool Test::run_test(const char * command, int8_t id) {
return true;
}
if (strcmp(command, "2thermostats") == 0) {
EMSESP::logger().info(F("Testing with multiple thermostats..."));
add_device(0x08, 123); // GB072
add_device(0x10, 158); // RC310
add_device(0x18, 157); // Bosch CR100
// Boiler -> Me, UBAMonitorFast(0x18), telegram: 08 00 18 00 00 02 5A 73 3D 0A 10 65 40 02 1A 80 00 01 E1 01 76 0E 3D 48 00 C9 44 02 00 (#data=25)
uart_telegram({0x08, 0x00, 0x18, 0x00, 0x00, 0x02, 0x5A, 0x73, 0x3D, 0x0A, 0x10, 0x65, 0x40, 0x02, 0x1A,
0x80, 0x00, 0x01, 0xE1, 0x01, 0x76, 0x0E, 0x3D, 0x48, 0x00, 0xC9, 0x44, 0x02, 0x00});
// Boiler -> Thermostat, UBAParameterWW(0x33), telegram: 08 97 33 00 23 24 (#data=2)
uart_telegram({0x08, 0x90, 0x33, 0x00, 0x23, 0x24});
// Boiler -> Me, UBAParameterWW(0x33), telegram: 08 0B 33 00 08 FF 34 FB 00 28 00 00 46 00 FF FF 00 (#data=13)
uart_telegram({0x08, 0x0B, 0x33, 0x00, 0x08, 0xFF, 0x34, 0xFB, 0x00, 0x28, 0x00, 0x00, 0x46, 0x00, 0xFF, 0xFF, 0x00});
// Thermostat 0x2A5 for HC1
uart_telegram({0x10, 00, 0xFF, 00, 01, 0xA5, 0x80, 00, 01, 0x30, 0x28, 00, 0x30, 0x28, 01, 0x54,
03, 03, 01, 01, 0x54, 02, 0xA8, 00, 00, 0x11, 01, 03, 0xFF, 0xFF, 00});
// RC300WWmode2(0x31D), data: 00 00 09 07
uart_telegram({0x10, 00, 0xFF, 00, 02, 0x1D, 00, 00, 0x09, 0x07});
// 2nd thermostat
// Thermostat RCPLUSStatusMessage_HC2(0x01A6)
uart_telegram({0x98, 0x00, 0xFF, 0x00, 0x01, 0xA6, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
return true;
}
if (strcmp(command, "310") == 0) {
EMSESP::logger().info(F("Adding a GB072/RC310 combo..."));
@@ -141,6 +173,8 @@ bool Test::run_test(const char * command, int8_t id) {
if (strcmp(command, "thermostat") == 0) {
EMSESP::logger().info(F("Adding thermostat..."));
Mqtt::nested_format(1); // use nested
// Mqtt::nested_format(2); // single
add_device(0x10, 192); // FW120
@@ -189,14 +223,12 @@ bool Test::run_test(const char * command, int8_t id) {
// These next tests are run from the Console
// using the test command
void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const std::string & data) {
// switch to su
shell.add_flags(CommandFlags::ADMIN);
// init stuff
Mqtt::ha_enabled(true);
EMSESP::dallassensor_.dallas_format(1);
Mqtt::ha_climate_format(1);
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
EMSESP::watch(EMSESP::Watch::WATCH_RAW); // raw
@@ -334,7 +366,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
shell.printfln(F("Testing unknown2..."));
// simulate getting version information back from an unknown device
rx_telegram({0x09, 0x0B, 0x02, 0x00, 0x5A, 0x01, 0x02}); // product id is 90 which doesn't exist
rx_telegram({0x09, 0x0B, 0x02, 0x00, 0x5A, 0x01, 0x02}); // productID is 90 which doesn't exist
}
if (command == "gateway") {
@@ -351,9 +383,19 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
shell.invoke_command("show mqtt");
}
if (command == "2thermostats") {
shell.printfln(F("Testing multiple thermostats..."));
run_test("2thermostats");
shell.invoke_command("show");
shell.invoke_command("show devices");
}
if (command == "web") {
shell.printfln(F("Testing Web..."));
Mqtt::enabled(false); // turn off mqtt
Mqtt::ha_enabled(false); // turn off ha
run_test("boiler");
run_test("thermostat");
@@ -373,10 +415,12 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
shell.println();
Serial.print(COLOR_RESET);
doc.clear();
JsonObject root = doc.to<JsonObject>();
emsdevice->generate_values_json_web(root);
// JsonObject root = doc.to<JsonObject>();
// emsdevice->generate_values_web(root);
JsonArray output = doc.to<JsonArray>();
emsdevice->generate_values_web_all(output);
Serial.print(COLOR_BRIGHT_MAGENTA);
serializeJson(doc, Serial);
@@ -453,7 +497,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
}
if (command == "ha") {
shell.printfln(F("Testing HA discovery"));
shell.printfln(F("Testing HA mqtt discovery"));
Mqtt::ha_enabled(true);
// Mqtt::nested_format(1);
Mqtt::nested_format(2);
@@ -466,25 +510,10 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
shell.invoke_command("call system publish");
shell.invoke_command("show mqtt");
shell.invoke_command("call boiler fanwork");
shell.invoke_command("call thermostat seltemp"); // sensor.thermostat_hc1_selected_room_temperature
shell.invoke_command("call thermostat entities");
shell.invoke_command("call boiler entities");
}
if (command == "dv") {
shell.printfln(F("Testing device value rendering"));
Mqtt::ha_enabled(true);
Mqtt::nested_format(1);
Mqtt::send_response(false);
run_test("boiler");
run_test("thermostat");
// change a value to null/bogus/dormant
shell.invoke_command("call boiler wwseltemp");
shell.invoke_command("call system publish");
// shell.invoke_command("call boiler fanwork");
// shell.invoke_command("call thermostat seltemp"); // sensor.thermostat_hc1_selected_room_temperature
// shell.invoke_command("call thermostat entities");
// shell.invoke_command("call boiler entities");
}
if (command == "lastcode") {
@@ -505,6 +534,67 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
// shell.invoke_command("show");
}
if (command == "dv") {
shell.printfln(F("Testing device value rendering"));
Mqtt::ha_enabled(true);
Mqtt::nested_format(1);
Mqtt::send_response(false);
run_test("boiler");
run_test("thermostat");
shell.invoke_command("call boiler wwseltemp");
shell.invoke_command("call system publish");
}
if (command == "dallas") {
shell.printfln(F("Testing adding Dallas sensor"));
Mqtt::ha_enabled(true);
Mqtt::nested_format(1);
// Mqtt::nested_format(0);
emsesp::EMSESP::dallassensor_.test();
shell.invoke_command("show");
shell.invoke_command("call system publish");
// rename
EMSESP::dallassensor_.update("01-0203-0405-0607", "testdallas", 2);
shell.invoke_command("show");
shell.invoke_command("call system publish");
}
if (command == "analog") {
shell.printfln(F("Testing adding Analog sensor"));
Mqtt::ha_enabled(true);
// Mqtt::ha_enabled(false);
Mqtt::nested_format(1);
// Mqtt::nested_format(0);
emsesp::EMSESP::analogsensor_.test();
shell.invoke_command("show");
// shell.invoke_command("call system publish");
// shell.invoke_command("show mqtt");
// rename
// bool update(uint8_t id, const std::string & name, int16_t offset, float factor, uint8_t uom, uint8_t type);
EMSESP::analogsensor_.update(36, "analogtest", 2, 0.7, 17, 1);
shell.invoke_command("show");
// shell.invoke_command("call system publish");
}
if (command == "healthcheck") {
uint8_t n = 0;
if (!data.empty()) {
n = Helpers::atoint(data.c_str());
}
// n=1 = EMSESP::system_.HEALTHCHECK_NO_BUS
// n=2 = EMSESP::system_.HEALTHCHECK_NO_NETWORK
shell.printfln(F("Testing healthcheck with %d"), n);
EMSESP::system_.healthcheck(n);
}
if (command == "dv2") {
shell.printfln(F("Testing device value lost"));
@@ -517,6 +607,41 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
shell.invoke_command("call system publish");
}
if (command == "api_values") {
shell.printfln(F("Testing API getting values"));
Mqtt::ha_enabled(false);
Mqtt::nested_format(1);
Mqtt::send_response(false);
// EMSESP::bool_format(BOOL_FORMAT_10); // BOOL_FORMAT_10_STR
EMSESP::system_.bool_format(BOOL_FORMAT_TRUEFALSE); // BOOL_FORMAT_TRUEFALSE_STR
run_test("boiler");
run_test("thermostat");
AsyncWebServerRequest request;
DynamicJsonDocument doc(2000);
JsonVariant json;
request.method(HTTP_GET);
request.url("/api/boiler/wwcirc");
EMSESP::webAPIService.webAPIService_get(&request);
request.url("/api/boiler/values");
EMSESP::webAPIService.webAPIService_get(&request);
}
if (command == "mqtt_post") {
shell.printfln(F("Testing MQTT incoming changes"));
Mqtt::ha_enabled(false);
Mqtt::nested_format(1);
Mqtt::send_response(false);
EMSESP::system_.bool_format(BOOL_FORMAT_10); // BOOL_FORMAT_10_STR
// EMSESP::bool_format(BOOL_FORMAT_TRUEFALSE); // BOOL_FORMAT_TRUEFALSE_STR
run_test("boiler");
run_test("thermostat");
EMSESP::mqtt_.incoming("ems-esp/boiler/wwseltemp", "59");
}
if (command == "api") {
shell.printfln(F("Testing API with MQTT and REST, standalone"));
@@ -609,10 +734,12 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
/*
requestX.url("/api"); // should fail
EMSESP::webAPIService.webAPIService_get(&requestX);
*/
requestX.method(HTTP_POST);
char dataX[] = "{\"device\":\"thermostat\", \"entity\":\"seltemp\",\"value\":13}";
/*
char dataX[] = "{\"device\":\"system\", \"entity\":\"settings\"}";
deserializeJson(docX, dataX);
jsonX = docX.as<JsonVariant>();
requestX.url("/api");
@@ -620,6 +747,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
return;
*/
/*
// char dataX[] = "{\"value\":\"0B 88 19 19 02\"}";
char dataX[] = "{\"name\":\"temp\",\"value\":11}";
@@ -702,6 +830,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
#if defined(EMSESP_STANDALONE)
// Web API TESTS
AsyncWebServerRequest request;
request.method(HTTP_GET);
request.url("/api/thermostat"); // check if defaults to info
@@ -727,7 +856,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
JsonVariant json;
// 1
char data1[] = "{\"name\":\"temp\",\"value\":11}";
char data1[] = "{\"entity\":\"seltemp\",\"value\":11}";
deserializeJson(doc, data1);
json = doc.as<JsonVariant>();
request.url("/api/thermostat");
@@ -737,11 +866,11 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
char data2[] = "{\"value\":12}";
deserializeJson(doc, data2);
json = doc.as<JsonVariant>();
request.url("/api/thermostat/temp");
request.url("/api/thermostat/seltemp");
EMSESP::webAPIService.webAPIService_post(&request, json);
// 3
char data3[] = "{\"device\":\"thermostat\", \"name\":\"seltemp\",\"value\":13}";
char data3[] = "{\"device\":\"thermostat\", \"cmd\":\"seltemp\",\"value\":13}";
deserializeJson(doc, data3);
json = doc.as<JsonVariant>();
request.url("/api");
@@ -1127,8 +1256,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XXLARGE_DYN);
char key[6];
char value[6];
char key[8];
char value[8];
// fit it up, to its limit of the Json buffer (which is about 169 records)
for (uint8_t i = 0; i < 200; i++) {
@@ -1170,23 +1299,29 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
char boiler_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
char thermostat_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
char system_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
EMSESP::EMSESP::mqtt_.publish("boiler", "test me");
Mqtt::show_mqtt(shell); // show queue
strcpy(boiler_topic, "ems-esp/boiler");
strcpy(thermostat_topic, "ems-esp/thermostat");
strcpy(system_topic, "ems-esp/system");
EMSESP::mqtt_.incoming(boiler_topic, ""); // test if ignore empty payloads
// test publishing
EMSESP::EMSESP::mqtt_.publish(boiler_topic, "test me");
// test receiving
EMSESP::mqtt_.incoming(boiler_topic, ""); // test if ignore empty payloads, should return values
EMSESP::mqtt_.incoming(boiler_topic, "12345"); // error: invalid format
EMSESP::mqtt_.incoming("bad_topic", "12345"); // error: no matching topic
EMSESP::mqtt_.incoming("bad_topic", "123456"); // error: no matching topic
EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"garbage\",\"data\":22.52}"); // error: should report error
EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"comfort\",\"data\":\"eco\"}");
EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"wwactivated\",\"data\":\"1\"}"); // with quotes
EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"wwactivated\",\"data\":1}"); // without quotes
EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"flowtemp\",\"data\":55}");
EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"selflowtemp\",\"data\":55}");
// test direct commands
EMSESP::mqtt_.incoming("ems-esp/boiler/selflowtemp", "56");
EMSESP::mqtt_.incoming(system_topic, "{\"cmd\":\"send\",\"data\":\"01 02 03 04 05\"}");
EMSESP::mqtt_.incoming(system_topic, "{\"cmd\":\"pin\",\"id\":12,\"data\":\"1\"}");

View File

@@ -30,22 +30,29 @@ namespace emsesp {
// #define EMSESP_DEBUG_DEFAULT "solar"
// #define EMSESP_DEBUG_DEFAULT "mixer"
// #define EMSESP_DEBUG_DEFAULT "web"
// #define EMSESP_DEBUG_DEFAULT "mqtt"
// #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 "ha"
// #define EMSESP_DEBUG_DEFAULT "board_profile"
// #define EMSESP_DEBUG_DEFAULT "shower_alert"
// #define EMSESP_DEBUG_DEFAULT "310"
// #define EMSESP_DEBUG_DEFAULT "render"
#define EMSESP_DEBUG_DEFAULT "api"
// #define EMSESP_DEBUG_DEFAULT "api"
// #define EMSESP_DEBUG_DEFAULT "crash"
// #define EMSESP_DEBUG_DEFAULT "dv"
// #define EMSESP_DEBUG_DEFAULT "lastcode"
// #define EMSESP_DEBUG_DEFAULT "2thermostats"
// #define EMSESP_DEBUG_DEFAULT "dallas"
// #define EMSESP_DEBUG_DEFAULT "analog"
// #define EMSESP_DEBUG_DEFAULT "api_values"
// #define EMSESP_DEBUG_DEFAULT "mqtt_post"
class Test {
public:
static void run_test(uuid::console::Shell & shell, const std::string & command);
static void run_test(uuid::console::Shell & shell, const std::string & command, const std::string & data = "");
static bool run_test(const char * command, int8_t id = 0);
static void dummy_mqtt_commands(const char * message);
static void rx_telegram(const std::vector<uint8_t> & data);

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.3.1b1"
#define EMSESP_APP_VERSION "3.4.0b0"

View File

@@ -24,6 +24,9 @@ using namespace std::placeholders; // for `_1` etc
namespace emsesp {
uint32_t WebAPIService::api_count_ = 0;
uint16_t WebAPIService::api_fails_ = 0;
WebAPIService::WebAPIService(AsyncWebServer * server, SecurityManager * securityManager)
: _securityManager(securityManager)
, _apiHandler("/api", std::bind(&WebAPIService::webAPIService_post, this, _1, _2), 256) { // for POSTS, must use 'Content-Type: application/json' in header
@@ -66,6 +69,33 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject & input) {
is_admin = settings.notoken_api | AuthenticationPredicates::IS_ADMIN(authentication);
});
// check for query parameters first, the old style from v2
// api?device={device}&cmd={name}&data={value}&id={hc}
if (request->url() == "/api") {
// get the device
if (request->hasParam(F_(device))) {
input["device"] = request->getParam(F_(device))->value().c_str();
}
if (request->hasParam(F_(cmd))) {
input["cmd"] = request->getParam(F_(cmd))->value().c_str();
}
if (request->hasParam(F_(data))) {
input["data"] = request->getParam(F_(data))->value().c_str();
}
if (request->hasParam(F_(value))) {
input["value"] = request->getParam(F_(value))->value().c_str();
}
if (request->hasParam(F_(id))) {
input["id"] = Helpers::atoint(request->getParam(F_(id))->value().c_str());
}
if (request->hasParam(F_(hc))) {
input["hc"] = Helpers::atoint(request->getParam(F_(hc))->value().c_str());
}
if (request->hasParam(F_(wwc))) {
input["wwc"] = Helpers::atoint(request->getParam(F_(wwc))->value().c_str());
}
}
// output json buffer
PrettyAsyncJsonResponse * response = new PrettyAsyncJsonResponse(false, EMSESP_JSON_SIZE_XXLARGE_DYN);
JsonObject output = response->getRoot();
@@ -81,8 +111,9 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject & input) {
snprintf(error, sizeof(error), "Call failed with error code (%s)", Command::return_code_string(return_code).c_str());
}
emsesp::EMSESP::logger().err(error);
api_fails_++;
} else {
emsesp::EMSESP::logger().debug(F("API command called successfully"));
// emsesp::EMSESP::logger().debug(F("API command called successfully"));
// if there was no json output from the call, default to the output message 'OK'.
if (!output.size()) {
output["message"] = "OK";
@@ -96,6 +127,7 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject & input) {
response->setLength();
response->setContentType("application/json");
request->send(response);
api_count_++;
#if defined(EMSESP_STANDALONE)
Serial.print(COLOR_YELLOW);

View File

@@ -19,14 +19,6 @@
#ifndef WebAPIService_h
#define WebAPIService_h
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
#include <string>
#include <unordered_map>
#include <vector>
#define EMSESP_API_SERVICE_PATH "/api"
namespace emsesp {
@@ -38,10 +30,21 @@ class WebAPIService {
void webAPIService_post(AsyncWebServerRequest * request, JsonVariant & json); // for POSTs
void webAPIService_get(AsyncWebServerRequest * request); // for GETs
static uint32_t api_count() {
return api_count_;
}
static uint16_t api_fails() {
return api_fails_;
}
private:
SecurityManager * _securityManager;
AsyncCallbackJsonWebHandler _apiHandler; // for POSTs
static uint32_t api_count_;
static uint16_t api_fails_;
void parse(AsyncWebServerRequest * request, JsonObject & input);
};

View File

@@ -0,0 +1,272 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "emsesp.h"
namespace emsesp {
using namespace std::placeholders; // for `_1` etc
WebCustomizationService::WebCustomizationService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(WebCustomization::read,
WebCustomization::update,
this,
server,
EMSESP_CUSTOMIZATION_SERVICE_PATH,
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))
, _device_entities_handler(DEVICE_ENTITIES_PATH,
securityManager->wrapCallback(std::bind(&WebCustomizationService::device_entities, this, _1, _2),
AuthenticationPredicates::IS_AUTHENTICATED)) {
server->on(DEVICES_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WebCustomizationService::devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
server->on(RESET_CUSTOMIZATION_SERVICE_PATH,
HTTP_POST,
securityManager->wrapRequest(std::bind(&WebCustomizationService::reset_customization, this, _1), AuthenticationPredicates::IS_ADMIN));
_exclude_entities_handler.setMethod(HTTP_POST);
_exclude_entities_handler.setMaxContentLength(256);
server->addHandler(&_exclude_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
void WebCustomization::read(WebCustomization & settings, JsonObject & root) {
// Dallas Sensor customization
JsonArray sensorsJson = root.createNestedArray("sensors");
for (const SensorCustomization & sensor : settings.sensorCustomizations) {
JsonObject sensorJson = sensorsJson.createNestedObject();
sensorJson["id_str"] = sensor.id_str; // is
sensorJson["name"] = sensor.name; // n
sensorJson["offset"] = sensor.offset; // o
}
// Analog Sensor customization
JsonArray analogJson = root.createNestedArray("analogs");
for (const AnalogCustomization & sensor : settings.analogCustomizations) {
JsonObject sensorJson = analogJson.createNestedObject();
sensorJson["id"] = sensor.id; // i
sensorJson["name"] = sensor.name; // n
sensorJson["offset"] = sensor.offset; // o
sensorJson["factor"] = sensor.factor; // f
sensorJson["uom"] = sensor.uom; // u
sensorJson["type"] = sensor.type; // t
}
// Exclude entities customization
JsonArray exclude_entitiesJson = root.createNestedArray("exclude_entities");
for (const EntityCustomization & entityCustomization : settings.entityCustomizations) {
JsonObject entityJson = exclude_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);
}
}
}
// call on initialization and also when the page is saved via web
// this loads the data into the internal class
StateUpdateResult WebCustomization::update(JsonObject & root, WebCustomization & settings) {
// Dallas Sensor customization
settings.sensorCustomizations.clear();
if (root["sensors"].is<JsonArray>()) {
for (const JsonObject sensorJson : root["sensors"].as<JsonArray>()) {
// create each of the sensor, overwritting any previous settings
SensorCustomization sensor = SensorCustomization();
sensor.id_str = sensorJson["id_str"].as<std::string>();
sensor.name = sensorJson["name"].as<std::string>();
sensor.offset = sensorJson["offset"];
settings.sensorCustomizations.push_back(sensor); // add to list
}
}
// Analog Sensor customization
settings.analogCustomizations.clear();
if (root["analogs"].is<JsonArray>()) {
for (const JsonObject analogJson : root["analogs"].as<JsonArray>()) {
// create each of the sensor, overwritting any previous settings
AnalogCustomization sensor = AnalogCustomization();
sensor.id = analogJson["id"];
sensor.name = analogJson["name"].as<std::string>();
sensor.offset = analogJson["offset"];
sensor.factor = analogJson["factor"];
sensor.uom = analogJson["uom"];
sensor.type = analogJson["type"];
settings.analogCustomizations.push_back(sensor); // add to list
}
}
// load array of entities id's to exclude, building up the object class
settings.entityCustomizations.clear();
if (root["exclude_entities"].is<JsonArray>()) {
for (const JsonObject exclude_entities : root["exclude_entities"].as<JsonArray>()) {
EntityCustomization new_entry = EntityCustomization();
new_entry.product_id = exclude_entities["product_id"];
new_entry.device_id = exclude_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
}
settings.entityCustomizations.push_back(new_entry); // save the new object
}
}
return StateUpdateResult::CHANGED;
}
// deletes the customization file
void WebCustomizationService::reset_customization(AsyncWebServerRequest * request) {
#ifndef EMSESP_STANDALONE
if (LITTLEFS.remove(EMSESP_CUSTOMIZATION_FILE)) {
AsyncWebServerResponse * response = request->beginResponse(200); // OK
request->send(response);
EMSESP::system_.restart_requested(true);
return;
}
// failed
AsyncWebServerResponse * response = request->beginResponse(204); // no content error
request->send(response);
#endif
}
// send back a short list devices used in the customization page
void WebCustomizationService::devices(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_LARGE_DYN);
JsonObject root = response->getRoot();
JsonArray devices = root.createNestedArray("devices");
for (auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice->has_entities()) {
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);
} else {
obj["s"] = emsdevice->device_type_name();
}
}
}
response->setLength();
request->send(response);
}
// send back list device entities
void WebCustomizationService::device_entities(AsyncWebServerRequest * request, JsonVariant & json) {
if (json.is<JsonObject>()) {
MsgpackAsyncJsonResponse * response = new MsgpackAsyncJsonResponse(true, EMSESP_JSON_SIZE_XXLARGE_DYN);
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice) {
if (emsdevice->unique_id() == json["id"]) {
#ifndef EMSESP_STANDALONE
JsonArray output = response->getRoot();
emsdevice->generate_values_web_all(output);
#endif
response->setLength();
request->send(response);
return;
}
}
}
}
// invalid, but send OK anyway
AsyncWebServerResponse * response = request->beginResponse(200);
request->send(response);
}
// takes a list of excluded ids send from the webUI
// saves it in the customization service
// and updates the entity list real-time
void WebCustomizationService::exclude_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) {
JsonArray entity_ids = json["entity_ids"];
std::vector<uint8_t> temp;
for (JsonVariant id : entity_ids) {
uint8_t entity_id = id.as<int>();
emsdevice->exclude_entity(entity_id); // this will have immediate affect
temp.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();
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 < temp.size(); i++) {
entityCustomization.entity_ids.push_back(temp[i]);
}
return StateUpdateResult::CHANGED;
}
}
// create a new entry in the list
EntityCustomization new_entry;
new_entry.product_id = product_id;
new_entry.device_id = device_id;
for (uint8_t i = 0; i < temp.size(); i++) {
new_entry.entity_ids.push_back(temp[i]);
}
settings.entityCustomizations.push_back(new_entry);
return StateUpdateResult::CHANGED;
},
"local");
break;
}
}
}
}
AsyncWebServerResponse * response = request->beginResponse(200); // OK
request->send(response);
}
// load the settings when the service starts
void WebCustomizationService::begin() {
_fsPersistence.readFromFS();
}
} // namespace emsesp

View File

@@ -0,0 +1,106 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef WebCustomizationService_h
#define WebCustomizationService_h
#define EMSESP_CUSTOMIZATION_FILE "/config/emsespCustomization.json"
// GET
#define DEVICES_SERVICE_PATH "/rest/devices"
#define EMSESP_CUSTOMIZATION_SERVICE_PATH "/rest/customization"
// POST
#define DEVICE_ENTITIES_PATH "/rest/deviceEntities"
#define EXCLUDE_ENTITIES_PATH "/rest/excludeEntities"
#define RESET_CUSTOMIZATION_SERVICE_PATH "/rest/resetCustomizations"
namespace emsesp {
// Customization for dallas sensor
class SensorCustomization {
public:
std::string id_str;
std::string name;
uint16_t offset;
};
class AnalogCustomization {
public:
uint8_t id;
std::string name;
uint16_t offset;
float factor;
uint8_t uom; // 0 is none
int8_t type; // -1 is for deletion
// used for removing from a list
bool operator==(const AnalogCustomization & a) const {
return id == a.id;
}
bool operator!=(const AnalogCustomization & a) const {
return !operator==(a);
}
};
// 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
};
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);
};
class WebCustomizationService : public StatefulService<WebCustomization> {
public:
WebCustomizationService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
void begin();
// make all functions public so we can test in the debug and standalone mode
#ifndef EMSESP_STANDALONE
private:
#endif
HttpEndpoint<WebCustomization> _httpEndpoint;
FSPersistence<WebCustomization> _fsPersistence;
// GET
void devices(AsyncWebServerRequest * request);
// POST
void exclude_entities(AsyncWebServerRequest * request, JsonVariant & json);
void device_entities(AsyncWebServerRequest * request, JsonVariant & json);
void reset_customization(AsyncWebServerRequest * request);
AsyncCallbackJsonWebHandler _exclude_entities_handler, _device_entities_handler;
};
} // namespace emsesp
#endif

View File

@@ -23,69 +23,131 @@ namespace emsesp {
using namespace std::placeholders; // for `_1` etc
WebDataService::WebDataService(AsyncWebServer * server, SecurityManager * securityManager)
: _device_dataHandler(DEVICE_DATA_SERVICE_PATH,
securityManager->wrapCallback(std::bind(&WebDataService::device_data, this, _1, _2), AuthenticationPredicates::IS_AUTHENTICATED))
, _writevalue_dataHandler(WRITE_VALUE_SERVICE_PATH,
securityManager->wrapCallback(std::bind(&WebDataService::write_value, this, _1, _2), AuthenticationPredicates::IS_ADMIN))
, _writesensor_dataHandler(WRITE_SENSOR_SERVICE_PATH,
securityManager->wrapCallback(std::bind(&WebDataService::write_sensor, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) {
server->on(EMSESP_DATA_SERVICE_PATH,
: _device_data_handler(DEVICE_DATA_SERVICE_PATH,
securityManager->wrapCallback(std::bind(&WebDataService::device_data, this, _1, _2), AuthenticationPredicates::IS_AUTHENTICATED))
, _write_value_handler(WRITE_VALUE_SERVICE_PATH,
securityManager->wrapCallback(std::bind(&WebDataService::write_value, this, _1, _2), AuthenticationPredicates::IS_ADMIN))
, _write_sensor_handler(WRITE_SENSOR_SERVICE_PATH,
securityManager->wrapCallback(std::bind(&WebDataService::write_sensor, this, _1, _2), AuthenticationPredicates::IS_ADMIN))
, _write_analog_handler(WRITE_ANALOG_SERVICE_PATH,
securityManager->wrapCallback(std::bind(&WebDataService::write_analog, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) {
server->on(CORE_DATA_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WebDataService::all_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
securityManager->wrapRequest(std::bind(&WebDataService::core_data, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
server->on(SENSOR_DATA_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WebDataService::sensor_data, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
server->on(SCAN_DEVICES_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WebDataService::scan_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
HTTP_POST,
securityManager->wrapRequest(std::bind(&WebDataService::scan_devices, this, _1), AuthenticationPredicates::IS_ADMIN));
_device_dataHandler.setMethod(HTTP_POST);
_device_dataHandler.setMaxContentLength(256);
server->addHandler(&_device_dataHandler);
_device_data_handler.setMethod(HTTP_POST);
_device_data_handler.setMaxContentLength(256);
server->addHandler(&_device_data_handler);
_writevalue_dataHandler.setMethod(HTTP_POST);
_writevalue_dataHandler.setMaxContentLength(256);
server->addHandler(&_writevalue_dataHandler);
_write_value_handler.setMethod(HTTP_POST);
_write_value_handler.setMaxContentLength(256);
server->addHandler(&_write_value_handler);
_writesensor_dataHandler.setMethod(HTTP_POST);
_writesensor_dataHandler.setMaxContentLength(256);
server->addHandler(&_writesensor_dataHandler);
_write_sensor_handler.setMethod(HTTP_POST);
_write_sensor_handler.setMaxContentLength(256);
server->addHandler(&_write_sensor_handler);
_write_analog_handler.setMethod(HTTP_POST);
_write_analog_handler.setMaxContentLength(256);
server->addHandler(&_write_analog_handler);
}
// scan devices service
void WebDataService::scan_devices(AsyncWebServerRequest * request) {
EMSESP::logger().info(F("Scanning devices..."));
EMSESP::scan_devices();
request->send(200);
}
void WebDataService::all_devices(AsyncWebServerRequest * request) {
// this is used in the dashboard and contains all ems device information
// /coreData endpoint
void WebDataService::core_data(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_XLARGE_DYN);
JsonObject root = response->getRoot();
// list is already sorted by device type
// Ignore Contoller
JsonArray devices = root.createNestedArray("devices");
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice) {
for (auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice && emsdevice->device_type() != EMSdevice::DeviceType::CONTROLLER) {
JsonObject obj = devices.createNestedObject();
obj["i"] = emsdevice->unique_id(); // id
obj["i"] = emsdevice->unique_id(); // a unique id
obj["t"] = emsdevice->device_type_name(); // type
obj["b"] = emsdevice->brand_to_string(); // brand
obj["n"] = emsdevice->name(); // name
obj["d"] = emsdevice->device_id(); // deviceid
obj["p"] = emsdevice->product_id(); // productid
obj["v"] = emsdevice->version(); // version
obj["e"] = emsdevice->count_entities(); // number of entities (device values)
}
}
// sensors stuff
root["active_sensors"] = EMSESP::dallassensor_.no_sensors() + (EMSESP::analogsensor_.analog_enabled() ? EMSESP::analogsensor_.no_sensors() : 0);
root["analog_enabled"] = EMSESP::analogsensor_.analog_enabled();
response->setLength();
request->send(response);
}
// sensor data - sends back to web
// /sensorData endpoint
// the "sensors" and "analogs" are arrays and must exist
void WebDataService::sensor_data(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_XLARGE_DYN);
JsonObject root = response->getRoot();
// dallas sensors
JsonArray sensors = root.createNestedArray("sensors");
if (EMSESP::have_sensors()) {
uint8_t i = 1;
for (const auto & sensor : EMSESP::sensor_devices()) {
if (EMSESP::dallassensor_.have_sensors()) {
for (const auto & sensor : EMSESP::dallassensor_.sensors()) {
JsonObject obj = sensors.createNestedObject();
obj["n"] = i++; // no
obj["i"] = sensor.to_string(true); // id
obj["t"] = (float)(sensor.temperature_c) / 10; // temp
obj["o"] = (float)(sensor.offset()) / 10; // offset
obj["is"] = sensor.id_str(); // id
obj["n"] = sensor.name(); // name
if (EMSESP::system_.fahrenheit()) {
if (Helpers::hasValue(sensor.temperature_c)) {
obj["t"] = (float)sensor.temperature_c * 0.18 + 32;
}
obj["u"] = DeviceValueUOM::FAHRENHEIT;
obj["o"] = (float)sensor.offset() * 0.18;
} else {
if (Helpers::hasValue(sensor.temperature_c)) {
obj["t"] = (float)sensor.temperature_c / 10;
}
obj["u"] = DeviceValueUOM::DEGREES;
obj["o"] = (float)(sensor.offset()) / 10;
}
}
}
if (EMSESP::system_.analog_enabled()) {
root["analog"] = EMSESP::system_.analog();
// analog sensors
// assume list is already sorted by id
JsonArray analogs = root.createNestedArray("analogs");
if (EMSESP::analog_enabled() && EMSESP::analogsensor_.have_sensors()) {
for (const auto & sensor : EMSESP::analogsensor_.sensors()) {
// don't send if it's marked for removal
if (sensor.type() != AnalogSensor::AnalogType::MARK_DELETED) {
JsonObject obj = analogs.createNestedObject();
obj["i"] = sensor.id();
obj["n"] = sensor.name();
obj["u"] = sensor.uom();
obj["o"] = sensor.offset();
obj["f"] = sensor.factor();
obj["t"] = sensor.type();
if (sensor.type() != AnalogSensor::AnalogType::NOTUSED) {
obj["v"] = Helpers::round2(sensor.value(), 1); // is optional and is a float
}
}
}
}
response->setLength();
@@ -96,7 +158,7 @@ void WebDataService::all_devices(AsyncWebServerRequest * request) {
// Compresses the JSON using MsgPack https://msgpack.org/index.html
void WebDataService::device_data(AsyncWebServerRequest * request, JsonVariant & json) {
if (json.is<JsonObject>()) {
MsgpackAsyncJsonResponse * response = new MsgpackAsyncJsonResponse(false, EMSESP_JSON_SIZE_XXLARGE_DYN);
MsgpackAsyncJsonResponse * response = new MsgpackAsyncJsonResponse(false, EMSESP_JSON_SIZE_XXXLARGE_DYN);
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice) {
if (emsdevice->unique_id() == json["id"]) {
@@ -106,8 +168,8 @@ void WebDataService::device_data(AsyncWebServerRequest * request, JsonVariant &
}
EMSESP::wait_validate(0); // reset in case of timeout
#ifndef EMSESP_STANDALONE
JsonObject root = response->getRoot();
emsdevice->generate_values_json_web(root);
JsonObject output = response->getRoot();
emsdevice->generate_values_web(output);
#endif
response->setLength();
request->send(response);
@@ -117,12 +179,13 @@ void WebDataService::device_data(AsyncWebServerRequest * request, JsonVariant &
}
}
// invalid
// invalid but send ok
AsyncWebServerResponse * response = request->beginResponse(200);
request->send(response);
}
// takes a command and its data value from a specific Device, from the Web
// takes a command and its data value from a specific EMS Device, from the Web
// assumes the service has been checked for admin authentication
void WebDataService::write_value(AsyncWebServerRequest * request, JsonVariant & json) {
if (json.is<JsonObject>()) {
@@ -180,23 +243,42 @@ void WebDataService::write_value(AsyncWebServerRequest * request, JsonVariant &
request->send(response);
}
// takes a sensorname and optional offset from the Web
// takes a dallas sensor name and optional offset from the WebUI and update the customization settings
// via the Dallas service
void WebDataService::write_sensor(AsyncWebServerRequest * request, JsonVariant & json) {
bool ok = false;
if (json.is<JsonObject>()) {
JsonObject sensor = json["sensor"];
JsonObject sensor = json;
// if valid add.
uint8_t no = sensor["no"];
if (no > 0 && no < 100) {
char name[20];
std::string id = sensor["id"];
strlcpy(name, id.c_str(), sizeof(name));
float offset = sensor["offset"]; // this will be a float value. We'll convert it to int and * 10 it
int16_t offset10 = offset * 10;
char idstr[3];
ok = EMSESP::dallassensor_.update(Helpers::itoa(idstr, no, 10), name, offset10);
std::string id_str = sensor["id_str"]; // this is the key
std::string name = sensor["name"];
// calculate offset. We'll convert it to an int and * 10
float offset = sensor["offset"];
int16_t offset10 = offset * 10;
if (EMSESP::system_.fahrenheit()) {
offset10 = offset / 0.18;
}
ok = EMSESP::dallassensor_.update(id_str, name, offset10);
}
AsyncWebServerResponse * response = request->beginResponse(ok ? 200 : 204);
request->send(response);
}
// update the analog record, or create a new one
void WebDataService::write_analog(AsyncWebServerRequest * request, JsonVariant & json) {
bool ok = false;
if (json.is<JsonObject>()) {
JsonObject analog = json;
uint8_t id = analog["id"]; // this is the unique key
std::string name = analog["name"];
float factor = analog["factor"];
int16_t offset = analog["offset"];
uint8_t uom = analog["uom"];
int8_t type = analog["type"];
ok = EMSESP::analogsensor_.update(id, name, offset, factor, uom, type);
}
AsyncWebServerResponse * response = request->beginResponse(ok ? 200 : 204);

View File

@@ -19,16 +19,16 @@
#ifndef WebDataService_h
#define WebDataService_h
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#define EMSESP_DATA_SERVICE_PATH "/rest/data"
// GET
#define CORE_DATA_SERVICE_PATH "/rest/coreData"
#define SCAN_DEVICES_SERVICE_PATH "/rest/scanDevices"
#define DEVICE_DATA_SERVICE_PATH "/rest/deviceData"
#define SENSOR_DATA_SERVICE_PATH "/rest/sensorData"
// POST
#define WRITE_VALUE_SERVICE_PATH "/rest/writeValue"
#define WRITE_SENSOR_SERVICE_PATH "/rest/writeSensor"
#define WRITE_ANALOG_SERVICE_PATH "/rest/writeAnalog"
namespace emsesp {
@@ -42,15 +42,17 @@ class WebDataService {
#endif
// GET
void all_devices(AsyncWebServerRequest * request);
void scan_devices(AsyncWebServerRequest * request);
void core_data(AsyncWebServerRequest * request);
void sensor_data(AsyncWebServerRequest * request);
// POST
void device_data(AsyncWebServerRequest * request, JsonVariant & json);
void write_value(AsyncWebServerRequest * request, JsonVariant & json);
void write_sensor(AsyncWebServerRequest * request, JsonVariant & json);
void write_analog(AsyncWebServerRequest * request, JsonVariant & json);
void scan_devices(AsyncWebServerRequest * request);
AsyncCallbackJsonWebHandler _device_dataHandler, _writevalue_dataHandler, _writesensor_dataHandler;
AsyncCallbackJsonWebHandler _device_data_handler, _write_value_handler, _write_sensor_handler, _write_analog_handler;
};
} // namespace emsesp

View File

@@ -27,6 +27,7 @@ WebLogService::WebLogService(AsyncWebServer * server, SecurityManager * security
, setValues_(LOG_SETTINGS_PATH, std::bind(&WebLogService::setValues, this, _1, _2), 256) { // for POSTS
events_.setFilter(securityManager->filterRequest(AuthenticationPredicates::IS_ADMIN));
server->addHandler(&events_);
server->on(EVENT_SOURCE_LOG_PATH, HTTP_GET, std::bind(&WebLogService::forbidden, this, _1));
@@ -104,6 +105,14 @@ WebLogService::QueuedLogMessage::QueuedLogMessage(unsigned long id, std::shared_
}
void WebLogService::operator<<(std::shared_ptr<uuid::log::Message> message) {
/*
// special case for trace, show trace and notice messages only
// added by mvdp
if (log_level() == uuid::log::Level::TRACE && message->level != uuid::log::Level::TRACE && message->level != uuid::log::Level::NOTICE) {
return;
}
*/
if (log_messages_.size() >= maximum_log_messages_) {
log_messages_.pop_front();
}
@@ -184,7 +193,7 @@ void WebLogService::transmit(const QueuedLogMessage & message) {
// send the complete log buffer to the API, not filtering on log level
void WebLogService::fetchLog(AsyncWebServerRequest * request) {
MsgpackAsyncJsonResponse * response = new MsgpackAsyncJsonResponse(false, EMSESP_JSON_SIZE_XXLARGE_DYN); // 16kb buffer
MsgpackAsyncJsonResponse * response = new MsgpackAsyncJsonResponse(false, EMSESP_JSON_SIZE_LARGE_DYN + 192 * log_messages_.size());
JsonObject root = response->getRoot();
JsonArray log = root.createNestedArray("events");

View File

@@ -19,13 +19,6 @@
#ifndef WebLogService_h
#define WebLogService_h
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#include <uuid/log.h>
#define EVENT_SOURCE_LOG_PATH "/es/log"
#define FETCH_LOG_PATH "/rest/fetchLog"
#define LOG_SETTINGS_PATH "/rest/logSettings"

View File

@@ -50,63 +50,49 @@ void WebSettings::read(WebSettings & settings, JsonObject & root) {
root["shower_alert"] = settings.shower_alert;
root["rx_gpio"] = settings.rx_gpio;
root["tx_gpio"] = settings.tx_gpio;
root["phy_type"] = settings.phy_type;
root["dallas_gpio"] = settings.dallas_gpio;
root["dallas_parasite"] = settings.dallas_parasite;
root["led_gpio"] = settings.led_gpio;
root["hide_led"] = settings.hide_led;
root["low_clock"] = settings.low_clock;
root["telnet_enabled"] = settings.telnet_enabled;
root["notoken_api"] = settings.notoken_api;
root["readonly_mode"] = settings.readonly_mode;
root["analog_enabled"] = settings.analog_enabled;
root["pbutton_gpio"] = settings.pbutton_gpio;
root["solar_maxflow"] = settings.solar_maxflow;
root["board_profile"] = settings.board_profile;
root["dallas_format"] = settings.dallas_format;
root["fahrenheit"] = settings.fahrenheit;
root["bool_format"] = settings.bool_format;
root["enum_format"] = settings.enum_format;
root["weblog_level"] = settings.weblog_level;
root["weblog_buffer"] = settings.weblog_buffer;
root["weblog_compact"] = settings.weblog_compact;
for (uint8_t i = 0; i < MAX_NUM_SENSOR_NAMES; i++) {
char buf[20];
snprintf(buf, sizeof(buf), "sensor_id%d", i);
root[buf] = settings.sensor[i].id;
snprintf(buf, sizeof(buf), "sensor_name%d", i);
root[buf] = settings.sensor[i].name;
snprintf(buf, sizeof(buf), "sensor_offset%d", i);
root[buf] = settings.sensor[i].offset;
}
root["phy_type"] = settings.phy_type;
root["eth_power"] = settings.eth_power;
root["eth_phy_addr"] = settings.eth_phy_addr;
root["eth_clock_mode"] = settings.eth_clock_mode;
}
// call on initialization and also when settings are updated via web or console
StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings) {
// load default GPIO configuration based on board profile
std::vector<uint8_t> data; // led, dallas, rx, tx, button, phy_type
std::vector<int8_t> data; // // led, dallas, rx, tx, button, phy_type, eth_power, eth_phy_addr, eth_clock_mode
String old_board_profile = settings.board_profile;
settings.board_profile = root["board_profile"] | EMSESP_DEFAULT_BOARD_PROFILE;
settings.board_profile = root["board_profile"] | EMSESP_DEFAULT_BOARD_PROFILE;
if (!System::load_board_profile(data, settings.board_profile.c_str())) {
settings.board_profile = EMSESP_DEFAULT_BOARD_PROFILE; // invalid board configuration, override the default in case it has been misspelled
}
uint8_t default_led_gpio = data[0];
uint8_t default_dallas_gpio = data[1];
uint8_t default_rx_gpio = data[2];
uint8_t default_tx_gpio = data[3];
uint8_t default_pbutton_gpio = data[4];
uint8_t default_phy_type = data[5];
if (old_board_profile != settings.board_profile) {
EMSESP::logger().info(F("EMS-ESP version %s"), EMSESP_APP_VERSION);
// check to see if we have a settings file, if not it's a fresh install
if (!root.size()) {
EMSESP::logger().info(F("Initializing configuration with board profile %s"), settings.board_profile.c_str());
} else {
EMSESP::logger().info(F("Using configuration from board profile %s"), settings.board_profile.c_str());
}
}
uint8_t default_led_gpio = data[0];
uint8_t default_dallas_gpio = data[1];
uint8_t default_rx_gpio = data[2];
uint8_t default_tx_gpio = data[3];
uint8_t default_pbutton_gpio = data[4];
uint8_t default_phy_type = data[5];
uint8_t default_eth_power = data[6];
uint8_t default_eth_phy_addr = data[7];
uint8_t default_eth_clock_mode = data[8];
int prev;
reset_flags();
@@ -135,6 +121,10 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings)
settings.syslog_mark_interval = root["syslog_mark_interval"] | EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL;
check_flag(prev, settings.syslog_mark_interval, ChangeFlags::SYSLOG);
prev = settings.syslog_port;
settings.syslog_port = root["syslog_port"] | EMSESP_DEFAULT_SYSLOG_PORT;
check_flag(prev, settings.syslog_port, ChangeFlags::SYSLOG);
#ifndef EMSESP_STANDALONE
String old_syslog_host = settings.syslog_host;
settings.syslog_host = root["syslog_host"] | EMSESP_DEFAULT_SYSLOG_HOST;
@@ -143,15 +133,6 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings)
}
#endif
prev = settings.syslog_port;
settings.syslog_port = root["syslog_port"] | EMSESP_DEFAULT_SYSLOG_PORT;
check_flag(prev, settings.syslog_port, ChangeFlags::SYSLOG);
// adc
prev = settings.analog_enabled;
settings.analog_enabled = root["analog_enabled"] | EMSESP_DEFAULT_ANALOG_ENABLED;
check_flag(prev, settings.analog_enabled, ChangeFlags::ADC);
// button
prev = settings.pbutton_gpio;
settings.pbutton_gpio = root["pbutton_gpio"] | default_pbutton_gpio;
@@ -181,15 +162,48 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings)
settings.hide_led = root["hide_led"] | EMSESP_DEFAULT_HIDE_LED;
check_flag(prev, settings.hide_led, ChangeFlags::LED);
//
// next ones are settings that don't need any follow-up actions
//
// adc
prev = settings.analog_enabled;
settings.analog_enabled = root["analog_enabled"] | EMSESP_DEFAULT_ANALOG_ENABLED;
check_flag(prev, settings.analog_enabled, ChangeFlags::ADC);
// these need reboots to be applied
settings.ems_bus_id = root["ems_bus_id"] | EMSESP_DEFAULT_EMS_BUS_ID;
//
// these need reboots to be applied...
//
prev = settings.telnet_enabled;
settings.telnet_enabled = root["telnet_enabled"] | EMSESP_DEFAULT_TELNET_ENABLED;
check_flag(prev, settings.telnet_enabled, ChangeFlags::RESTART);
prev = settings.ems_bus_id;
settings.ems_bus_id = root["ems_bus_id"] | EMSESP_DEFAULT_EMS_BUS_ID;
check_flag(prev, settings.ems_bus_id, ChangeFlags::RESTART);
prev = settings.low_clock;
settings.low_clock = root["low_clock"] | false;
check_flag(prev, settings.low_clock, ChangeFlags::RESTART);
prev = settings.master_thermostat;
settings.master_thermostat = root["master_thermostat"] | EMSESP_DEFAULT_MASTER_THERMOSTAT;
settings.low_clock = root["low_clock"] | false;
settings.phy_type = root["phy_type"] | default_phy_type; // use whatever came from the board profile
check_flag(prev, settings.master_thermostat, ChangeFlags::RESTART);
// use whatever came from the board profile
prev = settings.phy_type;
settings.phy_type = root["phy_type"] | default_phy_type;
check_flag(prev, settings.phy_type, ChangeFlags::RESTART);
prev = settings.eth_power;
settings.eth_power = root["eth_power"] | default_eth_power;
check_flag(prev, settings.eth_power, ChangeFlags::RESTART);
prev = settings.eth_phy_addr;
settings.eth_phy_addr = root["eth_phy_addr"] | default_eth_phy_addr;
check_flag(prev, settings.eth_phy_addr, ChangeFlags::RESTART);
prev = settings.eth_clock_mode;
settings.eth_clock_mode = root["eth_clock_mode"] | default_eth_clock_mode;
check_flag(prev, settings.eth_clock_mode, ChangeFlags::RESTART);
// without checks...
settings.trace_raw = root["trace_raw"] | EMSESP_DEFAULT_TRACELOG_RAW;
EMSESP::trace_raw(settings.trace_raw);
@@ -197,27 +211,25 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings)
settings.notoken_api = root["notoken_api"] | EMSESP_DEFAULT_NOTOKEN_API;
settings.solar_maxflow = root["solar_maxflow"] | EMSESP_DEFAULT_SOLAR_MAXFLOW;
settings.dallas_format = root["dallas_format"] | EMSESP_DEFAULT_DALLAS_FORMAT;
EMSESP::dallassensor_.dallas_format(settings.dallas_format);
settings.fahrenheit = root["fahrenheit"] | false;
EMSESP::system_.fahrenheit(settings.fahrenheit);
settings.readonly_mode = root["readonly_mode"] | false;
EMSESP::system_.readonly_mode(settings.readonly_mode);
settings.bool_format = root["bool_format"] | EMSESP_DEFAULT_BOOL_FORMAT;
EMSESP::bool_format(settings.bool_format);
EMSESP::system_.bool_format(settings.bool_format);
settings.enum_format = root["enum_format"] | EMSESP_DEFAULT_ENUM_FORMAT;
EMSESP::enum_format(settings.enum_format);
EMSESP::system_.enum_format(settings.enum_format);
settings.weblog_level = root["weblog_level"] | EMSESP_DEFAULT_WEBLOG_LEVEL;
settings.weblog_buffer = root["weblog_buffer"] | EMSESP_DEFAULT_WEBLOG_BUFFER;
settings.weblog_compact = root["weblog_compact"] | EMSESP_DEFAULT_WEBLOG_COMPACT;
for (uint8_t i = 0; i < MAX_NUM_SENSOR_NAMES; i++) {
char buf[20];
snprintf(buf, sizeof(buf), "sensor_id%d", i);
settings.sensor[i].id = root[buf] | EMSESP_DEFAULT_SENSOR_NAME;
snprintf(buf, sizeof(buf), "sensor_name%d", i);
settings.sensor[i].name = root[buf] | EMSESP_DEFAULT_SENSOR_NAME;
snprintf(buf, sizeof(buf), "sensor_offset%d", i);
settings.sensor[i].offset = root[buf] | 0;
// save the settings
if (flags_ == WebSettings::ChangeFlags::RESTART) {
return StateUpdateResult::CHANGED_RESTART; // tell WebUI that a restart is needed
}
return StateUpdateResult::CHANGED;
@@ -243,7 +255,7 @@ void WebSettingsService::onUpdate() {
}
if (WebSettings::has_flags(WebSettings::ChangeFlags::ADC)) {
EMSESP::system_.adc_init(true); // reload settings
EMSESP::analogsensor_.start();
}
if (WebSettings::has_flags(WebSettings::ChangeFlags::BUTTON)) {
@@ -270,22 +282,20 @@ void WebSettingsService::board_profile(AsyncWebServerRequest * request, JsonVari
if (json.is<JsonObject>()) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_MEDIUM);
JsonObject root = response->getRoot();
if (json.containsKey("code")) {
String board_profile = json["code"];
std::vector<uint8_t> data; // led, dallas, rx, tx, button
// check for valid board
if (System::load_board_profile(data, board_profile.c_str())) {
root["led_gpio"] = data[0];
root["dallas_gpio"] = data[1];
root["rx_gpio"] = data[2];
root["tx_gpio"] = data[3];
root["pbutton_gpio"] = data[4];
root["phy_type"] = data[5];
} else {
AsyncWebServerResponse * response = request->beginResponse(200);
request->send(response);
return;
}
if (json.containsKey("board_profile")) {
String board_profile = json["board_profile"];
std::vector<int8_t> data; // led, dallas, rx, tx, button, phy_type, eth_power, eth_phy_addr, eth_clock_mode
(void)System::load_board_profile(data, board_profile.c_str());
root["led_gpio"] = data[0];
root["dallas_gpio"] = data[1];
root["rx_gpio"] = data[2];
root["tx_gpio"] = data[3];
root["pbutton_gpio"] = data[4];
root["phy_type"] = data[5];
root["eth_power"] = data[6];
root["eth_phy_addr"] = data[7];
root["eth_clock_mode"] = data[8];
response->setLength();
request->send(response);

View File

@@ -16,25 +16,17 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef WebSettingsConfig_h
#define WebSettingsConfig_h
#include <HttpEndpoint.h>
#include <FSPersistence.h>
#ifndef WebSettingsService_h
#define WebSettingsService_h
#include "../default_settings.h"
#define EMSESP_SETTINGS_FILE "/config/emsespSettings.json"
#define EMSESP_SETTINGS_SERVICE_PATH "/rest/emsespSettings"
#define EMSESP_SETTINGS_SERVICE_PATH "/rest/settings"
#define EMSESP_BOARD_PROFILE_SERVICE_PATH "/rest/boardProfile"
#define MAX_NUM_SENSOR_NAMES 20
namespace emsesp {
enum { BOOL_FORMAT_ONOFF = 1, BOOL_FORMAT_ONOFF_CAP, BOOL_FORMAT_TRUEFALSE, BOOL_FORMAT_10 }; // matches Web UI settings
enum { ENUM_FORMAT_TEXT = 1, ENUM_FORMAT_NUMBER }; // matches Web UI settings
class WebSettings {
public:
uint8_t tx_mode;
@@ -55,38 +47,39 @@ class WebSettings {
uint8_t led_gpio;
bool hide_led;
bool low_clock;
bool telnet_enabled;
bool notoken_api;
bool readonly_mode;
bool analog_enabled;
uint8_t pbutton_gpio;
uint8_t solar_maxflow;
String board_profile;
uint8_t phy_type;
uint8_t dallas_format;
uint8_t bool_format;
uint8_t enum_format;
int8_t weblog_level;
uint8_t weblog_buffer;
bool weblog_compact;
bool fahrenheit;
struct {
String id;
String name;
int16_t offset;
} sensor[MAX_NUM_SENSOR_NAMES];
uint8_t phy_type;
int8_t eth_power; // -1 means disabled
uint8_t eth_phy_addr;
uint8_t eth_clock_mode;
static void read(WebSettings & settings, JsonObject & root);
static StateUpdateResult update(JsonObject & root, WebSettings & settings);
enum ChangeFlags : uint8_t {
NONE = 0,
UART = (1 << 0), // 1
SYSLOG = (1 << 1), // 2
ADC = (1 << 2), // 4
DALLAS = (1 << 3), // 8
SHOWER = (1 << 4), // 16
LED = (1 << 5), // 32
BUTTON = (1 << 6) // 64
NONE = 0,
UART = (1 << 0), // 1
SYSLOG = (1 << 1), // 2
ADC = (1 << 2), // 4 - analog
DALLAS = (1 << 3), // 8
SHOWER = (1 << 4), // 16
LED = (1 << 5), // 32
BUTTON = (1 << 6), // 64
RESTART = 0xFF
};

View File

@@ -34,13 +34,13 @@ WebStatusService::WebStatusService(AsyncWebServer * server, SecurityManager * se
void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
switch (event) {
case SYSTEM_EVENT_STA_DISCONNECTED:
EMSESP::logger().info(F("WiFi Disconnected. Reason code=%d"), info.disconnected.reason);
EMSESP::logger().info(F("WiFi disconnected. Reason code=%d"), info.disconnected.reason);
WiFi.disconnect(true);
break;
case SYSTEM_EVENT_STA_GOT_IP:
#ifndef EMSESP_STANDALONE
EMSESP::logger().info(F("WiFi Connected with IP=%s, hostname=%s"), WiFi.localIP().toString().c_str(), WiFi.getHostname());
EMSESP::logger().info(F("WiFi connected with IP=%s, hostname=%s"), WiFi.localIP().toString().c_str(), WiFi.getHostname());
#endif
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) {
if (!networkSettings.enableIPv6) {
@@ -52,7 +52,7 @@ void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
break;
case SYSTEM_EVENT_ETH_START:
EMSESP::logger().info(F("Ethernet initialized"));
// EMSESP::logger().info(F("Ethernet initialized"));
ETH.setHostname(EMSESP::system_.hostname().c_str());
// configure for static IP
@@ -68,7 +68,7 @@ void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
// prevent double calls
if (!EMSESP::system_.ethernet_connected()) {
#ifndef EMSESP_STANDALONE
EMSESP::logger().info(F("Ethernet Connected with IP=%s, speed %d Mbps"), ETH.localIP().toString().c_str(), ETH.linkSpeed());
EMSESP::logger().info(F("Ethernet connected with IP=%s, speed %d Mbps"), ETH.localIP().toString().c_str(), ETH.linkSpeed());
#endif
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) {
if (!networkSettings.enableIPv6) {
@@ -82,12 +82,12 @@ void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
break;
case SYSTEM_EVENT_ETH_DISCONNECTED:
EMSESP::logger().info(F("Ethernet Disconnected"));
EMSESP::logger().info(F("Ethernet disconnected"));
EMSESP::system_.ethernet_connected(false);
break;
case SYSTEM_EVENT_ETH_STOP:
EMSESP::logger().info(F("Ethernet Stopped"));
EMSESP::logger().info(F("Ethernet stopped"));
EMSESP::system_.ethernet_connected(false);
break;
@@ -110,9 +110,9 @@ void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
case SYSTEM_EVENT_GOT_IP6:
if (EMSESP::system_.ethernet_connected()) {
EMSESP::logger().info(F("Ethernet Connected with IP=%s, speed %d Mbps"), ETH.localIPv6().toString().c_str(), ETH.linkSpeed());
EMSESP::logger().info(F("Ethernet connected with IP=%s, speed %d Mbps"), ETH.localIPv6().toString().c_str(), ETH.linkSpeed());
} else {
EMSESP::logger().info(F("WiFi Connected with IP=%s, hostname=%s"), WiFi.localIPv6().toString().c_str(), WiFi.getHostname());
EMSESP::logger().info(F("WiFi connected with IP=%s, hostname=%s"), WiFi.localIPv6().toString().c_str(), WiFi.getHostname());
}
EMSESP::system_.send_heartbeat();
EMSESP::system_.syslog_start();
@@ -129,11 +129,34 @@ void WebStatusService::webStatusService(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_MEDIUM_DYN);
JsonObject root = response->getRoot();
root["status"] = EMSESP::bus_status(); // 0, 1 or 2
root["rx_received"] = EMSESP::rxservice_.telegram_count();
root["tx_sent"] = EMSESP::txservice_.telegram_read_count() + EMSESP::txservice_.telegram_write_count();
root["rx_quality"] = EMSESP::rxservice_.quality();
root["tx_quality"] = EMSESP::txservice_.quality();
root["status"] = EMSESP::bus_status(); // 0, 1 or 2
root["num_devices"] = EMSESP::count_devices(); // excluding Controller
root["num_sensors"] = EMSESP::dallassensor_.no_sensors();
root["num_analogs"] = EMSESP::analogsensor_.no_sensors();
root["tx_mode"] = EMSESP::txservice_.tx_mode();
root["rx_received"] = EMSESP::rxservice_.telegram_count();
root["tx_reads"] = EMSESP::txservice_.telegram_read_count();
root["tx_writes"] = EMSESP::txservice_.telegram_write_count();
root["rx_quality"] = EMSESP::rxservice_.quality();
root["tx_read_quality"] = EMSESP::txservice_.read_quality();
root["tx_write_quality"] = EMSESP::txservice_.write_quality();
root["rx_fails"] = EMSESP::rxservice_.telegram_error_count();
root["tx_read_fails"] = EMSESP::txservice_.telegram_read_fail_count();
root["tx_write_fails"] = EMSESP::txservice_.telegram_write_fail_count();
root["sensor_fails"] = EMSESP::dallassensor_.fails();
root["sensor_reads"] = EMSESP::dallassensor_.reads();
root["sensor_quality"] = EMSESP::dallassensor_.reads() == 0 ? 100 : 100 - (uint8_t)((100 * EMSESP::dallassensor_.fails()) / EMSESP::dallassensor_.reads());
root["analog_fails"] = EMSESP::analogsensor_.fails();
root["analog_reads"] = EMSESP::analogsensor_.reads();
root["analog_quality"] = EMSESP::analogsensor_.reads() == 0 ? 100 : 100 - (uint8_t)((100 * EMSESP::analogsensor_.fails()) / EMSESP::analogsensor_.reads());
root["mqtt_fails"] = Mqtt::publish_fails();
root["mqtt_count"] = Mqtt::publish_count();
root["mqtt_quality"] = Mqtt::publish_count() == 0 ? 100 : 100 - (Mqtt::publish_fails() * 100) / (Mqtt::publish_count() + Mqtt::publish_fails());
root["api_calls"] = WebAPIService::api_count(); // + WebAPIService::api_fails();
root["api_fails"] = WebAPIService::api_fails();
root["api_quality"] =
WebAPIService::api_count() == 0 ? 100 : 100 - (WebAPIService::api_fails() * 100) / (WebAPIService::api_count() + WebAPIService::api_fails());
root["uptime"] = EMSbus::bus_uptime();
response->setLength();
request->send(response);

View File

@@ -19,15 +19,9 @@
#ifndef WebStatusService_h
#define WebStatusService_h
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#include <AsyncMqttClient.h>
#include <ESPmDNS.h>
#define EMSESP_STATUS_SERVICE_PATH "/rest/emsespStatus"
#define EMSESP_STATUS_SERVICE_PATH "/rest/status"
namespace emsesp {