Merge remote-tracking branch 'origin/dev' into main

This commit is contained in:
Proddy
2022-05-23 21:20:36 +02:00
556 changed files with 38877 additions and 40829 deletions

610
src/analogsensor.cpp Normal file
View File

@@ -0,0 +1,610 @@
/*
* 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 1.5V
}
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));
Command::add(
EMSdevice::DeviceType::ANALOGSENSOR,
F_(setvalue),
[&](const char * value, const int8_t id) { return command_setvalue(value, id); },
F("set io value"),
CommandFlag::ADMIN_ONLY);
Command::add(
EMSdevice::DeviceType::ANALOGSENSOR,
F_(commands),
[&](const char * value, const int8_t id, JsonObject & output) { return command_commands(value, id, output); },
F_(commands_cmd));
Mqtt::subscribe(EMSdevice::DeviceType::ANALOGSENSOR, "analogsensor/#", nullptr); // use empty function callback
}
// 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 it = sensors_.begin();
for (auto & sensor_ : sensors_) {
// update existing sensors
bool found = false;
for (const auto & sensor : settings.analogCustomizations) { //search customlist
if (sensor_.gpio() == sensor.gpio) {
// for output sensors set value to new start-value
if ((sensor.type == AnalogType::COUNTER || sensor.type >= AnalogType::DIGITAL_OUT)
&& (sensor_.type() != sensor.type || sensor_.offset() != sensor.offset || sensor_.factor() != sensor.factor)) {
sensor_.set_value(sensor.offset);
}
sensor_.set_name(sensor.name);
sensor_.set_type(sensor.type);
sensor_.set_offset(sensor.offset);
sensor_.set_factor(sensor.factor);
sensor_.set_uom(sensor.uom);
sensor_.ha_registered = false;
found = true;
}
}
if (!found) {
sensors_.erase(it);
}
it++;
}
// add new sensors from list
for (const auto & sensor : settings.analogCustomizations) {
bool found = false;
for (const auto & sensor_ : sensors_) {
if (sensor_.gpio() == sensor.gpio) {
found = true;
}
}
if (!found) {
sensors_.emplace_back(sensor.gpio, sensor.name, sensor.offset, sensor.factor, sensor.uom, sensor.type);
sensors_.back().ha_registered = false; // this will trigger recrate of the HA config
if (sensor.type == AnalogType::COUNTER || sensor.type >= AnalogType::DIGITAL_OUT) {
sensors_.back().set_value(sensor.offset);
} else {
sensors_.back().set_value(0); // reset value only for new sensors
}
}
}
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.gpio());
// 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.gpio());
pinMode(sensor.gpio(), INPUT_PULLUP);
if (sensor.gpio() == 25 || sensor.gpio() == 26) {
dacWrite(sensor.gpio(), 255);
}
sensor.polltime_ = 0;
sensor.poll_ = digitalRead(sensor.gpio());
publish_sensor(sensor);
} else if (sensor.type() == AnalogType::TIMER || sensor.type() == AnalogType::RATE) {
LOG_DEBUG(F("Adding analog Timer/Rate sensor on GPIO%d"), sensor.gpio());
pinMode(sensor.gpio(), INPUT_PULLUP);
sensor.polltime_ = uuid::get_uptime();
sensor.last_polltime_ = uuid::get_uptime();
sensor.poll_ = digitalRead(sensor.gpio());
sensor.set_offset(0);
sensor.set_value(0);
publish_sensor(sensor);
} else if (sensor.type() == AnalogType::DIGITAL_IN) {
LOG_DEBUG(F("Adding analog Read sensor on GPIO%d"), sensor.gpio());
pinMode(sensor.gpio(), INPUT_PULLUP);
sensor.set_value(digitalRead(sensor.gpio())); // initial value
sensor.set_uom(0); // no uom, just for safe measures
sensor.polltime_ = 0;
sensor.poll_ = digitalRead(sensor.gpio());
publish_sensor(sensor);
} else if (sensor.type() == AnalogType::DIGITAL_OUT) {
LOG_DEBUG(F("Adding analog Write sensor on GPIO%d"), sensor.gpio());
pinMode(sensor.gpio(), OUTPUT);
if (sensor.gpio() == 25 || sensor.gpio() == 26) {
if (sensor.offset() > 255) {
sensor.set_offset(255);
} else if (sensor.offset() < 0) {
sensor.set_offset(0);
}
dacWrite(sensor.gpio(), sensor.offset());
sensor.set_value(sensor.offset());
} else {
digitalWrite(sensor.gpio(), sensor.offset() > 0 ? 1 : 0);
sensor.set_value(digitalRead(sensor.gpio()));
}
sensor.set_uom(0); // no uom, just for safe measures
publish_sensor(sensor);
} else if (sensor.type() >= AnalogType::PWM_0) {
LOG_DEBUG(F("Adding PWM output sensor on GPIO%d"), sensor.gpio());
uint channel = sensor.type() - AnalogType::PWM_0;
ledcSetup(channel, sensor.factor(), 13);
ledcAttachPin(sensor.gpio(), channel);
if (sensor.offset() > 100) {
sensor.set_offset(100);
} else if (sensor.offset() < 0) {
sensor.set_offset(0);
}
ledcWrite(channel, (uint32_t)(sensor.offset() * 8191 / 100));
sensor.set_value(sensor.offset());
sensor.set_uom(DeviceValueUOM::PERCENT);
publish_sensor(sensor);
}
}
}
// measure input sensors and moving average adc
void AnalogSensor::measure() {
static uint32_t measure_last_ = 0;
// measure interval 500ms for adc 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.gpio()); // 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 with debounce
// go through the list of digital sensors
for (auto & sensor : sensors_) {
if (sensor.type() == AnalogType::DIGITAL_IN || sensor.type() == AnalogType::COUNTER || sensor.type() == AnalogType::TIMER
|| sensor.type() == AnalogType::RATE) {
auto old_value = sensor.value(); // remember current value before reading
auto current_reading = digitalRead(sensor.gpio());
if (sensor.poll_ != current_reading) { // check for pinchange
sensor.polltime_ = uuid::get_uptime(); // remember time of pinchange
sensor.poll_ = current_reading;
}
// debounce and check for real pinchange
if (uuid::get_uptime() - sensor.polltime_ >= 15 && sensor.poll_ != sensor.last_reading_) {
sensor.last_reading_ = sensor.poll_;
if (sensor.type() == AnalogType::DIGITAL_IN) {
sensor.set_value(sensor.poll_);
} else if (!sensor.poll_) { // falling edge
if (sensor.type() == AnalogType::COUNTER) {
sensor.set_value(old_value + sensor.factor());
} else if (sensor.type() == AnalogType::RATE) { // dafault uom: Hz (1/sec) with factor 1
sensor.set_value(sensor.factor() * 1000 / (sensor.polltime_ - sensor.last_polltime_));
} else if (sensor.type() == AnalogType::TIMER) { // default seconds with factor 1
sensor.set_value(sensor.factor() * (sensor.polltime_ - sensor.last_polltime_) / 1000);
}
sensor.last_polltime_ = sensor.polltime_;
}
}
// 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 gpio, const std::string & name, float 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.gpio == gpio) {
found_sensor = true; // found the record
// see if it's marked for deletion
if (type == AnalogType::MARK_DELETED) {
LOG_DEBUG(F("Removing analog sensor GPIO %d"), gpio);
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 GPIO %d"), gpio);
}
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(gpio); // the GPIO
}
// we didn't find it, it's new, so create and store it
if (!found_sensor) {
EMSESP::webCustomizationService.update(
[&](WebCustomization & settings) {
auto newSensor = AnalogCustomization();
newSensor.gpio = gpio;
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 GPIO %d"), gpio);
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(const Sensor & sensor) const {
if (Mqtt::publish_single()) {
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
if (Mqtt::publish_single2cmd()) {
snprintf(topic, sizeof(topic), "%s/%s", read_flash_string(F_(analogsensor)).c_str(), sensor.name().c_str());
} else {
snprintf(topic, sizeof(topic), "%s%s/%s", read_flash_string(F_(analogsensor)).c_str(), "_data", 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 gpio) const {
if (!Mqtt::ha_enabled()) {
return;
}
#ifdef EMSESP_DEBUG
LOG_DEBUG(F("Removing HA config for analog sensor GPIO %d"), gpio);
#endif
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "sensor/%s/analogsensor_%d/config", Mqtt::base().c_str(), gpio);
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);
}
}
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.gpio()));
dataSensor["name"] = sensor.name();
switch (sensor.type()) {
case AnalogType::COUNTER:
case AnalogType::TIMER:
case AnalogType::RATE:
case AnalogType::ADC:
case AnalogType::PWM_0:
case AnalogType::PWM_1:
case AnalogType::PWM_2:
dataSensor["value"] = sensor.value(); // float
break;
default:
dataSensor["value"] = (uint8_t)sensor.value(); // convert to char for 1 or 0
break;
}
// create HA config
if (Mqtt::ha_enabled() && (!sensor.ha_registered || force)) {
LOG_DEBUG(F("Recreating HA config for analog sensor GPIO %d"), sensor.gpio());
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.gpio());
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.gpio());
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.gpio());
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) const {
// make a copy of the string command for parsing
char command_s[30];
strlcpy(command_s, cmd, sizeof(command_s));
char * attribute_s = nullptr;
// check specific attribute to fetch instead of the complete record
char * breakp = strchr(command_s, '/');
if (breakp) {
*breakp = '\0';
attribute_s = breakp + 1;
}
for (const auto & sensor : sensors_) {
if (strcmp(command_s, sensor.name().c_str()) == 0) {
output["gpio"] = sensor.gpio();
output["name"] = sensor.name();
output["type"] = F_(number);
output["analog"] = FL_(enum_sensortype)[sensor.type()];
output["uom"] = EMSdevice::uom_to_string(sensor.uom());
output["offset"] = sensor.offset();
output["factor"] = sensor.factor();
output["value"] = sensor.value();
// if we're filtering on an attribute, go find it
if (attribute_s) {
if (output.containsKey(attribute_s)) {
JsonVariant data = output[attribute_s];
output.clear();
output["api_data"] = data;
return true;
} else {
char error[100];
snprintf(error, sizeof(error), "cannot find attribute %s in entity %s", attribute_s, command_s);
output.clear();
output["message"] = error;
return false;
}
}
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) const {
if (sensors_.empty()) {
return false;
}
for (const auto & sensor : sensors_) {
if (id == -1) { // show number and id
JsonObject dataSensor = output.createNestedObject(sensor.name());
dataSensor["gpio"] = sensor.gpio();
dataSensor["type"] = F_(number);
dataSensor["analog"] = FL_(enum_sensortype)[sensor.type()];
if (sensor.type() == AnalogType::ADC) {
dataSensor["uom"] = EMSdevice::uom_to_string(sensor.uom());
dataSensor["offset"] = sensor.offset();
dataSensor["factor"] = sensor.factor();
} else if (sensor.type() == AnalogType::COUNTER) {
dataSensor["uom"] = EMSdevice::uom_to_string(sensor.uom());
dataSensor["start_value"] = sensor.offset();
dataSensor["factor"] = sensor.factor();
} else if (sensor.type() == AnalogType::TIMER || sensor.type() == AnalogType::RATE) {
dataSensor["factor"] = sensor.factor();
} else if (sensor.type() >= AnalogType::PWM_0) {
dataSensor["uom"] = EMSdevice::uom_to_string(sensor.uom());
dataSensor["frequency"] = 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 gpio, const std::string & name, const float offset, const float factor, const uint8_t uom, const int8_t type)
: gpio_(gpio)
, 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", gpio_);
return name;
}
return name_;
}
// set the counter value, id is gpio-no
bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) {
float val;
if (!Helpers::value2float(value, val)) {
return false;
}
for (auto & sensor : sensors_) {
if (sensor.gpio() == gpio) {
if (sensor.type() == AnalogType::COUNTER) {
if (val < 0 || value[0] == '+') { // sign corrects values
sensor.set_offset(sensor.value() + val);
sensor.set_value(sensor.value() + val);
} else { // positive values are set
sensor.set_offset(val);
sensor.set_value(val);
}
publish_sensor(sensor);
return true;
} else if (sensor.type() == AnalogType::ADC) {
sensor.set_offset(val);
return true;
} else if (sensor.type() == AnalogType::DIGITAL_OUT) {
uint8_t v = val;
if (sensor.gpio() == 25 || sensor.gpio() == 26) {
sensor.set_offset(v);
sensor.set_value(v);
pinMode(sensor.gpio(), OUTPUT);
dacWrite(sensor.gpio(), sensor.offset());
publish_sensor(sensor);
return true;
} else if (v == 0 || v == 1) {
sensor.set_offset(v);
sensor.set_value(v);
pinMode(sensor.gpio(), OUTPUT);
digitalWrite(sensor.gpio(), sensor.offset() > 0 ? 1 : 0);
publish_sensor(sensor);
return true;
}
} else if (sensor.type() >= AnalogType::PWM_0) {
uint8_t channel = sensor.type() - AnalogType::PWM_0;
if (val > 100) {
val = 100;
} else if (val < 0) {
val = 0;
}
sensor.set_offset(val);
sensor.set_value(val);
ledcWrite(channel, (uint32_t)(val * 8191 / 100));
publish_sensor(sensor);
return true;
}
}
}
return false;
}
// list commands
bool AnalogSensor::command_commands(const char * value, const int8_t id, JsonObject & output) {
return Command::list(EMSdevice::DeviceType::ANALOGSENSOR, output);
}
// hard coded tests
#ifdef EMSESP_DEBUG
void AnalogSensor::test() {
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

189
src/analogsensor.h Normal file
View File

@@ -0,0 +1,189 @@
/*
* 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 gpio, const std::string & name, const float offset, const float factor, const uint8_t uom, const int8_t type);
~Sensor() = default;
void set_offset(const float offset) {
offset_ = offset;
}
std::string name() const;
void set_name(const std::string & name) {
name_ = name;
}
uint8_t gpio() const {
return gpio_;
}
float value() const {
return value_;
}
void set_value(float value) {
value_ = value;
}
float factor() const {
return factor_;
}
void set_factor(float factor) {
factor_ = factor;
}
float 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
uint16_t count_ = 0; // counter raw counts
uint32_t polltime_ = 0; // digital IO & COUNTER debounce time
int poll_ = 0;
uint32_t last_polltime_ = 0; // for timer
private:
uint8_t gpio_;
std::string name_;
float 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,
COUNTER,
ADC,
TIMER,
RATE,
DIGITAL_OUT,
PWM_0,
PWM_1,
PWM_2
};
void start();
void loop();
void publish_sensor(const Sensor & sensor) const;
void publish_values(const bool force);
void reload();
bool updated_values();
// return back reference to the sensor list, used by other classes
std::vector<Sensor> sensors() const {
return sensors_;
}
uint32_t reads() const {
return sensorreads_;
}
uint32_t fails() const {
return sensorfails_;
}
bool analog_enabled() const {
return (analog_enabled_);
}
bool have_sensors() const {
return (!sensors_.empty());
}
size_t no_sensors() const {
return sensors_.size();
}
bool update(uint8_t gpio, const std::string & name, float offset, float factor, uint8_t uom, int8_t type);
bool get_value_info(JsonObject & output, const char * cmd, const int8_t id) const;
#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) const;
bool command_setvalue(const char * value, const int8_t gpio);
void measure();
bool command_info(const char * value, const int8_t id, JsonObject & output) const;
bool command_commands(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

@@ -39,20 +39,22 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
}
// check first if it's from API, if so strip the "api/"
if ((p.paths().front() == "api")) {
if (p.paths().front() == "api") {
p.paths().erase(p.paths().begin());
} else {
// not /api, so must be MQTT path. Check for base and remove it.
if (!strncmp(path, Mqtt::base().c_str(), Mqtt::base().length())) {
char new_path[Mqtt::MQTT_TOPIC_MAX_SIZE];
strncpy(new_path, path, sizeof(new_path));
strlcpy(new_path, path, sizeof(new_path));
p.parse(new_path + Mqtt::base().length() + 1); // re-parse the stripped path
} else {
return message(CommandRet::ERROR, "unrecognized path", output); // error
}
}
#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!
@@ -84,15 +86,20 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
return message(CommandRet::ERROR, "unknown device", output);
}
// the next value on the path should be the command
// the next value on the path should be the command or entity name
const char * command_p = nullptr;
if (num_paths == 2) {
command_p = p.paths()[1].c_str();
} else if (num_paths >= 3) {
} else if (num_paths == 3) {
// concatenate the path into one string as it could be in the format 'hc/XXX'
char command[50];
snprintf(command, sizeof(command), "%s/%s", p.paths()[1].c_str(), p.paths()[2].c_str());
command_p = command;
} else if (num_paths > 3) {
// concatenate the path into one string as it could be in the format 'hc/XXX/attribute'
char command[50];
snprintf(command, sizeof(command), "%s/%s/%s", p.paths()[1].c_str(), p.paths()[2].c_str(), p.paths()[3].c_str());
command_p = command;
} else {
// take it from the JSON
if (input.containsKey("entity")) {
@@ -102,13 +109,13 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
}
}
// some commands may be prefixed with hc. or wwc. so extract these if they exist
// some commands may be prefixed with hc. wwc. or hc/ or wwc/ so extract these if they exist
// parse_command_string returns the extracted command
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
if (num_paths < 3) {
// default to 'info' for SYSTEM, DALLASENSOR and ANALOGSENSOR, the other devices to 'values' for shortname version
if (num_paths < (id_n > 0 ? 4 : 3)) {
if (device_type < EMSdevice::DeviceType::BOILER) {
command_p = "info";
} else {
@@ -126,7 +133,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,10 +153,12 @@ 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);
return_code = Command::call(device_type, command_p, Helpers::render_value(data_str, data.as<float>(), 2), is_admin, id_n, output);
} else if (data.is<bool>()) {
return_code = Command::call(device_type, command_p, data.as<bool>() ? "1" : "0", is_admin, id_n, output);
} else if (data.isNull()) {
return_code = Command::call(device_type, command_p, "", is_admin, id_n, output); // empty, will do a query instead
} else {
@@ -158,22 +167,19 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
return return_code;
}
const std::string Command::return_code_string(const uint8_t return_code) {
std::string Command::return_code_string(const uint8_t return_code) {
switch (return_code) {
case CommandRet::ERROR:
return read_flash_string(F("Error"));
break;
case CommandRet::OK:
return read_flash_string(F("OK"));
break;
case CommandRet::NOT_FOUND:
return read_flash_string(F("Not Found"));
break;
case CommandRet::NOT_ALLOWED:
return read_flash_string(F("Not Authorized"));
break;
case CommandRet::FAIL:
return read_flash_string(F("Failed"));
default:
break;
}
char s[4];
@@ -187,39 +193,33 @@ const char * Command::parse_command_string(const char * command, int8_t & id) {
return nullptr;
}
// make a copy of the string command for parsing
char command_s[100];
strncpy(command_s, command, sizeof(command_s));
// look for a delimeter and split the string
char * p = command_s;
char * breakp = strchr(p, '.');
if (!breakp) {
p = command_s; // reset and look for /
breakp = strchr(p, '/');
if (!breakp) {
p = command_s; // reset and look for _
breakp = strchr(p, '_');
if (!breakp) {
return command;
}
}
// check prefix and valid number range, also check 'id'
if (!strncmp(command, "hc", 2) && command[2] >= '1' && command[2] <= '8') {
id = command[2] - '0';
command += 3;
} else if (!strncmp(command, "wwc", 3) && command[3] == '1' && command[4] == '0') {
id = 19;
command += 5;
} else if (!strncmp(command, "wwc", 3) && command[3] >= '1' && command[3] <= '9') {
id = command[3] - '0' + 8;
command += 4;
} else if (!strncmp(command, "id", 2) && command[2] == '1' && command[3] >= '0' && command[3] <= '9') {
id = command[3] - '0' + 10;
command += 4;
} else if (!strncmp(command, "id", 2) && command[2] >= '1' && command[2] <= '9') {
id = command[2] - '0';
command += 3;
}
// extract the hc or wwc number
uint8_t start_pos = breakp - p + 1;
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
} else {
#if defined(EMSESP_DEBUG)
LOG_DEBUG(F("[DEBUG] Command parse error, unknown hc/wwc in %s"), command_s);
#endif
// remove separator
if (command[0] == '/' || command[0] == '.' || command[0] == '_') {
command++;
}
// return null for empty command
if (command[0] == '\0') {
return nullptr;
}
return (command + start_pos);
return command;
}
// calls a command directly
@@ -243,44 +243,49 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char *
// see if there is a command registered
auto cf = find_command(device_type, cmd);
// check if its a call to and end-point to a device, i.e. has no value
// check if its a call to and end-point to a device
// 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))) {
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 attributes"), 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) || (strlen(value) == 0)) {
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_) {
if (cf->cmdfunction_ && !EMSESP::cmd_is_readonly(device_type, cmd, id)) {
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);
}
@@ -309,14 +314,13 @@ void Command::add(const uint8_t device_type, const __FlashStringHelper * cmd, co
}
// add a command to the list, which does return a json object as output
// flag is fixed to MqttSubFlag::MQTT_SUB_FLAG_NOSUB so there will be no topic subscribed to this
void Command::add(const uint8_t device_type, const __FlashStringHelper * cmd, const cmd_json_function_p cb, const __FlashStringHelper * description, uint8_t flags) {
// if the command already exists for that device type don't add it
if (find_command(device_type, read_flash_string(cmd).c_str()) != nullptr) {
return;
}
cmdfunctions_.emplace_back(device_type, (CommandFlag::MQTT_SUB_FLAG_NOSUB | flags), cmd, nullptr, cb, description); // callback for json is included
cmdfunctions_.emplace_back(device_type, flags, cmd, nullptr, cb, description); // callback for json is included
}
// see if a command exists for that device type
@@ -358,7 +362,7 @@ bool Command::list(const uint8_t device_type, JsonObject & output) {
}
sorted_cmds.sort();
for (auto & cl : sorted_cmds) {
for (const auto & cl : sorted_cmds) {
for (const auto & cf : cmdfunctions_) {
if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN) && cf.description_ && (cl == read_flash_string(cf.cmd_))) {
output[cl] = cf.description_;
@@ -387,7 +391,7 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
// if not in verbose mode, just print them on a single line
if (!verbose) {
for (auto & cl : sorted_cmds) {
for (const auto & cl : sorted_cmds) {
shell.print(cl);
shell.print(" ");
}
@@ -397,7 +401,7 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
// verbose mode
shell.println();
for (auto & cl : sorted_cmds) {
for (const auto & cl : sorted_cmds) {
// find and print the description
for (const auto & cf : cmdfunctions_) {
if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN) && cf.description_ && (cl == read_flash_string(cf.cmd_))) {
@@ -417,7 +421,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,11 +451,15 @@ 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) {
if ((emsdevice) && (emsdevice->device_type() == device_type)) {
if (emsdevice && (emsdevice->device_type() == device_type)) {
// device found, now see if it has any commands
for (const auto & cf : cmdfunctions_) {
if (cf.device_type_ == device_type) {
@@ -464,16 +472,20 @@ 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) {
if ((emsdevice) && (emsdevice->device_type() == device_class.first) && (device_has_commands(device_class.first))) {
if (emsdevice && (emsdevice->device_type() == device_class.first) && (device_has_commands(device_class.first))) {
shell.printf("%s ", EMSdevice::device_type_2_device_name(device_class.first).c_str());
break; // we only want to show one (not multiple of the same device types)
}
@@ -494,14 +506,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()) {
@@ -519,7 +538,7 @@ void Command::show_all(uuid::console::Shell & shell) {
// e.g. //one/two////three/// becomes /one/two/three
std::string SUrlParser::path() {
std::string s = "/"; // set up the beginning slash
for (std::string & f : m_folders) {
for (const std::string & f : m_folders) {
s += f;
s += "/";
}
@@ -532,6 +551,14 @@ SUrlParser::SUrlParser(const char * uri) {
}
bool SUrlParser::parse(const char * uri) {
if (uri == nullptr) {
return false;
}
if (*uri == '\0') {
return false;
}
m_folders.clear();
m_keysvalues.clear();
enum Type { begin, folder, param, value };
@@ -541,54 +568,53 @@ bool SUrlParser::parse(const char * uri) {
enum Type t = Type::begin;
std::string last_param;
if (c != nullptr || *c != '\0') {
do {
if (*c == '/') {
if (s.length() > 0) {
m_folders.push_back(s);
s.clear();
}
t = Type::folder;
} else if (*c == '?' && (t == Type::folder || t == Type::begin)) {
if (s.length() > 0) {
m_folders.push_back(s);
s.clear();
}
t = Type::param;
} else if (*c == '=' && (t == Type::param || t == Type::begin)) {
do {
if (*c == '/') {
if (s.length() > 0) {
m_folders.push_back(s);
s.clear();
}
t = Type::folder;
} else if (*c == '?' && (t == Type::folder || t == Type::begin)) {
if (s.length() > 0) {
m_folders.push_back(s);
s.clear();
}
t = Type::param;
} else if (*c == '=' && (t == Type::param || t == Type::begin)) {
m_keysvalues[s] = "";
last_param = s;
s.clear();
t = Type::value;
} else if (*c == '&' && (t == Type::value || t == Type::param || t == Type::begin)) {
if (t == Type::value) {
m_keysvalues[last_param] = s;
} else if ((t == Type::param || t == Type::begin) && (s.length() > 0)) {
m_keysvalues[s] = "";
last_param = s;
s.clear();
t = Type::value;
} else if (*c == '&' && (t == Type::value || t == Type::param || t == Type::begin)) {
if (t == Type::value) {
m_keysvalues[last_param] = s;
} else if ((t == Type::param || t == Type::begin) && (s.length() > 0)) {
m_keysvalues[s] = "";
last_param = s;
}
t = Type::param;
s.clear();
} else if (*c == '\0' && s.length() > 0) {
if (t == Type::value) {
m_keysvalues[last_param] = s;
} else if (t == Type::folder || t == Type::begin) {
m_folders.push_back(s);
} else if (t == Type::param) {
m_keysvalues[s] = "";
last_param = s;
}
s.clear();
} else if (*c == '\0' && s.length() == 0) {
if (t == Type::param && last_param.length() > 0) {
m_keysvalues[last_param] = "";
}
s.clear();
} else {
s += *c;
}
} while (*c++ != '\0');
}
t = Type::param;
s.clear();
} else if (*c == '\0' && s.length() > 0) {
if (t == Type::value) {
m_keysvalues[last_param] = s;
} else if (t == Type::folder || t == Type::begin) {
m_folders.push_back(s);
} else if (t == Type::param) {
m_keysvalues[s] = "";
last_param = s;
}
s.clear();
} else if (*c == '\0' && s.length() == 0) {
if (t == Type::param && last_param.length() > 0) {
m_keysvalues[last_param] = "";
}
s.clear();
} else {
s += *c;
}
} while (*c++ != '\0');
return true;
}

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,12 +30,11 @@ 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
HIDDEN = (1 << 4), // 16 do not show in API or Web
ADMIN_ONLY = (1 << 5) // 32 requires authentication
MQTT_SUB_FLAG_WW = (1 << 2), // 4 TAG_DEVICE_DATA_WW
HIDDEN = (1 << 3), // 8 do not show in API or Web
ADMIN_ONLY = (1 << 4) // 16 requires authentication
};
@@ -134,39 +125,36 @@ class Command {
static const char * parse_command_string(const char * command, int8_t & id);
static const std::string return_code_string(const uint8_t return_code);
static std::string return_code_string(const uint8_t return_code);
private:
static uuid::log::Logger logger_;
static std::vector<CmdFunction> cmdfunctions_; // the list of commands
inline static uint8_t message(uint8_t error_code, const char * message, JsonObject & output) {
inline static uint8_t message(uint8_t error_code, const char * message, const JsonObject & output) {
output.clear();
output["message"] = (const char *)message;
output["message"] = message;
return error_code;
}
};
typedef std::unordered_map<std::string, std::string> KeyValueMap_t;
typedef std::vector<std::string> Folder_t;
class SUrlParser {
private:
KeyValueMap_t m_keysvalues;
Folder_t m_folders;
std::unordered_map<std::string, std::string> m_keysvalues;
std::vector<std::string> m_folders;
public:
SUrlParser(){};
SUrlParser() = default;
SUrlParser(const char * url);
bool parse(const char * url);
Folder_t & paths() {
std::vector<std::string> & paths() {
return m_folders;
};
KeyValueMap_t & params() {
std::unordered_map<std::string, std::string> & params() {
return m_keysvalues;
};

View File

@@ -31,9 +31,8 @@ std::shared_ptr<Commands> EMSESPShell::commands = [] {
return commands;
}();
static std::shared_ptr<EMSESPShell> shell;
std::vector<bool> EMSESPStreamConsole::ptys_;
std::shared_ptr<EMSESPShell> shell;
std::vector<bool> EMSESPStreamConsole::ptys_;
#ifndef EMSESP_STANDALONE
uuid::telnet::TelnetService telnet_([](Stream & stream, const IPAddress & addr, uint16_t port) -> std::shared_ptr<uuid::console::Shell> {
@@ -54,10 +53,6 @@ void EMSESPShell::stopped() {
logger().log(LogLevel::DEBUG, LogFacility::AUTH, F("su session closed on console %s"), console_name().c_str());
}
logger().log(LogLevel::DEBUG, LogFacility::CONSOLE, F("User session closed on console %s"), console_name().c_str());
// remove all custom contexts
// commands->remove_all_commands();
// console_commands_loaded_ = false; // make sure they get reloaded next time a console is opened
}
// show welcome banner
@@ -76,8 +71,7 @@ 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";
}
// load the list of commands
@@ -244,12 +238,12 @@ void EMSESPShell::add_console_commands() {
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(read)},
flash_string_vector{F_(deviceid_mandatory), F_(typeid_mandatory), F_(offset_optional)},
flash_string_vector{F_(deviceid_mandatory), F_(typeid_mandatory), F_(offset_optional), F_(length_optional)},
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
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 +349,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 +427,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));
@@ -463,48 +458,24 @@ std::string EMSESPShell::hostname_text() {
return console_hostname_;
}
/*
// remove commands from the current context to save memory before exiting
bool EMSESPShell::exit_context() {
unsigned int current_context = context();
if (current_context == ShellContext::MAIN) {
Shell::stop();
return true;
}
// commands->remove_context_commands(current_context);
// return Shell::exit_context();
return false;
}
// enter a custom context (sub-menu)
void Console::enter_custom_context(Shell & shell, unsigned int context) {
// load_standard_commands(context);
// don't go into the new context if it's already the root (to prevent double loading)
if (context != ShellContext::MAIN) {
shell.enter_context(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) {
if (arguments.empty()) {
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");
});
@@ -516,7 +487,7 @@ void Console::load_standard_commands(unsigned int context) {
flash_string_vector{F_(debug)},
flash_string_vector{F_(name_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.size() == 0) {
if (arguments.empty()) {
Test::debug(shell, "default");
} else {
Test::debug(shell, arguments.front());
@@ -564,10 +535,7 @@ void Console::load_standard_commands(unsigned int context) {
EMSESPShell::commands->add_command(context,
CommandFlags::USER,
flash_string_vector{F_(exit)},
[=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.stop();
// shell.exit_context();
});
[=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { shell.stop(); });
EMSESPShell::commands->add_command(context,
CommandFlags::USER,
@@ -693,7 +661,17 @@ void Console::load_system_commands(unsigned int context) {
networkSettings.ssid = arguments.front().c_str();
return StateUpdateResult::CHANGED;
});
shell.println("Use `wifi reconnect` to save and apply the new settings");
shell.println("Use `wifi reconnect` to 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,
@@ -723,72 +701,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)},
@@ -797,22 +736,6 @@ void Console::load_system_commands(unsigned int context) {
});
}
/*
// prompt, change per context
std::string EMSESPShell::context_text() {
switch (static_cast<ShellContext>(context())) {
case ShellContext::MAIN:
return std::string{'/'};
case ShellContext::SYSTEM:
return std::string{"/system"};
default:
return std::string{};
}
}
*/
// when in su (admin) show # as the prompt suffix
std::string EMSESPShell::prompt_suffix() {
if (has_flags(CommandFlags::ADMIN)) {
@@ -853,7 +776,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_);
@@ -874,11 +797,13 @@ std::string EMSESPStreamConsole::console_name() {
return name_;
}
// Start up telnet and logging
// Log order is off, err, warning, notice, info, debug, trace, all
void Console::start() {
// Start serial console
void Console::start_serial() {
Serial.begin(115200);
// Serial Console - is always active
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)
@@ -888,18 +813,20 @@ void Console::start() {
#if defined(EMSESP_STANDALONE)
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 up telnet
void Console::start_telnet() {
telnet_enabled_ = true; // telnet is enabled when calling this function
// 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
#endif
// turn watch off in case it was still set in the last session
// EMSESP::watch(EMSESP::WATCH_OFF);
}
// handles telnet sync and logging to console
@@ -907,7 +834,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,10 +96,9 @@ 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;
// bool exit_context() override;
private:
void add_console_commands();
@@ -132,13 +126,16 @@ class EMSESPStreamConsole : public uuid::console::StreamConsole, public EMSESPSh
class Console {
public:
void loop();
void start();
void start_serial();
void start_telnet();
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_ = false; // telnet is default off
};
} // namespace emsesp

View File

@@ -35,36 +35,40 @@ 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));
Mqtt::subscribe(EMSdevice::DeviceType::DALLASSENSOR, "dallasssensor/#", nullptr); // use empty function callback
}
// 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 +97,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 +116,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 +146,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)) {
if (sensor.internal_id() == get_id(addr)) {
t += sensor.offset();
if (t != sensor.temperature_c) {
publish_sensor(sensor);
changed_ |= true;
}
sensor.temperature_c = t;
@@ -150,12 +161,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 +180,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().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().c_str());
}
} else {
if (!parasite_) {
@@ -187,7 +203,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 +229,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().c_str());
return EMS_VALUE_SHORT_NOTSET;
}
YIELD;
@@ -225,7 +241,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().c_str());
return EMS_VALUE_SHORT_NOTSET;
}
YIELD;
@@ -241,7 +257,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().c_str());
return EMS_VALUE_SHORT_NOTSET;
}
@@ -273,162 +289,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, const std::string & name, int16_t offset) {
// find the sensor
for (auto & sensor : sensors_) {
if (sensor.id() == id) {
// 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);
}
}
});
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 == id) {
SensorCustomization.name = name;
SensorCustomization.offset = offset;
found = true;
LOG_DEBUG(F("Customizing existing sensor ID %s"), id.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 = id;
newSensor.name = name;
newSensor.offset = offset;
settings.sensorCustomizations.push_back(newSensor);
LOG_DEBUG(F("Adding new customization for sensor ID %s"), id.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 +352,104 @@ 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) {
if (sensors_.empty()) {
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"] = sensor.id();
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;
dataSensor["temp"] = Helpers::round2((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0);
}
} else if (Helpers::hasValue(sensor.temperature_c)) {
output[sensor.name()] = Helpers::round2((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0);
}
}
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) {
// make a copy of the string command for parsing
char command_s[30];
strlcpy(command_s, cmd, sizeof(command_s));
char * attribute_s = nullptr;
// check specific attribute to fetch instead of the complete record
char * breakp = strchr(command_s, '/');
if (breakp) {
*breakp = '\0';
attribute_s = breakp + 1;
}
for (const auto & sensor : sensors_) {
if (strcmp(command_s, sensor.name().c_str()) == 0) {
output["id"] = sensor.id();
output["name"] = sensor.name();
if (Helpers::hasValue(sensor.temperature_c)) {
output["value"] = Helpers::round2((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0);
}
output["type"] = F_(number);
output["min"] = Helpers::round2(-55, 0, EMSESP::system_.fahrenheit() ? 2 : 0);
output["max"] = Helpers::round2(125, 0, EMSESP::system_.fahrenheit() ? 2 : 0);
output["uom"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES);
output["writeable"] = false;
// if we're filtering on an attribute, go find it
if (attribute_s) {
if (output.containsKey(attribute_s)) {
JsonVariant data = output[attribute_s];
output.clear();
output["api_data"] = data;
return true;
} else {
char error[100];
snprintf(error, sizeof(error), "cannot find attribute %s in entity %s", attribute_s, command_s);
output.clear();
output["message"] = error;
return 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];
if (Mqtt::publish_single2cmd()) {
snprintf(topic, sizeof(topic), "%s/%s", read_flash_string(F_(dallassensor)).c_str(), sensor.name().c_str());
} else {
snprintf(topic, sizeof(topic), "%s%s/%s", read_flash_string(F_(dallassensor)).c_str(), "_data", 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) {
if (!Mqtt::ha_enabled()) {
return;
}
#ifdef EMSESP_DEBUG
LOG_DEBUG(F("Removing HA config for temperature sensor ID %s"), id.c_str());
#endif
// use '_' as HA doesn't like '-' in the topic name
std::string sensorid = id;
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 +458,32 @@ 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);
}
}
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)) {
dataSensor["temp"] = (float)(sensor.temperature_c) / 10;
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());
dataSensor["name"] = sensor.name();
if (has_value) {
dataSensor["temp"] = Helpers::round2((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0);
}
} else if (Helpers::hasValue(sensor.temperature_c)) {
doc[sensor.to_string()] = (float)(sensor.temperature_c) / 10;
} else if (has_value) {
doc[sensor.name()] = Helpers::round2((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0);
}
// 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().c_str());
StaticJsonDocument<EMSESP_JSON_SIZE_MEDIUM> config;
config["dev_cla"] = FJSON("temperature");
@@ -511,25 +491,16 @@ 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");
config["unit_of_meas"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES);
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().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().c_str());
config["uniq_id"] = str;
JsonObject dev = config.createNestedObject("dev");
@@ -540,23 +511,94 @@ void DallasSensor::publish_values(const bool force) {
ids.add("ems-esp-dallas"); // Different ids as the other portions of the 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();
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[])
: internal_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_s[20];
snprintf(id_s,
sizeof(id_s),
"%02X-%04X-%04X-%04X",
(unsigned int)(internal_id_ >> 48) & 0xFF,
(unsigned int)(internal_id_ >> 32) & 0xFFFF,
(unsigned int)(internal_id_ >> 16) & 0xFFFF,
(unsigned int)(internal_id_)&0xFFFF);
id_ = std::string(id_s);
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_;
}
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.empty()) {
for (const auto & sensor : sensors) {
#if defined(EMSESP_DEBUG)
LOG_DEBUG(F("Loading customization for dallas sensor %s"), sensor.id.c_str());
#endif
if (id_ == sensor.id) {
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 internal_id() const {
return internal_id_;
}
std::string id() const {
return id_;
}
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 internal_id_;
std::string id_;
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
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_.empty());
}
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, const std::string & name, int16_t offset);
#ifdef EMSESP_DEBUG
void test();
#endif
private:
static constexpr uint8_t MAX_SENSORS = 20;
@@ -122,34 +147,30 @@ class DallasSensor {
static uuid::log::Logger logger_;
#ifndef EMSESP_STANDALONE
OneWire bus_;
#endif
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);
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;
std::vector<Sensor> sensors_;
bool registered_ha_[MAX_SENSORS];
std::vector<Sensor> sensors_; // our list of active sensors
#ifndef EMSESP_STANDALONE
OneWire bus_;
uint32_t last_activity_ = uuid::get_uptime();
State state_ = State::IDLE;
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;
#endif
uint8_t dallas_gpio_ = 0;
bool parasite_ = false;
bool changed_ = false;
uint32_t sensorfails_ = 0;
uint32_t sensorreads_ = 0;
};
} // namespace emsesp

View File

@@ -64,6 +64,14 @@
#define EMSESP_DEFAULT_SHOWER_ALERT false
#endif
#ifndef EMSESP_DEFAULT_SHOWER_ALERT_TRIGGER
#define EMSESP_DEFAULT_SHOWER_ALERT_TRIGGER 7
#endif
#ifndef EMSESP_DEFAULT_SHOWER_ALERT_COLDSHOT
#define EMSESP_DEFAULT_SHOWER_ALERT_COLDSHOT 10
#endif
#ifndef EMSESP_DEFAULT_HIDE_LED
#define EMSESP_DEFAULT_HIDE_LED false
#endif
@@ -85,11 +93,15 @@
#endif
#ifndef EMSESP_DEFAULT_ANALOG_ENABLED
#define EMSESP_DEFAULT_ANALOG_ENABLED false
#define EMSESP_DEFAULT_ANALOG_ENABLED true
#endif
#ifndef EMSESP_DEFAULT_TELNET_ENABLED
#define EMSESP_DEFAULT_TELNET_ENABLED true
#endif
#ifndef EMSESP_DEFAULT_BOARD_PROFILE
#define EMSESP_DEFAULT_BOARD_PROFILE "S32" // Gateway S32
#define EMSESP_DEFAULT_BOARD_PROFILE "default"
#endif
// Default GPIO PIN definitions
@@ -124,14 +136,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
#ifndef EMSESP_DEFAULT_MQTT_QOS
#define EMSESP_DEFAULT_MQTT_QOS 0
#endif
@@ -152,6 +156,18 @@
#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_PUBLISH_SINGLE2CMD
#define EMSESP_DEFAULT_PUBLISH_SINGLE2CMD false
#endif
#ifndef EMSESP_DEFAULT_SEND_RESPONSE
#define EMSESP_DEFAULT_SEND_RESPONSE false
#endif
@@ -164,6 +180,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 +200,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,14 +26,17 @@
// 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},
{168, DeviceType::BOILER, F("Hybrid Heatpump"), DeviceFlags::EMS_DEVICE_FLAG_HYBRID},
{170, DeviceType::BOILER, F("Logano GB212"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{172, DeviceType::BOILER, F("Enviline/Compress 6000AW/Hybrid 7000iAW/SupraEco/Geo 5xx"), DeviceFlags::EMS_DEVICE_FLAG_HEATPUMP},
{173, DeviceType::BOILER, F("Geo 5xx"), DeviceFlags::EMS_DEVICE_FLAG_HEATPUMP},
@@ -42,17 +45,19 @@
{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
{114, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{125, DeviceType::CONTROLLER, F("BC25"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{152, DeviceType::CONTROLLER, F("Controller"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{168, DeviceType::CONTROLLER, F("Hybrid Heatpump"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{169, DeviceType::CONTROLLER, F("BC40"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{190, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{194, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
@@ -61,6 +66,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,12 +86,13 @@
{ 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
{172, DeviceType::THERMOSTAT, F("Rego 2000/3000"), DeviceFlags::EMS_DEVICE_FLAG_RC300}, // 0x10
{216, DeviceType::THERMOSTAT, F("CRF200S"), DeviceFlags::EMS_DEVICE_FLAG_CRF | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18
{246, DeviceType::THERMOSTAT, F("Comfort+2RF"), DeviceFlags::EMS_DEVICE_FLAG_CRF | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18
// Thermostat - Sieger - 0x10 / 0x17
{ 66, DeviceType::THERMOSTAT, F("ES72/RC20"), DeviceFlags::EMS_DEVICE_FLAG_RC20_N}, // 0x17 or remote
@@ -97,28 +104,33 @@
{106, DeviceType::THERMOSTAT, F("FW200"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},
{107, DeviceType::THERMOSTAT, F("FR100"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_OLD}, // older model
{108, DeviceType::THERMOSTAT, F("FR110"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_OLD}, // older model
{109, DeviceType::THERMOSTAT, F("FB10"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},
{110, DeviceType::THERMOSTAT, F("FB100"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},
{111, DeviceType::THERMOSTAT, F("FR10"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_OLD}, // older model
{147, DeviceType::THERMOSTAT, F("FR50"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_OLD},
{191, DeviceType::THERMOSTAT, F("FR120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_OLD}, // older model
{192, DeviceType::THERMOSTAT, F("FW120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},
// Solar Modules - 0x30, 0x2A (for ww)
// Solar Modules - 0x30 (for solar), 0x2A, 0x41 (for ww)
{ 73, DeviceType::SOLAR, F("SM10"), DeviceFlags::EMS_DEVICE_FLAG_SM10},
{101, DeviceType::SOLAR, F("ISM1"), DeviceFlags::EMS_DEVICE_FLAG_ISM},
{103, DeviceType::SOLAR, F("ISM2"), DeviceFlags::EMS_DEVICE_FLAG_ISM},
{162, DeviceType::SOLAR, F("SM50"), DeviceFlags::EMS_DEVICE_FLAG_SM100},
{163, DeviceType::SOLAR, F("SM100/MS100"), DeviceFlags::EMS_DEVICE_FLAG_SM100},
{164, DeviceType::SOLAR, F("SM200/MS200"), DeviceFlags::EMS_DEVICE_FLAG_SM100},
// Mixer Modules - 0x20-0x27 for HC, 0x28-0x29 for WWC
// Mixer Modules - 0x20-0x27 for HC, 0x28-0x29 for WWC and 0x11 for the MP100
{ 69, DeviceType::MIXER, F("MM10"), DeviceFlags::EMS_DEVICE_FLAG_MM10},
{100, DeviceType::MIXER, F("IPM"), DeviceFlags::EMS_DEVICE_FLAG_IPM},
{102, DeviceType::MIXER, F("IPM"), DeviceFlags::EMS_DEVICE_FLAG_IPM},
{159, DeviceType::MIXER, F("MM50"), DeviceFlags::EMS_DEVICE_FLAG_MMPLUS},
{160, DeviceType::MIXER, F("MM100"), DeviceFlags::EMS_DEVICE_FLAG_MMPLUS},
{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},
{248, DeviceType::HEATPUMP, F("Hybrid Manager HM200"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{252, DeviceType::HEATPUMP, F("HP Module"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
// Connect devices - 0x02
@@ -126,8 +138,9 @@
{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},
{238, DeviceType::CONNECT, F("Wireless sensor base"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
// Switches - 0x11
{ 71, DeviceType::SWITCH, F("WM10"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
@@ -135,7 +148,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,54 +50,53 @@ 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 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 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
uint8_t wwActive_; //
uint8_t wwHeat_; // 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 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 wwComfort1_; // WW comfort mode RC310
uint8_t wwCircPump_; // DHW circulation pump available
uint8_t wwChargeType_; // DHW charge type (pump or 3-way-valve)
uint8_t wwChargeOptimization_; // DHW charge optimization
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_; // 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 ww3wayValve_; // 3-way valve on WW
uint8_t wwSetPumpPower_; // ww pump speed/power?
uint8_t wwFlowTempOffset_; // Boiler offset for ww heating
uint8_t wwMaxPower_; // DHW maximum power
uint8_t wwMaxTemp_; // DHW maximum temperature
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
uint8_t selBurnPow_; // Burner max power %
uint8_t selBurnPow_; // Burner max power % (can be > 100%)
uint8_t heatingPump2Mod_; // heatpump modulation from 0xE3 (heatpumps)
uint8_t heatingPumpMod_; // Pump modulation %
int16_t outdoorTemp_; // Outside temperature
@@ -110,10 +107,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 %
@@ -129,31 +128,34 @@ class Boiler : public EMSdevice {
uint8_t setBurnPow_; // max output power in %
uint32_t burnStarts_; // burner restarts
uint32_t burnWorkMin_; // Total burner operating time
uint32_t burn2WorkMin_; // burner stage 2 operating time
uint32_t heatWorkMin_; // Total heat operating time
uint32_t UBAuptime_; // Total UBA working hours
char lastCode_[60]; // last error code
char lastCode_[50]; // last error code
char serviceCode_[4]; // 3 character status/service code
uint16_t serviceCodeNumber_; // error/service code
uint8_t emergencyOps_;
uint8_t emergencyTemp_;
// info
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
@@ -181,7 +183,6 @@ class Boiler : public EMSdevice {
uint8_t hpCoolingOn_;
uint8_t hpWwOn_;
uint8_t hpPoolOn_;
uint8_t hpHeatingOn;
int16_t hpTc0_;
int16_t hpTc1_;
int16_t hpTc3_;
@@ -197,6 +198,21 @@ class Boiler : public EMSdevice {
// Pool unit
int8_t poolSetTemp_;
/*
* Hybrid heatpump with telegram 0xBB is readable and writeable in boiler and thermostat
* thermostat always overwrites settings in boiler
* enable settings here if no thermostat is used in system
*
// HybridHP
uint8_t hybridStrategy_; // cost = 2, temperature = 3, mix = 4
int8_t switchOverTemp_; // degrees
uint8_t energyCostRatio_; // is *10
uint8_t fossileFactor_; // is * 10
uint8_t electricFactor_; // is * 10
uint8_t delayBoiler_; // minutes
uint8_t tempDiffBoiler_; // relative temperature degrees
*/
void process_UBAParameterWW(std::shared_ptr<const Telegram> telegram);
void process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram);
void process_UBATotalUptime(std::shared_ptr<const Telegram> telegram);
@@ -214,6 +230,7 @@ class Boiler : public EMSdevice {
void process_MC110Status(std::shared_ptr<const Telegram> telegram);
void process_UBAMaintenanceStatus(std::shared_ptr<const Telegram> telegram);
void process_UBAMaintenanceData(std::shared_ptr<const Telegram> telegram);
void process_ErrorMessage(std::shared_ptr<const Telegram> telegram);
void process_UBAErrorMessage(std::shared_ptr<const Telegram> telegram);
void process_UBAErrorMessage2(std::shared_ptr<const Telegram> telegram);
void process_UBAMonitorWWPlus(std::shared_ptr<const Telegram> telegram);
@@ -224,9 +241,11 @@ class Boiler : public EMSdevice {
void process_HpPower(std::shared_ptr<const Telegram> telegram);
void process_HpOutdoor(std::shared_ptr<const Telegram> telegram);
void process_HpPool(std::shared_ptr<const Telegram> telegram);
void process_HybridHp(std::shared_ptr<const Telegram> telegram);
// commands - none of these use the additional id parameter
bool set_ww_mode(const char * value, const int8_t id);
bool set_ww_mode1(const char * value, const int8_t id);
bool set_ww_activated(const char * value, const int8_t id);
bool set_tapwarmwater_activated(const char * value, const int8_t id);
bool set_ww_onetime(const char * value, const int8_t id);
@@ -239,7 +258,9 @@ class Boiler : public EMSdevice {
bool set_ww_temp_single(const char * value, const int8_t id);
bool set_ww_disinfect_temp(const char * value, const int8_t id);
bool set_ww_maxpower(const char * value, const int8_t id);
bool set_ww_maxtemp(const char * value, const int8_t id);
bool set_ww_flowTempOffset(const char * value, const int8_t id);
bool set_ww_chargeOptimization(const char * value, const int8_t id);
bool set_flow_temp(const char * value, const int8_t id);
bool set_burn_power(const char * value, const int8_t id);
bool set_heating_activated(const char * value, const int8_t id);
@@ -259,6 +280,17 @@ class Boiler : public EMSdevice {
bool set_ww_hyst_on(const char * value, const int8_t id);
bool set_ww_hyst_off(const char * value, const int8_t id);
bool set_pool_temp(const char * value, const int8_t id);
bool set_emergency_temp(const char * value, const int8_t id);
bool set_emergency_ops(const char * value, const int8_t id);
/*
bool set_hybridStrategy(const char * value, const int8_t id);
bool set_switchOverTemp(const char * value, const int8_t id);
bool set_energyCostRatio(const char * value, const int8_t id);
bool set_fossileFactor(const char * value, const int8_t id);
bool set_electricFactor(const char * value, const int8_t id);
bool set_delayBoiler(const char * value, const int8_t id);
bool set_tempDiffBoiler(const char * value, const int8_t id);
*/
};
} // namespace emsesp

View File

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

View File

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

View File

@@ -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);
@@ -42,17 +43,33 @@ class Mixer : public EMSdevice {
void process_MMSetMessage(std::shared_ptr<const Telegram> telegram);
void process_HpPoolStatus(std::shared_ptr<const Telegram> telegram);
void process_IPMMonitorWW(std::shared_ptr<const Telegram> telegram);
void process_IPMHydrTemp(std::shared_ptr<const Telegram> telegram);
void process_IPMParameterWW(std::shared_ptr<const Telegram> telegram);
bool set_flowSetTemp(const char * value, const int8_t id);
bool set_pump(const char * value, const int8_t id);
bool set_activated(const char * value, const int8_t id);
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);
bool set_wwSelTemp(const char * value, const int8_t id);
bool set_wwFlowTempOffset(const char * value, const int8_t id);
bool set_wwHystOn(const char * value, const int8_t id);
bool set_wwHystOff(const char * value, const int8_t id);
enum class Type {
NONE,
HC, // heating circuit
WWC, // warm water circuit
MP // pool
};
private:
@@ -64,14 +81,31 @@ 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
uint8_t wwSelTemp_;
uint16_t wwCurTemp_1_;
uint16_t wwCurTemp_2_;
uint16_t HydrTemp_;
int8_t wwHystOn_; // Hyst on (default -5)
int8_t wwHystOff_; // Hyst off (default -1)
uint8_t wwFlowTempOffset_; // default 40
};
} // namespace emsesp

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 wwSelTemp_;
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,36 +174,65 @@ 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);
void process_ISM2StatusMessage(std::shared_ptr<const Telegram> telegram);
// settings
bool set_CollectorMaxTemp(const char * value, const int8_t id);
bool set_CollectorMinTemp(const char * value, const int8_t id);
bool set_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_wwSelTemp(const char * value, const int8_t id);
bool set_wwMaxTemp(const char * value, const int8_t id);
bool set_wwRedTemp(const char * value, const int8_t id);
bool set_wwCirc(const char * value, const int8_t id);
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,16 +34,17 @@ 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;
uint8_t daytemp;
uint8_t daylowtemp;
uint8_t daymidtemp;
uint8_t nighttemp;
uint8_t holidaytemp;
uint8_t heatingtype; // type of heating: 1 radiator, 2 convectors, 3 floors, 4 room supply
@@ -53,23 +54,33 @@ class Thermostat : public EMSdevice {
uint8_t designtemp; // heating curve design temp at MinExtTemp
int8_t offsettemp; // heating curve offest temp at roomtemp signed!
uint8_t manualtemp;
uint8_t summer_setmode;
uint8_t summersetmode;
uint8_t hpoperatingmode;
uint8_t roominfluence;
uint8_t roominfl_factor;
int16_t curroominfl;
uint8_t flowtempoffset;
uint8_t minflowtemp;
uint8_t maxflowtemp;
uint8_t reducemode;
uint8_t nofrostmode;
uint8_t program;
uint8_t controlmode;
uint8_t control;
uint8_t pause;
uint8_t party;
int8_t noreducetemp; // signed -20°C to +10°C
int8_t reducetemp;
int8_t vacreducetemp;
uint8_t vacreducemode;
uint8_t wwprio;
uint8_t fastHeatup;
char holiday[22];
char vacation[22];
char holiday[26];
char vacation[26];
char switchtime1[16];
char switchtime2[16];
uint8_t climate;
// RC 10
uint8_t reducehours; // night reduce duration
uint16_t reduceminutes; // remaining minutes to night->day
@@ -78,13 +89,17 @@ 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);
bool is_active() const {
return Helpers::hasValue(selTemp);
}
uint8_t get_mode() const;
@@ -110,14 +125,18 @@ class Thermostat : public EMSdevice {
ROOMINFLUENCE,
TEMPAUTO,
NOREDUCE,
REDUCE,
VACREDUCE,
ON,
DAYLOW,
DAYMID,
UNKNOWN
};
// 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:
@@ -127,8 +146,6 @@ class Thermostat : public EMSdevice {
static std::string mode_tostring(uint8_t mode);
virtual bool publish_ha_device_config();
private:
static uuid::log::Logger logger_;
@@ -144,18 +161,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
@@ -167,6 +183,11 @@ class Thermostat : public EMSdevice {
uint8_t ibaDamping_; // damping 0-off, 0xff-on
uint8_t backlight_;
uint8_t heatingpid_;
int8_t brightness_; // Screen brightness 0F=dark F1=light
uint8_t preheating_; // Preheating in the clock program: (0x00 = off, 0xFF = on)
uint8_t autodst_; // Automatic change Daylight Saving time: (0x00 = off, 0xFF = on)
uint8_t offtemp_; // Set Temperature when mode is Off / 10 (e.g.: 0x0F = 7.5 degrees Celsius)
uint8_t mixingvalves_; // Number of Mixing Valves: (0x00=0, 0x01=1, 0x02=2)
int8_t dampedoutdoortemp_;
uint16_t tempsensor1_;
@@ -184,13 +205,27 @@ 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_;
uint8_t wwWhenModeOff_;
// HybridHP
uint8_t hybridStrategy_; // co2 = 1, cost = 2, temperature = 3, mix = 4
int8_t switchOverTemp_; // degrees
uint8_t energyCostRatio_; // is *10
uint8_t fossileFactor_; // is * 10
uint8_t electricFactor_; // is * 10
uint8_t delayBoiler_; // minutes
uint8_t tempDiffBoiler_; // relative temperature degrees
std::vector<std::shared_ptr<HeatingCircuit>> heating_circuits_; // each thermostat can have multiple heating circuits
@@ -208,7 +243,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
@@ -218,6 +254,11 @@ class Thermostat : public EMSdevice {
static constexpr uint8_t EMS_OFFSET_RC30StatusMessage_curr = 2; // current temp
static constexpr uint8_t EMS_OFFSET_RC30Set_mode = 23; // position of thermostat mode
static constexpr uint8_t EMS_OFFSET_RC30Set_temp = 28; // position of thermostat setpoint temperature
static constexpr uint8_t EMS_OFFSET_RC30Temp_temp_night = 3; // position of thermostat setpoint temperature for night time (T1)
static constexpr uint8_t EMS_OFFSET_RC30Temp_temp_daylow = 4; // position of thermostat setpoint temperature for daylow time (T2)
static constexpr uint8_t EMS_OFFSET_RC30Temp_temp_daymid = 5; // position of thermostat setpoint temperature for daymid time (T3)
static constexpr uint8_t EMS_OFFSET_RC30Temp_temp_day = 6; // position of thermostat setpoint temperature for day time (T4)
static constexpr uint8_t EMS_OFFSET_RC30Temp_temp_holiday = 7; // temp during holiday mode
static constexpr uint8_t EMS_OFFSET_RC35StatusMessage_setpoint = 2; // desired temp
static constexpr uint8_t EMS_OFFSET_RC35StatusMessage_curr = 3; // current temp
@@ -231,6 +272,12 @@ class Thermostat : public EMSdevice {
static constexpr uint8_t EMS_OFFSET_RC35Set_targetflowtemp = 14; // target flow temperature
static constexpr uint8_t EMS_OFFSET_RC35Set_seltemp = 37; // selected temp
static constexpr uint8_t EMS_OFFSET_RC35Set_noreducetemp = 38; // temp to stop reducing
static constexpr uint8_t EMS_OFFSET_RC35Set_reducetemp = 39; // temp reducing/hold
static constexpr uint8_t EMS_OFFSET_RC35Set_vacreducetemp = 40; // temp reducing/hold in vacations
static constexpr uint8_t EMS_OFFSET_RC35Set_vacreducemode = 41; // reduce mode in vacations
static constexpr uint8_t EMS_OFFSET_RC35Set_reducemode = 25; // reduce mode in normal operation
static constexpr uint8_t EMS_OFFSET_RC35Set_nofrostmode = 28; // 0-off, 1-room, 2-outdoor
static constexpr uint8_t EMS_OFFSET_RC35Set_control = 26; // 0-off, 1-RC20, 2-RC3x
static constexpr uint8_t EMS_OFFSET_RC35Set_temp_offset = 6;
static constexpr uint8_t EMS_OFFSET_RC35Set_temp_flowoffset = 24;
static constexpr uint8_t EMS_OFFSET_RC35Set_temp_design = 17;
@@ -269,17 +316,18 @@ class Thermostat : public EMSdevice {
static constexpr uint8_t AUTO_HEATING_CIRCUIT = 0;
// Installation settings
static constexpr uint8_t EMS_TYPE_IBASettings = 0xA5; // installation settings
static constexpr uint8_t EMS_TYPE_wwSettings = 0x37; // ww settings
static constexpr uint8_t EMS_TYPE_time = 0x06; // time
static constexpr uint8_t EMS_TYPE_IBASettings = 0xA5; // installation settings
static constexpr uint8_t EMS_TYPE_RC30Settings = 0xA7; // RC30 settings
static constexpr uint8_t EMS_TYPE_wwSettings = 0x37; // ww settings
static constexpr uint8_t EMS_TYPE_RC30wwSettings = 0x3A; // RC30 ww settings
static constexpr uint8_t EMS_TYPE_time = 0x06; // time
std::shared_ptr<Thermostat::HeatingCircuit> heating_circuit(std::shared_ptr<const Telegram> telegram);
std::shared_ptr<Thermostat::HeatingCircuit> heating_circuit(const uint8_t hc_num);
void publish_ha_config_hc(std::shared_ptr<Thermostat::HeatingCircuit> hc);
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) const;
void process_RCOutdoorTemp(std::shared_ptr<const Telegram> telegram);
void process_IBASettings(std::shared_ptr<const Telegram> telegram);
@@ -287,13 +335,19 @@ 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);
void process_RC30Monitor(std::shared_ptr<const Telegram> telegram);
void process_RC30Set(std::shared_ptr<const Telegram> telegram);
void process_RC30Temp(std::shared_ptr<const Telegram> telegram);
void process_RC30wwSettings(std::shared_ptr<const Telegram> telegram);
void process_RC30Timer(std::shared_ptr<const Telegram> telegram);
void process_RC20Monitor(std::shared_ptr<const Telegram> telegram);
void process_RC20Set(std::shared_ptr<const Telegram> telegram);
void process_RC20Temp(std::shared_ptr<const Telegram> telegram);
void process_RC20Timer(std::shared_ptr<const Telegram> telegram);
void process_RC20Remote(std::shared_ptr<const Telegram> telegram);
void process_RC20Monitor_2(std::shared_ptr<const Telegram> telegram);
void process_RC20Set_2(std::shared_ptr<const Telegram> telegram);
@@ -316,12 +370,16 @@ 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);
void process_JunkersHybridSettings(std::shared_ptr<const Telegram> telegram);
void process_JunkersSetMixer(std::shared_ptr<const Telegram> telegram);
// internal helper functions
bool set_mode_n(const uint8_t mode, const uint8_t hc_num);
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);
@@ -337,6 +395,8 @@ class Thermostat : public EMSdevice {
bool set_temp(const char * value, const int8_t id);
bool set_nighttemp(const char * value, const int8_t id);
bool set_daytemp(const char * value, const int8_t id);
bool set_daylowtemp(const char * value, const int8_t id);
bool set_daymidtemp(const char * value, const int8_t id);
bool set_comforttemp(const char * value, const int8_t id);
bool set_nofrosttemp(const char * value, const int8_t id);
bool set_ecotemp(const char * value, const int8_t id);
@@ -348,13 +408,20 @@ class Thermostat : public EMSdevice {
bool set_manualtemp(const char * value, const int8_t id);
bool set_tempautotemp(const char * value, const int8_t id);
bool set_noreducetemp(const char * value, const int8_t id);
bool set_reducetemp(const char * value, const int8_t id);
bool set_vacreducetemp(const char * value, const int8_t id);
bool set_vacreducemode(const char * value, const int8_t id);
bool set_nofrostmode(const char * value, const int8_t id);
bool set_remotetemp(const char * value, const int8_t id);
bool set_roominfluence(const char * value, const int8_t id);
bool set_roominfl_factor(const char * value, const int8_t id);
bool set_flowtempoffset(const char * value, const int8_t id);
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 +441,11 @@ 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_wwwhenmodeoff(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);
@@ -387,6 +459,19 @@ class Thermostat : public EMSdevice {
bool set_reducehours(const char * value, const int8_t id);
bool set_backlight(const char * value, const int8_t id);
bool set_heatingpid(const char * value, const int8_t id);
bool set_brightness(const char * value, const int8_t id);
bool set_autodst(const char * value, const int8_t id);
bool set_preheating(const char * value, const int8_t id);
bool set_mixingvalves(const char * value, const int8_t id);
bool set_offtemp(const char * value, const int8_t id);
bool set_hybridStrategy(const char * value, const int8_t id);
bool set_switchOverTemp(const char * value, const int8_t id);
bool set_energyCostRatio(const char * value, const int8_t id);
bool set_fossileFactor(const char * value, const int8_t id);
bool set_electricFactor(const char * value, const int8_t id);
bool set_delayBoiler(const char * value, const int8_t id);
bool set_tempDiffBoiler(const char * value, const int8_t id);
};
} // namespace emsesp

File diff suppressed because it is too large Load Diff

View File

@@ -19,143 +19,38 @@
#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;
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);
std::string device_type_name() const;
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);
static std::string device_type_2_device_name(const uint8_t device_type);
static uint8_t device_name_2_device_type(const char * topic);
static std::string uom_to_string(uint8_t uom);
static std::string tag_to_string(uint8_t tag);
static std::string tag_to_mqtt(uint8_t tag);
inline uint8_t device_id() const {
return device_id_;
@@ -169,7 +64,7 @@ class EMSdevice {
product_id_ = product_id;
}
inline bool is_device_id(uint8_t device_id) {
inline bool is_device_id(uint8_t device_id) const {
return ((device_id & 0x7F) == (device_id_ & 0x7F));
}
@@ -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() const {
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,32 +123,84 @@ 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;
}
const std::string brand_to_string() const;
static uint8_t decode_brand(uint8_t value);
inline void has_update(void * value) {
has_update_ = true;
publish_value(value);
}
const std::string to_string() const;
const std::string to_string_short() const;
inline void has_update(char * value, const char * newvalue, size_t len) {
if (strcmp(value, newvalue) != 0) {
strlcpy(value, newvalue, len);
has_update_ = true;
publish_value(value);
}
}
void show_telegram_handlers(uuid::console::Shell & shell);
char * show_telegram_handlers(char * result);
void show_mqtt_handlers(uuid::console::Shell & shell);
void list_device_entries(JsonObject & output);
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);
}
}
std::string brand_to_string() const;
static uint8_t decode_brand(uint8_t value);
std::string to_string() const;
std::string to_string_short() const;
enum Handlers : uint8_t { ALL, RECEIVED, FETCHED, PENDING, IGNORED };
void show_telegram_handlers(uuid::console::Shell & shell) const;
char * show_telegram_handlers(char * result, const size_t len, const uint8_t handlers);
void show_mqtt_handlers(uuid::console::Shell & shell) const;
void list_device_entries(JsonObject & output) const;
void add_handlers_ignored(const uint16_t handler);
void mask_entity(const std::string & entity_id);
void getMaskedEntities(std::vector<std::string> & entity_ids);
using process_function_p = std::function<void(std::shared_ptr<const Telegram>)>;
void register_telegram_type(const uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, const process_function_p cb);
bool handle_telegram(std::shared_ptr<const Telegram> telegram);
const std::string get_value_uom(const char * key);
bool get_value_info(JsonObject & root, const char * cmd, const int8_t id);
std::string get_value_uom(const char * key) const;
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);
enum OUTPUT_TARGET : uint8_t { API_VERBOSE, API_SHORTNAMES, MQTT, CONSOLE };
bool generate_values(JsonObject & output, const uint8_t tag_filter, const bool nested, const uint8_t output_target);
void generate_values_web(JsonObject & output);
void generate_values_web_all(JsonArray & output);
void register_device_value(uint8_t tag,
void * value_p,
@@ -262,8 +210,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 +220,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 +230,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,
@@ -287,29 +238,42 @@ class EMSdevice {
const __FlashStringHelper * const * name,
uint8_t uom);
void write_command(const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid);
void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid);
void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value);
void write_command(const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid) const;
void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid) const;
void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value) const;
void read_command(const uint16_t type_id, uint8_t offset = 0, uint8_t length = 0);
void read_command(const uint16_t type_id, uint8_t offset = 0, uint8_t length = 0) const;
void publish_mqtt_ha_entity_config();
bool is_readable(const void * value_p) const;
bool is_readonly(const std::string & cmd, const int8_t id) const;
bool has_command(const void * value_p) const;
void publish_value(void * value_p) const;
void publish_all_values();
const std::string telegram_type_name(std::shared_ptr<const Telegram> telegram);
void mqtt_ha_entity_config_create();
void mqtt_ha_entity_config_remove();
std::string telegram_type_name(std::shared_ptr<const Telegram> telegram) const;
void fetch_values();
void toggle_fetch(uint16_t telegram_id, bool toggle);
bool is_fetch(uint16_t telegram_id);
bool is_fetch(uint16_t telegram_id) const;
bool has_telegram_id(uint16_t id) const;
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 +289,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,
@@ -355,6 +320,7 @@ class EMSdevice {
static constexpr uint8_t EMS_DEVICE_FLAG_EMSPLUS = 2;
static constexpr uint8_t EMS_DEVICE_FLAG_HT3 = 3;
static constexpr uint8_t EMS_DEVICE_FLAG_HEATPUMP = 4;
static constexpr uint8_t EMS_DEVICE_FLAG_HYBRID = 5;
// Solar Module
static constexpr uint8_t EMS_DEVICE_FLAG_SM10 = 1;
@@ -374,34 +340,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() const;
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,79 +382,12 @@ 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);
}
std::vector<uint16_t> handlers_ignored_;
};
} // namespace emsesp

255
src/emsdevicevalue.cpp Normal file
View File

@@ -0,0 +1,255 @@
/*
* 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_wwc5), // "wwc5"
F_(tag_wwc6), // "wwc6"
F_(tag_wwc7), // "wwc7"
F_(tag_wwc8), // "wwc8"
F_(tag_wwc9), // "wwc9"
F_(tag_wwc10), // "wwc10"
F_(tag_hs1), // "hs1"
F_(tag_hs2), // "hs2"
F_(tag_hs3), // "hs3"
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_wwc5), // "wwc5"
F_(tag_wwc6), // "wwc6"
F_(tag_wwc7), // "wwc7"
F_(tag_wwc8), // "wwc8"
F_(tag_wwc9), // "wwc9"
F_(tag_wwc10), // "wwc10"
F_(tag_hs1), // "hs1"
F_(tag_hs2), // "hs2"
F_(tag_hs3), // "hs3"
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() const {
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;
}
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

202
src/emsdevicevalue.h Normal file
View File

@@ -0,0 +1,202 @@
/*
* 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 squaremeter
M3, // 19 cubic meter
L // 20
};
// 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_WWC5,
TAG_WWC6,
TAG_WWC7,
TAG_WWC8,
TAG_WWC9,
TAG_WWC10,
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 {
// low nibble active state of the device value
DV_DEFAULT = 0, // 0 - does not yet have a value
DV_ACTIVE = (1 << 0), // 1 - has a validated real value
DV_HA_CONFIG_CREATED = (1 << 1), // 2 - set if the HA config topic has been created
DV_HA_CLIMATE_NO_RT = (1 << 2), // 4 - climate created without roomTemp
// high nibble as mask for exclusions & special functions
DV_WEB_EXCLUDE = (1 << 4), // 16 - not shown on web
DV_API_MQTT_EXCLUDE = (1 << 5), // 32 - not shown on mqtt, API
DV_READONLY = (1 << 6), // 64 - read only
DV_FAVORITE = (1 << 7) // 128 - sort to front
};
uint8_t device_type; // EMSdevice::DeviceType
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::*
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)
: 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) {
}
bool hasValue() const;
bool get_min_max(int16_t & dv_set_min, int16_t & dv_set_max);
// state flags
void add_state(uint8_t s) {
state |= s;
}
bool has_state(uint8_t s) const {
return (state & s) == s;
}
void remove_state(uint8_t s) {
state &= ~s;
}
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

File diff suppressed because it is too large Load Diff

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 {
@@ -111,8 +128,11 @@ class EMSESP {
static void send_raw_telegram(const char * data);
static bool device_exists(const uint8_t device_id);
static bool cmd_is_readonly(const uint8_t device_type, const char * cmd, const int8_t id);
static uint8_t count_devices(const uint8_t device_type);
static uint8_t count_devices();
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);
@@ -126,44 +146,16 @@ class EMSESP {
static void show_devices(uuid::console::Shell & shell);
static void show_ems(uuid::console::Shell & shell);
static void init_uart();
static void uart_init();
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 +170,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);
}
@@ -213,8 +207,9 @@ class EMSESP {
static void fetch_device_values(const uint8_t device_id = 0);
static void fetch_device_values_type(const uint8_t device_type);
static bool valid_device(const uint8_t device_id);
static void scheduled_fetch_values();
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 +219,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 +242,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 +271,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,11 +33,40 @@ 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) {
if (NULL == ptr) {
return NULL;
if (nullptr == ptr) {
return nullptr;
}
unsigned long t = 0;
@@ -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,33 +175,26 @@ char * Helpers::smallitoa(char * result, const uint16_t value) {
}
// work out how to display booleans
char * Helpers::render_boolean(char * result, bool value) {
uint8_t bool_format_ = EMSESP::bool_format();
if (bool_format_ == BOOL_FORMAT_ONOFF) {
// for strings only
char * Helpers::render_boolean(char * result, const bool value, const bool dashboard) {
uint8_t bool_format_ = dashboard ? EMSESP::system_.bool_dashboard() : 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))) {
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 ((uint8_t)format == EMS_VALUE_BOOL) {
if (value == EMS_VALUE_BOOL_OFF) {
render_boolean(result, false);
} else if (value == EMS_VALUE_BOOL_NOTSET) {
@@ -164,8 +209,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,32 +220,41 @@ 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;
}
uint32_t p[] = {0, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
char * ret = result;
int32_t whole = (int32_t)value;
char * ret = result;
auto whole = (int32_t)value;
itoa(result, whole, 10);
itoa(whole, result, 10);
while (*result != '\0') {
result++;
@@ -206,97 +262,102 @@ 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);
strlcpy(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 +367,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 +381,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 +413,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
@@ -377,58 +464,139 @@ uint32_t Helpers::abs(const int32_t i) {
}
// for booleans, use isBool true (EMS_VALUE_BOOL)
bool Helpers::hasValue(const uint8_t & v, const uint8_t isBool) {
bool Helpers::hasValue(const uint8_t & value, const uint8_t isBool) {
if (isBool == EMS_VALUE_BOOL) {
return (v != EMS_VALUE_BOOL_NOTSET);
return (value != EMS_VALUE_BOOL_NOTSET);
}
return (v != EMS_VALUE_UINT_NOTSET);
return (value != EMS_VALUE_UINT_NOTSET);
}
bool Helpers::hasValue(const int8_t & v) {
return (v != EMS_VALUE_INT_NOTSET);
bool Helpers::hasValue(const int8_t & value) {
return (value != EMS_VALUE_INT_NOTSET);
}
bool Helpers::hasValue(char * v) {
if ((v == nullptr) || (strlen(v) == 0)) {
bool Helpers::hasValue(const char * value) {
if ((value == nullptr) || (strlen(value) == 0)) {
return false;
}
return (v[0] != '\0');
return (value[0] != '\0');
}
// for short these are typically 0x8300, 0x7D00 and sometimes 0x8000
bool Helpers::hasValue(const int16_t & v) {
return (abs(v) < EMS_VALUE_USHORT_NOTSET);
bool Helpers::hasValue(const int16_t & value) {
return (abs(value) < EMS_VALUE_USHORT_NOTSET);
}
bool Helpers::hasValue(const uint16_t & v) {
return (v < EMS_VALUE_USHORT_NOTSET);
bool Helpers::hasValue(const uint16_t & value) {
return (value < EMS_VALUE_USHORT_NOTSET);
}
bool Helpers::hasValue(const uint32_t & v) {
return (v != EMS_VALUE_ULONG_NOTSET);
bool Helpers::hasValue(const uint32_t & value) {
return (value != EMS_VALUE_ULONG_NOTSET);
}
// checks if we can convert a char string to an int value
bool Helpers::value2number(const char * v, int & value) {
if ((v == nullptr) || (strlen(v) == 0)) {
value = 0;
bool Helpers::value2number(const char * value, int & value_i, const int min, const int max) {
if ((value == nullptr) || (strlen(value) == 0)) {
value_i = 0;
return false;
}
value = atoi((char *)v);
return true;
value_i = atoi(value);
if (value_i >= min && value_i <= max) {
return true;
}
return false;
}
// checks if we can convert a char string to a float value
bool Helpers::value2float(const char * v, float & value) {
value = 0;
if ((v == nullptr) || (strlen(v) == 0)) {
bool Helpers::value2float(const char * value, float & value_f) {
value_f = 0;
if ((value == nullptr) || (strlen(value) == 0)) {
return false;
}
if (v[0] == '-' || v[0] == '.' || (v[0] >= '0' && v[0] <= '9')) {
value = atof((char *)v);
if (value[0] == '-' || value[0] == '.' || (value[0] >= '0' && value[0] <= '9')) {
value_f = atof(value);
return true;
}
if (value[0] == '+' && (value[1] == '.' || (value[1] >= '0' && value[1] <= '9'))) {
value_f = atof(value + 1);
return true;
}
return false;
}
bool Helpers::value2temperature(const char * value, float & value_f, bool relative) {
if (value2float(value, value_f)) {
if (EMSESP::system_.fahrenheit()) {
value_f = relative ? (value_f / 1.8) : (value_f - 32) / 1.8;
}
return true;
}
return false;
}
bool Helpers::value2temperature(const char * value, int & value_i, const bool relative, const int min, const int max) {
if (value2number(value, value_i, min, max)) {
if (EMSESP::system_.fahrenheit()) {
value_i = relative ? (value_i / 1.8) : (value_i - 32) / 1.8;
}
return true;
}
return false;
}
// checks if we can convert a char string to a lowercase string
bool Helpers::value2string(const char * value, std::string & value_s) {
if ((value == nullptr) || (strlen(value) == 0)) {
value_s = std::string{};
return false;
}
value_s = toLower(value);
return true;
}
// checks to see if a string (usually a command or payload cmd) looks like a boolean
// on, off, true, false, 1, 0
bool Helpers::value2bool(const char * value, bool & value_b) {
if ((value == nullptr) || (strlen(value) == 0)) {
return false;
}
std::string bool_str = toLower(value); // convert to lower case
if ((bool_str == read_flash_string(F_(on))) || (bool_str == "1") || (bool_str == "true")) {
value_b = true;
return true; // is a bool
}
if ((bool_str == read_flash_string(F_(off))) || (bool_str == "0") || (bool_str == "false")) {
value_b = false;
return true; // is a bool
}
return false; // not a bool
}
// checks to see if a string is member of a vector and return the index, also allow true/false for on/off
bool Helpers::value2enum(const char * value, uint8_t & value_ui, const __FlashStringHelper * const * strs) {
if ((value == nullptr) || (strlen(value) == 0)) {
return false;
}
std::string str = toLower(value);
for (value_ui = 0; strs[value_ui]; value_ui++) {
std::string str1 = toLower(read_flash_string(strs[value_ui]));
if ((str1 != "")
&& ((str1 == read_flash_string(F_(off)) && str == "false") || (str1 == read_flash_string(F_(on)) && str == "true") || (str == str1)
|| (value[0] == ('0' + value_ui) && value[1] == '\0'))) {
return true;
}
}
return false;
}
@@ -445,53 +613,17 @@ std::string Helpers::toUpper(std::string const & s) {
return lc;
}
// checks if we can convert a char string to a lowercase string
bool Helpers::value2string(const char * v, std::string & value) {
if ((v == nullptr) || (strlen(v) == 0)) {
value = std::string{};
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++;
}
value = toLower(v);
return true;
}
// checks to see if a string (usually a command or payload cmd) looks like a boolean
// on, off, true, false, 1, 0
bool Helpers::value2bool(const char * v, bool & value) {
if ((v == nullptr) || (strlen(v) == 0)) {
return false;
}
std::string bool_str = toLower(v); // convert to lower case
if ((bool_str == read_flash_string(F_(on))) || (bool_str == "1") or (bool_str == "true")) {
value = true;
return true; // is a bool
}
if ((bool_str == read_flash_string(F_(off))) || (bool_str == "0") or (bool_str == "false")) {
value = false;
return true; // is a bool
}
return false; // not a bool
}
// checks to see if a string is member of a vector and return the index, also allow true/false for on/off
bool Helpers::value2enum(const char * v, uint8_t & value, const __FlashStringHelper * const * strs) {
if ((v == nullptr) || (strlen(v) == 0)) {
return false;
}
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')) {
return true;
}
}
return false;
}
} // 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,40 +30,47 @@ 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_boolean(char * result, bool value);
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_boolean(char * result, const bool value, const bool dashboard = false);
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);
static bool hasValue(const int16_t & v);
static bool hasValue(const uint16_t & v);
static bool hasValue(const uint32_t & v);
static bool hasValue(char * v);
static bool hasValue(const uint8_t & value, const uint8_t isBool = 0);
static bool hasValue(const int8_t & value);
static bool hasValue(const int16_t & value);
static bool hasValue(const uint16_t & value);
static bool hasValue(const uint32_t & value);
static bool hasValue(const char * value);
static bool value2number(const char * v, int & value);
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 value2number(const char * value, int & value_i, const int min = -2147483648, const int max = 2147483647);
static bool value2float(const char * value, float & value_f);
static bool value2bool(const char * value, bool & value_b);
static bool value2string(const char * value, std::string & value_s);
static bool value2enum(const char * value, uint8_t & value_ui, const __FlashStringHelper * const * strs);
static bool value2temperature(const char * value, float & value_f, bool relative = false);
static bool value2temperature(const char * value, int & value_i, 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);

802
src/locale_DE.h Normal file
View File

@@ -0,0 +1,802 @@
/*
* 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(setvalue)
// 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)
// 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(analogsensor)
MAKE_PSTR_WORD(unknown)
MAKE_PSTR_WORD(dallassensor)
// 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(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 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"), F("timer"), F("rate"), F("digital out"), F("pwm 0"), F("pwm 1"), F("pwm 2"))
// strings
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(length_optional, "[length]")
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, "lists all values")
MAKE_PSTR(commands_cmd, "lists all commands")
MAKE_PSTR(entities_cmd, "lists all entities")
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(div60, F_(60))
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")
MAKE_PSTR(wh, "Wh")
MAKE_PSTR(bar, "bar")
MAKE_PSTR(minutes, "Minuten")
MAKE_PSTR(hours, "Stunden")
MAKE_PSTR(days, "Tage")
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")
MAKE_PSTR(m3, "m3")
MAKE_PSTR(l, "l")
// MAKE_PSTR(times, "mal")
// MAKE_PSTR(oclock, "Uhr")
// 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_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")
MAKE_PSTR(tag_wwc4, "wwc4")
MAKE_PSTR(tag_wwc5, "wwc5")
MAKE_PSTR(tag_wwc6, "wwc6")
MAKE_PSTR(tag_wwc7, "wwc7")
MAKE_PSTR(tag_wwc8, "wwc8")
MAKE_PSTR(tag_wwc9, "wwc9")
MAKE_PSTR(tag_wwc10, "wwc10")
MAKE_PSTR(tag_hs1, "hs1")
MAKE_PSTR(tag_hs2, "hs2")
MAKE_PSTR(tag_hs3, "hs3")
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(eco, "Eco")
MAKE_PSTR(intelligent, "Intelligent")
MAKE_PSTR_(flow, "Fluss")
MAKE_PSTR(manual, "Manuell")
MAKE_PSTR_(buffer, "Speicher")
MAKE_PSTR(bufferedflow, "Durchlaufspeicher")
MAKE_PSTR(layeredbuffer, "Schichtspeicher")
MAKE_PSTR(maintenance, "Wartung")
// 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_(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("-"), F_(maintenance), F_(error))
// MAKE_PSTR_LIST(enum_bool, F_(off), F_(on))
//heatpump
MAKE_PSTR_LIST(enum_hpactivity, F("Kein"), F("Heizen"), F("Kühlen"), F("Warmwasser"), F("Pool"))
// mixer
MAKE_PSTR_LIST(enum_shunt, F("gestoppt"), F("öffnen"), F("schließen"), F("Offen"), F("Geschlossen"))
// thermostat
MAKE_PSTR(light, "Leicht")
MAKE_PSTR(medium, "Mittel")
MAKE_PSTR(heavy, "Schwer")
MAKE_PSTR(own_prog, "Eigenprog")
MAKE_PSTR(start, "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(winter, "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: < NTP | dd.mm.yyyy-hh:mm:ss-dw-dst >"))
MAKE_PSTR_LIST(tpl_switchtime, F("Format: < nn.d.o.hh:mm >"))
MAKE_PSTR_LIST(tpl_switchtime1, F("Format: <nn> [ not_set | day hh:mm Tn ]"))
MAKE_PSTR_LIST(tpl_holidays, F("Format: < dd.mm.yyyy-dd.mm.yyyy >"))
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_ibaLanguage_RC30, F_(german), F_(dutch))
MAKE_PSTR_LIST(enum_floordrystatus, F_(off), F_(start), F_(heat), F_(hold), F_(cool), F_(end))
MAKE_PSTR_LIST(enum_ibaBuildingType, F_(light), F_(medium), F_(heavy)) // RC300
MAKE_PSTR_LIST(enum_PID, F("fast"), F_(medium), F("slow"))
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_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_hpoperatingmode, F_(off), F_(auto), F("heizen"), F("kühlen"))
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, 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_(nofrost), F_(night), F_(day)) // RC10
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_nofrostmode, F_(off), F_(room), F_(outdoor))
MAKE_PSTR_LIST(enum_controlmode, F_(off), F_(optimized), F_(simple), F_(mpc), F_(room), F_(power), F_(constant))
MAKE_PSTR_LIST(enum_controlmode2, F_(outdoor), F_(room))
// 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_j_control, F_(off), F("fb10"), F("fb110"))
MAKE_PSTR_LIST(enum_wwProgMode, F("std Prog"), F_(own_prog))
MAKE_PSTR_LIST(enum_dayOfWeek, F("Mo"), F("Di"), F("Mi"), F("Do"), F("Fr"), F("Sa"), F("So"), F("Alle"))
MAKE_PSTR_LIST(enum_progMode, F("Prog_1"), F("Prog_2"))
MAKE_PSTR_LIST(enum_progMode2,
F("Eigen_1"),
F("Familie"),
F("Morgends"),
F("Abends"),
F("Vormittag"),
F("Nachmittag"),
F("Mittag"),
F("Singles"),
F("Senioren"),
F("Neu"),
F("Eigen_2"))
MAKE_PSTR_LIST(enum_progMode3, F("Familie"), F("Morgends"), F("Abends"), F("Vormittag"), F("Nachmittag"), F("Mittag"), F("Singles"), F("Senioren"))
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))
MAKE_PSTR_LIST(enum_climate, F("Solltemperature"), F("Raumtemperatur"))
// solar list
MAKE_PSTR_LIST(enum_solarmode, F_(constant), F("pwm"), F("analog"))
MAKE_PSTR_LIST(enum_collectortype, F("flach"), F("vakuum"))
MAKE_PSTR_LIST(enum_cylprio, F("Zyl_1"), F("Zyl_2"))
// id used to store the device ID, goes into MQTT payload
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("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(burnGas2, F("burngas2"), F("Gas Stufe 2"))
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(oilPreHeat, F("oilpreheat"), F("oil preheating"))
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(burn2WorkMin, F("burn2workmin"), F("Brenner Stufe 2 Laufzeit"))
MAKE_PSTR_LIST(heatWorkMin, F("heatworkmin"), F("Heizung Laufzeit"))
MAKE_PSTR_LIST(UBAuptime, F("ubauptime"), F("gesamte Laufzeit"))
MAKE_PSTR_LIST(lastCode, F("lastcode"), F("Fehlerspeicher"))
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"))
// heatpump/compress specific
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(upTimeCompPool, F("uptimecomppool"), F("Betriebszeit Kompressor Pool"))
MAKE_PSTR_LIST(totalCompStarts, F("totalcompstarts"), F("gesamt Kompressor Starts"))
MAKE_PSTR_LIST(heatingStarts, F("heatingstarts"), F("Heizen Starts"))
MAKE_PSTR_LIST(coolingStarts, F("coolingstarts"), F("Kühlen Starts"))
MAKE_PSTR_LIST(poolStarts, F("poolstarts"), F("Pool 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(nrgConsCompPool, F("nrgconscomppool"), F("Energieverbrauch Kompressor Pool"))
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(nrgSuppPool, F("nrgsupppool"), F("gesamte Energieabgabe Pool"))
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(auxElecHeatNrgConsPool, F("auxelecheatnrgconspool"), F("Energieverbrauch el. Zusatzheizung Pool"))
MAKE_PSTR_LIST(hpPower, F("hppower"), F("Leistung Wärmepumpe"))
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 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"))
MAKE_PSTR_LIST(hpCircSpd, F("hpcircspd"), F("Circulation pump Speed"))
MAKE_PSTR_LIST(hpBrineIn, F("hpbrinein"), F("Brine in/Evaporator"))
MAKE_PSTR_LIST(hpBrineOut, F("hpbrineout"), F("Brine out/Condenser"))
MAKE_PSTR_LIST(hpSuctionGas, F("hpsuctiongas"), F("Suction gas"))
MAKE_PSTR_LIST(hpHotGas, F("hphotgas"), F("Hot gas/Compressed"))
MAKE_PSTR_LIST(hpSwitchValve, F("hpswitchvalve"), F("Switch Valve"))
MAKE_PSTR_LIST(hpActivity, F("hpactivity"), F("Compressor Activity"))
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)"))
// hybrid heatpump
MAKE_PSTR_LIST(enum_hybridStrategy, F("co2-optimized"), F("cost-optimized"), F("outside-temp-switched"), F("co2-cost-mix"))
MAKE_PSTR_LIST(hybridStrategy, F("hybridstrategy"), F("hybrid control strategy"))
MAKE_PSTR_LIST(switchOverTemp, F("switchovertemp"), F("outside switchover temperature"))
MAKE_PSTR_LIST(energyCostRatio, F("energycostratio"), F("energy cost ratio"))
MAKE_PSTR_LIST(fossileFactor, F("fossilefactor"), F("fossile energy factor"))
MAKE_PSTR_LIST(electricFactor, F("electricfactor"), F("electric energy factor"))
MAKE_PSTR_LIST(delayBoiler, F("delayboiler"), F("delay boiler support"))
MAKE_PSTR_LIST(tempDiffBoiler, F("tempdiffboiler"), F("tempediff boiler support"))
// the following are dhw for the boiler and automatically tagged with 'ww'
MAKE_PSTR_LIST(wWSelTemp, F("wwseltemp"), F("gewählte Temperatur"))
MAKE_PSTR_LIST(wwSelTempLow, F("wwseltemplow"), F("selected lower temperature"))
MAKE_PSTR_LIST(wwSelTempOff, F("wwseltempoff"), F("selected temperature for off"))
MAKE_PSTR_LIST(wwSelTempSingle, F("wwseltempsingle"), F("single charge temperature"))
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(wWDisinfecting, 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(ww3wayValve, F("ww3wayvalve"), F("3way valve active"))
MAKE_PSTR_LIST(wWSetPumpPower, F("wwsetpumppower"), F("Soll Pumpenleistung"))
MAKE_PSTR_LIST(mixerTemp, F("mixertemp"), F("Mischertemperatur"))
MAKE_PSTR_LIST(wwCylMiddleTemp, F("wwcylmiddletemp"), F("cylinder middle temperature (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(wwProgMode, F("wwprogmode"), F("Programmmodus"))
MAKE_PSTR_LIST(wwCircProg, F("wwcircprog"), F("Zirkulationsprogramm"))
MAKE_PSTR_LIST(wwMaxTemp, F("wwmaxtemp"), F("Maximale Temperatur"))
MAKE_PSTR_LIST(wwOneTimeKey, F("wwonetimekey"), F("Einmalladungstaste"))
// mqtt values / commands
MAKE_PSTR_LIST(switchtime, F("switchtime"), F("program switchtime"))
MAKE_PSTR_LIST(switchtime1, F("switchtime1"), F("own1 program switchtime"))
MAKE_PSTR_LIST(switchtime2, F("switchtime2"), F("own2 program switchtime"))
MAKE_PSTR_LIST(wwswitchtime, F("wwswitchtime"), F("program switchtime"))
MAKE_PSTR_LIST(wwcircswitchtime, F("wwcircswitchtime"), F("circulation program switchtime"))
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(heatingPID, F("heatingpid"), F("heating PID"))
MAKE_PSTR_LIST(ibaCalIntTemperature, F("intoffset"), F("Korrektur interner Temperatur"))
MAKE_PSTR_LIST(ibaMinExtTemperature, F("minexttemp"), F("min Aussentemperatur"))
MAKE_PSTR_LIST(backlight, F("backlight"), F("key backlight"))
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(brightness, F("brightness"), F("bildschirmhelligkeit"))
MAKE_PSTR_LIST(autodst, F("autodst"), F("automatische sommerzeit umstellung"))
MAKE_PSTR_LIST(preheating, F("preheating"), F("vorheizen im uhrenprogramm"))
MAKE_PSTR_LIST(offtemp, F("offtemp"), F("temperatur bei ausgeschaltetem modus"))
MAKE_PSTR_LIST(mixingvalves, F("mixingvalves"), F("mischventile"))
// thermostat ww
MAKE_PSTR_LIST(wwMode, F("wwmode"), F("modus"))
MAKE_PSTR_LIST(wwSetTempLow, F("wwsettemplow"), F("untere Solltemperatur"))
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("Kreis 1 Extra"))
MAKE_PSTR_LIST(wwExtra2, F("wwextra2"), F("Kreis 2 Extra"))
MAKE_PSTR_LIST(wwDailyHeating, F("wwdailyheating"), F("daily heating"))
MAKE_PSTR_LIST(wwDailyHeatTime, F("wwdailyheattime"), F("daily heating time"))
MAKE_PSTR_LIST(wwWhenModeOff, F("wwwhenmodeoff"), F("wenn Thermostatmodus ist aus"))
// thermostat hc
MAKE_PSTR_LIST(climate, F("climate"))
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(fastheatup, F("fastheatup"), F("fast heatup"))
MAKE_PSTR_LIST(daytemp, F("daytemp"), F("Tagestemperatur"))
MAKE_PSTR_LIST(daylowtemp, F("daytemp2"), F("Tagestemperatur T2"))
MAKE_PSTR_LIST(daymidtemp, F("daytemp3"), F("Tagestemperatur T3"))
MAKE_PSTR_LIST(dayhightemp, F("daytemp4"), F("Tagestemperatur T4"))
MAKE_PSTR_LIST(heattemp, F("heattemp"), F("Heizen Temperatur"))
MAKE_PSTR_LIST(nighttemp, F("nighttemp"), F("Nachttemperatur"))
MAKE_PSTR_LIST(nighttemp2, F("nighttemp"), F("Nachttemperatur T1"))
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(roominfl_factor, F("roominflfactor"), F("Raumeinfluss Factor"))
MAKE_PSTR_LIST(curroominfl, F("curroominfl"), F("aktueller 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(hpoperatingmode, F("hpoperatingmode"), F("Wärmepumpe Betriebsmodus"))
MAKE_PSTR_LIST(controlmode, F("controlmode"), F("Kontrollmodus"))
MAKE_PSTR_LIST(control, F("control"), F("Fernsteuerung"))
MAKE_PSTR_LIST(holidays, F("holidays"), F("holiday dates"))
MAKE_PSTR_LIST(vacations, F("vacations"), F("vacation dates"))
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(reducetemp, F("reducetemp"), F("Absenkmodus unter Temperatur"))
MAKE_PSTR_LIST(vacreducetemp, F("vacreducetemp"), F("Urlaub Absenkmodus unter Temperatur"))
MAKE_PSTR_LIST(vacreducemode, F("vacreducemode"), F("Urlaub Absenkmodus"))
MAKE_PSTR_LIST(nofrostmode, F("nofrostmode"), F("Frostschutz Modus"))
MAKE_PSTR_LIST(remotetemp, F("remotetemp"), F("Raumtemperatur der Fernsteuerung"))
MAKE_PSTR_LIST(reducehours, F("reducehours"), F("duration for nighttemp"))
MAKE_PSTR_LIST(reduceminutes, F("reduceminutes"), F("remaining time for nightmode"))
// 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(mixerSetTime, F("valvesettime"), F("time to set valve"))
// mixer prefixed with wwc
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"))
// 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)"))
MAKE_PSTR_LIST(hydrTemp, F("hydrTemp"), F("hydraulic header temperature"))
// solar
MAKE_PSTR_LIST(collectorTemp, F("collectortemp"), F("Kollektortemperatur (TS1)"))
MAKE_PSTR_LIST(collector2Temp, F("collector2temp"), F("collector 2 temperature (TS7)"))
MAKE_PSTR_LIST(cylBottomTemp, F("cylbottomtemp"), F("Speicher Bodentemperatur (TS2)"))
MAKE_PSTR_LIST(cyl2BottomTemp, F("cyl2bottomtemp"), F("2. Speicher Bodentemperatur (TS5)"))
MAKE_PSTR_LIST(heatExchangerTemp, F("heatexchangertemp"), F("wärmetauscher Temperatur (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("maximale Kollektortemperatur"))
MAKE_PSTR_LIST(collectorMinTemp, F("collectormintemp"), F("minimale Kollektortemperatur"))
MAKE_PSTR_LIST(cylMaxTemp, F("cylmaxtemp"), F("maximale Speichertemperatur"))
// MAKE_PSTR_LIST(cyl2MaxTemp, F("cyl2maxtemp"), F("maximum cylinder 2 temperature"))
MAKE_PSTR_LIST(solarPumpMod, F("solarpumpmod"), F("Pumpenmodulation (PS1)"))
MAKE_PSTR_LIST(cylPumpMod, F("cylpumpmod"), F("Speicherpumpenmodulation (PS5)"))
MAKE_PSTR_LIST(solarPump, F("solarpump"), F("Pumpe (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("ventilstatus"))
MAKE_PSTR_LIST(cylHeated, F("cylheated"), F("Speichertemperatur erreicht"))
MAKE_PSTR_LIST(collectorShutdown, F("collectorshutdown"), F("Kollektorabschaltung"))
MAKE_PSTR_LIST(pumpWorkTime, F("pumpworktime"), F("Pumpenlaufzeit"))
MAKE_PSTR_LIST(pump2WorkTime, F("pump2worktime"), F("Pumpe 2 Laufzeit"))
MAKE_PSTR_LIST(m1WorkTime, F("m1worktime"), F("Differenzregelung Arbeitszeit"))
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(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("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"))
// solar ww and mixer wwc
MAKE_PSTR_LIST(wwMinTemp, F("wwmintemp"), F("minimale Temperatur"))
MAKE_PSTR_LIST(wwRedTemp, F("wwredtemp"), F("redizierte Temperatur"))
MAKE_PSTR_LIST(wwDailyTemp, F("wwdailytemp"), F("tägl. Temperatur"))
MAKE_PSTR_LIST(wwKeepWarm, F("wwkeepwarm"), F("Warmhalten"))
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("Pumpen Modulation"))
MAKE_PSTR_LIST(wwFlow, F("wwflow"), F("Flussrate"))
// extra mixer ww
MAKE_PSTR_LIST(wwRequiredTemp, F("wwrequiredtemp"), F("benötigte Temperatur"))
MAKE_PSTR_LIST(wwDiffTemp, F("wwdifftemp"), F("Start Differential Temperatur"))
//SM100
MAKE_PSTR_LIST(heatTransferSystem, F("heattransfersystem"), F("Wärmetransfer System"))
MAKE_PSTR_LIST(externalCyl, F("externalcyl"), F("Externer Speicher"))
MAKE_PSTR_LIST(thermalDisinfect, F("thermaldisinfect"), F("Thermische Desinfektion"))
MAKE_PSTR_LIST(heatMetering, F("heatmetering"), F("Wärmemessung"))
MAKE_PSTR_LIST(solarIsEnabled, F("solarenabled"), F("Solarmodul aktiviert"))
// telegram 0x035A
MAKE_PSTR_LIST(solarPumpMode, F("solarpumpmode"), F("solar Pumpen Einst."))
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("Speicher Priorität"))
// telegram 0x380
MAKE_PSTR_LIST(climateZone, F("climatezone"), F("climate zone"))
MAKE_PSTR_LIST(collector1Area, F("collector1area"), F("Kollektor 1 Fläche"))
MAKE_PSTR_LIST(collector1Type, F("collector1type"), F("Kollektor 1 Type"))
MAKE_PSTR_LIST(collector2Area, F("collector2area"), F("Kollektor 2 Fläche"))
MAKE_PSTR_LIST(collector2Type, F("collector2type"), F("Kollektor 2 Type"))
// telegram 0x0363 heatCounter
MAKE_PSTR_LIST(heatCntFlowTemp, F("heatcntflowtemp"), F("Wärmezähler Fluss-Temperatur"))
MAKE_PSTR_LIST(heatCntRetTemp, F("heatcntrettemp"), F("Wärmezähler Rückfluss-Temperatur"))
MAKE_PSTR_LIST(heatCnt, F("heatcnt"), F("Wärmezäler Impulse"))
MAKE_PSTR_LIST(swapFlowTemp, F("swapflowtemp"), F("Austausch Fluss-Temperatur (TS14)"))
MAKE_PSTR_LIST(swapRetTemp, F("swaprettemp"), F("Austausch Rückfluss-Temperatur (TS15)"))
// switch
MAKE_PSTR_LIST(activated, F("activated"), F("aktiviert"))
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 Raumtemperatur Sensor"));

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,21 @@ MAKE_PSTR_WORD(pin)
MAKE_PSTR_WORD(publish)
MAKE_PSTR_WORD(timeout)
MAKE_PSTR_WORD(board_profile)
MAKE_PSTR_WORD(sensorname)
MAKE_PSTR_WORD(setvalue)
// 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 +100,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,15 +113,16 @@ 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"), F("timer"), F("rate"), F("digital out"), F("pwm 0"), F("pwm 1"), F("pwm 2"))
// strings
MAKE_PSTR(EMSESP, "EMS-ESP")
MAKE_PSTR(cmd_optional, "[cmd]")
@@ -134,6 +138,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 +161,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 +185,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,28 +193,40 @@ 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(times, "times")
MAKE_PSTR(oclock, "o'clock")
MAKE_PSTR(days, "days")
MAKE_PSTR(sqm, "sqm")
MAKE_PSTR(m3, "m3")
MAKE_PSTR(l, "l")
// MAKE_PSTR(times, "times")
// MAKE_PSTR(oclock, "o'clock")
// 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")
MAKE_PSTR(tag_wwc4, "wwc4")
MAKE_PSTR(tag_wwc5, "wwc5")
MAKE_PSTR(tag_wwc6, "wwc6")
MAKE_PSTR(tag_wwc7, "wwc7")
MAKE_PSTR(tag_wwc8, "wwc8")
MAKE_PSTR(tag_wwc9, "wwc9")
MAKE_PSTR(tag_wwc10, "wwc10")
MAKE_PSTR(tag_hs1, "hs1")
MAKE_PSTR(tag_hs2, "hs2")
MAKE_PSTR(tag_hs3, "hs3")
@@ -223,6 +245,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")
@@ -239,25 +262,29 @@ MAKE_PSTR_WORD(continuous)
MAKE_PSTR(3wayvalve, "3-way valve")
MAKE_PSTR(chargepump, "charge pump")
MAKE_PSTR_WORD(hot)
MAKE_PSTR_WORD(high_comfort)
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_comfort1, F_(high_comfort), F_(eco))
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 +319,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 +341,10 @@ MAKE_PSTR(functioning_mode, "functioning mode")
MAKE_PSTR(smoke_temperature, "smoke temperature")
// thermostat lists
MAKE_PSTR_LIST(tpl_datetime, F("Format: < NTP | dd.mm.yyyy-hh:mm:ss-dw-dst >"))
MAKE_PSTR_LIST(tpl_switchtime, F("Format: <nn> [ not_set | day hh:mm on|off ]"))
MAKE_PSTR_LIST(tpl_switchtime1, F("Format: <nn> [ not_set | day hh:mm Tn ]"))
MAKE_PSTR_LIST(tpl_holidays, F("format: < dd.mm.yyyy-dd.mm.yyyy >"))
MAKE_PSTR_LIST(enum_ibaMainDisplay,
F_(internal_temperature),
F_(internal_setpoint),
@@ -326,6 +356,7 @@ MAKE_PSTR_LIST(enum_ibaMainDisplay,
F_(date),
F_(smoke_temperature))
MAKE_PSTR_LIST(enum_ibaLanguage, F_(german), F_(dutch), F_(french), F_(italian))
MAKE_PSTR_LIST(enum_ibaLanguage_RC30, F_(german), F_(dutch))
MAKE_PSTR_LIST(enum_floordrystatus, F_(off), F_(start), F_(heat), F_(hold), F_(cool), F_(end))
MAKE_PSTR_LIST(enum_ibaBuildingType, F_(light), F_(medium), F_(heavy))
MAKE_PSTR_LIST(enum_PID, F("fast"), F_(medium), F("slow"))
@@ -335,51 +366,53 @@ 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_hpoperatingmode, F_(off), F_(auto), F("heating"), F("cooling"))
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))
// 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_nofrostmode, F_(off), F_(room), F_(outdoor))
MAKE_PSTR_LIST(enum_controlmode, F_(off), F_(optimized), F_(simple), F_(mpc), F_(room), F_(power), F_(constant))
MAKE_PSTR_LIST(enum_controlmode2, F_(outdoor), F_(room))
MAKE_PSTR_LIST(enum_controlmode3, F_(off), F_(room), F_(outdoor), F("room+outdoor"))
// 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_j_control, F_(off), F("fb10"), F("fb110"))
MAKE_PSTR_LIST(enum_j_control, F_(off), F("fb10"), F("fb100"))
MAKE_PSTR_LIST(enum_wwProgMode, F("std_prog"), F_(own_prog))
MAKE_PSTR_LIST(enum_dayOfWeek, F("mo"), F("tu"), F("we"), F("th"), F("fr"), F("sa"), F("so"), F("all"))
MAKE_PSTR_LIST(enum_dayOfWeek, F("mo"), F("tu"), F("we"), F("th"), F("fr"), F("sa"), F("su"), F("all"))
MAKE_PSTR_LIST(enum_progMode, F("prog_1"), F("prog_2"))
MAKE_PSTR_LIST(enum_progMode2, F("own_1"), F("family"), F("morning"), F("evening"), F("am"), F("pm"), F("midday"), F("singles"), F("seniors"), F("new"), F("own_2"))
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))
MAKE_PSTR_LIST(enum_climate, F("selTemp"), F("roomTemp"))
// 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 +421,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 +434,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"))
@@ -420,58 +455,62 @@ MAKE_PSTR_LIST(setBurnPow, F("setburnpow"), F("burner set power"))
MAKE_PSTR_LIST(curBurnPow, F("curburnpow"), F("burner current power"))
MAKE_PSTR_LIST(burnStarts, F("burnstarts"), F("burner starts"))
MAKE_PSTR_LIST(burnWorkMin, F("burnworkmin"), F("total burner operating time"))
MAKE_PSTR_LIST(burn2WorkMin, F("burn2workmin"), F("burner stage 2 operating time"))
MAKE_PSTR_LIST(heatWorkMin, F("heatworkmin"), F("total heat operating time"))
MAKE_PSTR_LIST(UBAuptime, F("ubauptime"), F("total UBA operating time"))
MAKE_PSTR_LIST(lastCode, F("lastcode"), F("last error code"))
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(upTimeControl, F("uptimecontrol"), F("operating time total heat"))
MAKE_PSTR_LIST(maintenanceTime, F("maintenancetime"), F("time to next maintenance"))
MAKE_PSTR_LIST(emergencyOps, F("emergencyops"), F("emergency operation"))
MAKE_PSTR_LIST(emergencyTemp, F("emergencytemp"), F("emergency temperature"))
// heatpump/compress specific
MAKE_PSTR_LIST(upTimeControl, F("uptimecontrol"), F("total operating time heat"))
MAKE_PSTR_LIST(upTimeCompHeating, F("uptimecompheating"), F("operating time compressor heating"))
MAKE_PSTR_LIST(upTimeCompCooling, F("uptimecompcooling"), F("operating time compressor cooling"))
MAKE_PSTR_LIST(upTimeCompWw, F("uptimecompww"), F("operating time compressor 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(nrgConsCompTotal, F("nrgconscomptotal"), F("total energy consumption compressor"))
MAKE_PSTR_LIST(nrgConsCompHeating, F("nrgconscompheating"), F("energy consumption compressor heating"))
MAKE_PSTR_LIST(nrgConsCompWw, F("nrgconscompww"), F("energy consumption compressor 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(auxElecHeatNrgConsTotal, F("auxelecheatnrgconstotal"), F("total auxiliary electrical heater energy consumption"))
MAKE_PSTR_LIST(auxElecHeatNrgConsHeating, F("auxelecheatnrgconsheating"), F("auxiliary electrical heater energy consumption heating"))
MAKE_PSTR_LIST(auxElecHeatNrgConsWW, F("auxelecheatnrgconsww"), F("auxiliary electrical heater energy consumption 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(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"))
MAKE_PSTR_LIST(hpCircSpd, F("hpcircspd"), F("Circulation pump Speed"))
MAKE_PSTR_LIST(hpBrineIn, F("hpbrinein"), F("Brine in/Evaporator"))
MAKE_PSTR_LIST(hpBrineOut, F("hpbrineout"), F("Brine out/Condenser"))
MAKE_PSTR_LIST(hpSuctionGas, F("hpsuctiongas"), F("Suction gas"))
MAKE_PSTR_LIST(hpHotGas, F("hphotgas"), F("Hot gas/Compressed"))
MAKE_PSTR_LIST(hpSwitchValve, F("hpswitchvalve"), F("Switch Valve"))
MAKE_PSTR_LIST(hpActivity, F("hpactivity"), F("Compressor Activity"))
MAKE_PSTR_LIST(hpTc0, F("hptc0"), F("Heat carrier return (TC0)"))
MAKE_PSTR_LIST(hpTc1, F("hptc1"), F("Heat carrier forward (TC1)"))
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 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"))
MAKE_PSTR_LIST(hpCircSpd, F("hpcircspd"), F("circulation pump speed"))
MAKE_PSTR_LIST(hpBrineIn, F("hpbrinein"), F("brine in/evaporator"))
MAKE_PSTR_LIST(hpBrineOut, F("hpbrineout"), F("brine out/condenser"))
MAKE_PSTR_LIST(hpSuctionGas, F("hpsuctiongas"), F("suction gas"))
MAKE_PSTR_LIST(hpHotGas, F("hphotgas"), F("hot gas/compressed"))
MAKE_PSTR_LIST(hpSwitchValve, F("hpswitchvalve"), F("switch valve"))
MAKE_PSTR_LIST(hpActivity, F("hpactivity"), F("compressor activity"))
MAKE_PSTR_LIST(hpTc0, F("hptc0"), F("heat carrier return (TC0)"))
MAKE_PSTR_LIST(hpTc1, F("hptc1"), F("heat carrier forward (TC1)"))
MAKE_PSTR_LIST(hpTc3, F("hptc3"), F("condenser temperature (TC3)"))
MAKE_PSTR_LIST(hpTr3, F("hptr3"), F("refrigerant temperature liquid side (condenser output) (TR3)"))
MAKE_PSTR_LIST(hpTr4, F("hptr4"), F("evaporator inlet temperature (TR4)"))
@@ -481,12 +520,18 @@ 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'
// hybrid heatpump
MAKE_PSTR_LIST(enum_hybridStrategy, F("co2-optimized"), F("cost-optimized"), F("outside-temp-switched"), F("co2-cost-mix"))
MAKE_PSTR_LIST(hybridStrategy, F("hybridstrategy"), F("hybrid control strategy"))
MAKE_PSTR_LIST(switchOverTemp, F("switchovertemp"), F("outside switchover temperature"))
MAKE_PSTR_LIST(energyCostRatio, F("energycostratio"), F("energy cost ratio"))
MAKE_PSTR_LIST(fossileFactor, F("fossilefactor"), F("fossile energy factor"))
MAKE_PSTR_LIST(electricFactor, F("electricfactor"), F("electric energy factor"))
MAKE_PSTR_LIST(delayBoiler, F("delayboiler"), F("delay boiler support"))
MAKE_PSTR_LIST(tempDiffBoiler, F("tempdiffboiler"), F("tempediff boiler support"))
// the following are dhw for the boiler and automatically tagged with 'ww'
MAKE_PSTR_LIST(wwSelTemp, F("wwseltemp"), F("selected temperature"))
MAKE_PSTR_LIST(wwSelTempLow, F("wwseltemplow"), F("selected lower temperature"))
MAKE_PSTR_LIST(wwSelTempOff, F("wwseltempoff"), F("selected temperature for off"))
@@ -494,12 +539,13 @@ MAKE_PSTR_LIST(wwSelTempSingle, F("wwseltempsingle"), F("single charge temperatu
MAKE_PSTR_LIST(wwSetTemp, F("wwsettemp"), F("set temperature"))
MAKE_PSTR_LIST(wwType, F("wwtype"), F("type"))
MAKE_PSTR_LIST(wwComfort, F("wwcomfort"), F("comfort"))
MAKE_PSTR_LIST(wwComfort1, F("wwcomfort1"), F("comfort mode"))
MAKE_PSTR_LIST(wwFlowTempOffset, F("wwflowtempoffset"), F("flow temperature offset"))
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,70 +554,84 @@ 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(wwChargeOptimization, F("wwchargeoptimization"), F("charge optimization"))
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 starts2"))
MAKE_PSTR_LIST(wwWorkM, F("wwworkm"), F("active time"))
MAKE_PSTR_LIST(wwHystOn, F("wwhyston"), F("hysteresis on temperature"))
MAKE_PSTR_LIST(wwHystOff, F("wwhystoff"), F("hysteresis off temperature"))
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(switchtime, F("switchtime"), F("program switchtime"))
MAKE_PSTR_LIST(switchtime1, F("switchtime1"), F("own1 program switchtime"))
MAKE_PSTR_LIST(switchtime2, F("switchtime2"), F("own2 program switchtime"))
MAKE_PSTR_LIST(wwswitchtime, F("wwswitchtime"), F("program switchtime"))
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"))
MAKE_PSTR_LIST(brightness, F("brightness"), F("screen brightness"))
MAKE_PSTR_LIST(autodst, F("autodst"), F("automatic change daylight saving time"))
MAKE_PSTR_LIST(preheating, F("preheating"), F("preheating in the clock program"))
MAKE_PSTR_LIST(offtemp, F("offtemp"), F("temperature when mode is off"))
MAKE_PSTR_LIST(mixingvalves, F("mixingvalves"), F("mixing valves"))
// thermostat ww
MAKE_PSTR_LIST(wwMode, F("wwmode"), F("mode"))
MAKE_PSTR_LIST(wwSetTempLow, F("wwsettemplow"), F("set low temperature"))
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"))
MAKE_PSTR_LIST(wwWhenModeOff, F("wwwhenmodeoff"), F("when thermostat mode off"))
// thermostat hc
MAKE_PSTR_LIST(climate, F("climate"))
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"))
MAKE_PSTR_LIST(daytemp, F("daytemp"), F("day temperature"))
MAKE_PSTR_LIST(daylowtemp, F("daytemp2"), F("day temperature T2"))
MAKE_PSTR_LIST(daymidtemp, F("daytemp3"), F("day temperature T3"))
MAKE_PSTR_LIST(dayhightemp, F("daytemp4"), F("day temperature T4"))
MAKE_PSTR_LIST(heattemp, F("heattemp"), F("heat temperature"))
MAKE_PSTR_LIST(nighttemp, F("nighttemp"), F("night temperature"))
MAKE_PSTR_LIST(nighttemp2, F("nighttemp"), F("night temperature T1"))
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"))
@@ -579,11 +639,13 @@ MAKE_PSTR_LIST(offsettemp, F("offsettemp"), F("offset temperature"))
MAKE_PSTR_LIST(minflowtemp, F("minflowtemp"), F("min flow temperature"))
MAKE_PSTR_LIST(maxflowtemp, F("maxflowtemp"), F("max flow temperature"))
MAKE_PSTR_LIST(roominfluence, F("roominfluence"), F("room influence"))
MAKE_PSTR_LIST(roominfl_factor, F("roominflfactor"), F("room influence factor"))
MAKE_PSTR_LIST(curroominfl, F("curroominfl"), F("current room influence"))
MAKE_PSTR_LIST(nofrosttemp, F("nofrosttemp"), F("nofrost temperature"))
MAKE_PSTR_LIST(targetflowtemp, F("targetflowtemp"), F("target flow temperature"))
MAKE_PSTR_LIST(heatingtype, F("heatingtype"), F("heating type"))
MAKE_PSTR_LIST(summersetmode, F("summersetmode"), F("set summer mode"))
MAKE_PSTR_LIST(hpoperatingmode, F("hpoperatingmode"), F("heatpump operating mode"))
MAKE_PSTR_LIST(controlmode, F("controlmode"), F("control mode"))
MAKE_PSTR_LIST(control, F("control"), F("control device"))
MAKE_PSTR_LIST(holidays, F("holidays"), F("holiday dates"))
@@ -591,14 +653,17 @@ 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(reducetemp, F("reducetemp"), F("off/reduce switch temperature"))
MAKE_PSTR_LIST(vacreducetemp, F("vacreducetemp"), F("vacations off/reduce switch temperature"))
MAKE_PSTR_LIST(vacreducemode, F("vacreducemode"), F("vacations reduce mode"))
MAKE_PSTR_LIST(nofrostmode, F("nofrostmode"), F("nofrost mode"))
MAKE_PSTR_LIST(remotetemp, F("remotetemp"), F("room temperature from remote"))
MAKE_PSTR_LIST(reducehours, F("reducehours"), F("duration for nighttemp"))
MAKE_PSTR_LIST(reduceminutes, F("reduceminutes"), F("remaining time for nightmode"))
@@ -609,67 +674,124 @@ 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)"))
MAKE_PSTR_LIST(hydrTemp, F("hydrTemp"), F("hydraulic header temperature"))
// 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(energyTotal, F("energytotal"), F("total energy"))
MAKE_PSTR_LIST(energyToday, F("energytoday"), F("total energy today"))
MAKE_PSTR_LIST(pumpMinMod, F("pumpminmod"), F("minimum pump modulation"))
MAKE_PSTR_LIST(maxFlow, F("maxflow"), F("maximum solar flow"))
MAKE_PSTR_LIST(solarPower, F("solarpower"), F("actual solar power"))
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"));

File diff suppressed because it is too large Load Diff

View File

@@ -19,30 +19,18 @@
#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;
// time between HA publishes
#define MQTT_HA_PUBLISH_DELAY 50
// size of queue
#define MAX_MQTT_MESSAGES 300
static constexpr uint16_t MAX_MQTT_MESSAGES = 300;
namespace emsesp {
@@ -79,24 +67,15 @@ class Mqtt {
void set_publish_time_sensor(uint16_t publish_time);
bool get_publish_onchange(uint8_t device_type);
enum Operation { PUBLISH, SUBSCRIBE };
enum Operation : uint8_t { PUBLISH, SUBSCRIBE, UNSUBSCRIBE };
enum NestedFormat : uint8_t { NESTED = 1, SINGLE };
enum HA_Climate_Format : uint8_t {
CURRENT = 1, // 1
SETPOINT, // 2
ZERO // 3
};
// 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 constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 128; // fixed, not a user setting anymore
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);
@@ -104,7 +83,6 @@ class Mqtt {
static void publish(const std::string & topic, const JsonObject & payload);
static void publish(const __FlashStringHelper * topic, const JsonObject & payload);
static void publish(const __FlashStringHelper * topic, const std::string & payload);
static void publish(const std::string & topic);
static void publish_retain(const std::string & topic, const JsonObject & payload, bool retain);
static void publish_retain(const __FlashStringHelper * topic, const std::string & payload, bool retain);
static void publish_retain(const __FlashStringHelper * topic, const JsonObject & payload, bool retain);
@@ -112,31 +90,30 @@ 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,
const bool remove,
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_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_system_ha_sensor_config(uint8_t type, const __FlashStringHelper * name, const __FlashStringHelper * entity, const uint8_t uom);
static void publish_ha_climate_config(uint8_t tag, bool has_roomtemp, bool remove = false);
static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type);
static void show_mqtt(uuid::console::Shell & shell);
static void ha_status();
void disconnect() {
mqttClient_->disconnect();
}
#if defined(EMSESP_DEBUG)
void incoming(const char * topic, const char * payload = ""); // for testing only
#endif
@@ -165,11 +142,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 +164,24 @@ 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 bool is_nested() {
return nested_format_ == NestedFormat::NESTED;
}
static void nested_format(uint8_t nested_format) {
nested_format_ = nested_format;
}
static void ha_climate_format(uint8_t ha_climate_format) {
ha_climate_format_ = ha_climate_format;
static bool publish_single() {
return publish_single_;
}
static bool publish_single2cmd() {
return publish_single2cmd_;
}
static void publish_single(bool publish_single) {
publish_single_ = publish_single;
}
static bool ha_enabled() {
@@ -204,6 +192,14 @@ class Mqtt {
ha_enabled_ = ha_enabled;
}
static bool ha_climate_reset() {
return ha_climate_reset_;
}
static void ha_climate_reset(bool reset) {
ha_climate_reset_ = reset;
}
static bool send_response() {
return send_response_;
}
@@ -212,11 +208,11 @@ class Mqtt {
send_response_ = send_response;
}
void set_qos(uint8_t mqtt_qos) {
void set_qos(uint8_t mqtt_qos) const {
mqtt_qos_ = mqtt_qos;
}
void set_retain(bool mqtt_retain) {
void set_retain(bool mqtt_retain) const {
mqtt_retain_ = mqtt_retain;
}
@@ -224,20 +220,18 @@ class Mqtt {
return mqtt_messages_.empty();
}
static const std::string tag_to_topic(uint8_t device_type, uint8_t tag);
static 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_;
uint8_t retry_count_ = 0;
uint16_t packet_id_ = 0;
~QueuedMqttMessage() = default;
QueuedMqttMessage(uint16_t id, std::shared_ptr<MqttMessage> && content)
QueuedMqttMessage(uint32_t id, std::shared_ptr<MqttMessage> && content)
: id_(id)
, content_(std::move(content)) {
retry_count_ = 0;
packet_id_ = 0;
}
};
static std::deque<QueuedMqttMessage> mqtt_messages_;
@@ -247,7 +241,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
@@ -255,9 +249,10 @@ class Mqtt {
static std::shared_ptr<const MqttMessage> queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, bool retain);
static std::shared_ptr<const MqttMessage> queue_publish_message(const std::string & topic, const std::string & payload, bool retain);
static std::shared_ptr<const MqttMessage> queue_subscribe_message(const std::string & topic);
static std::shared_ptr<const MqttMessage> queue_unsubscribe_message(const std::string & topic);
void on_publish(uint16_t packetId);
void on_message(const char * topic, const char * payload, size_t len);
void on_publish(uint16_t packetId) const;
void on_message(const char * topic, const char * payload, size_t len) const;
void process_queue();
// function handlers for MQTT subscriptions
@@ -282,11 +277,13 @@ class Mqtt {
uint32_t last_publish_mixer_ = 0;
uint32_t last_publish_other_ = 0;
uint32_t last_publish_sensor_ = 0;
uint32_t last_publish_queue_ = 0;
static bool connecting_;
static bool initialized_;
static uint16_t mqtt_publish_fails_;
static uint32_t mqtt_publish_fails_;
static uint8_t connectcount_;
static bool ha_climate_reset_;
// settings, copied over
static std::string mqtt_base_;
@@ -300,9 +297,11 @@ 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 publish_single2cmd_;
static bool send_response_;
};

View File

@@ -24,8 +24,10 @@ uuid::log::Logger Shower::logger_{F_(shower), uuid::log::Facility::CONSOLE};
void Shower::start() {
EMSESP::webSettingsService.read([&](WebSettings & settings) {
shower_timer_ = settings.shower_timer;
shower_alert_ = settings.shower_alert;
shower_timer_ = settings.shower_timer;
shower_alert_ = settings.shower_alert;
shower_alert_trigger_ = settings.shower_alert_trigger * 60000; // convert from minutes
shower_alert_coldshot_ = settings.shower_alert_coldshot * 1000; // convert from seconds
});
set_shower_state(false, true); // turns shower to off and creates HA topic if not already done
@@ -59,18 +61,18 @@ void Shower::loop() {
LOG_DEBUG(F("[Shower] hot water still running, starting shower timer"));
}
// check if the shower has been on too long
else if ((time_now - timer_start_) > SHOWER_MAX_DURATION) {
else if ((time_now - timer_start_) > shower_alert_trigger_) {
shower_alert_start();
}
}
} else { // hot water is off
// if it just turned off, record the time as it could be a short pause
if ((timer_start_) && (timer_pause_ == 0)) {
if (timer_start_ && (timer_pause_ == 0)) {
timer_pause_ = time_now;
}
// if shower has been off for longer than the wait time
if ((timer_pause_) && ((time_now - timer_pause_) > SHOWER_PAUSE_TIME)) {
if (timer_pause_ && ((time_now - timer_pause_) > SHOWER_PAUSE_TIME)) {
// it is over the wait period, so assume that the shower has finished and calculate the total time and publish
// because its unsigned long, can't have negative so check if length is less than OFFSET_TIME
if ((timer_pause_ - timer_start_) > SHOWER_OFFSET_TIME) {
@@ -80,15 +82,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;
@@ -104,7 +97,7 @@ void Shower::loop() {
// at this point we're in the shower cold shot (doing_cold_shot_ == true)
// keep repeating until the time is up
if ((time_now - alert_timer_start_) > SHOWER_COLDSHOT_DURATION) {
if ((time_now - alert_timer_start_) > shower_alert_coldshot_) {
shower_alert_stop();
}
}
@@ -130,12 +123,20 @@ void Shower::shower_alert_start() {
// Publish to the shower_data topic
// showing whether the shower timer and alert are enabled or disabled
// and the duration of the last shower
void Shower::publish_shower_data() {
void Shower::publish_shower_data() const {
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 +166,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 +177,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

@@ -30,6 +30,8 @@ class Shower {
void set_shower_state(bool state, bool force = false);
/* unused header
*
bool shower_alert() const {
return shower_alert_;
}
@@ -45,22 +47,23 @@ class Shower {
void shower_timer(const bool shower_timer) {
shower_timer_ = shower_timer;
}
*/
private:
static uuid::log::Logger logger_;
static constexpr uint32_t SHOWER_PAUSE_TIME = 15000; // in ms. 15 seconds, max time if water is switched off & on during a shower
static constexpr uint32_t SHOWER_MIN_DURATION = 120000; // in ms. 2 minutes, before recognizing its a shower
static constexpr uint32_t SHOWER_OFFSET_TIME = 5000; // in ms. 5 seconds grace time, to calibrate actual time under the shower
static constexpr uint32_t SHOWER_COLDSHOT_DURATION = 10000; // 10 seconds for cold water before turning back hot water
static constexpr uint32_t SHOWER_MAX_DURATION = 420000; // in ms. 7 minutes, before trigger a shot of cold water
static constexpr uint32_t SHOWER_PAUSE_TIME = 15000; // in ms. 15 seconds, max time if water is switched off & on during a shower
static constexpr uint32_t SHOWER_MIN_DURATION = 120000; // in ms. 2 minutes, before recognizing its a shower
static constexpr uint32_t SHOWER_OFFSET_TIME = 5000; // in ms. 5 seconds grace time, to calibrate actual time under the shower
void publish_shower_data();
void publish_shower_data() const;
void shower_alert_start();
void shower_alert_stop();
bool shower_timer_; // true if we want to report back on shower times
bool shower_alert_; // true if we want the alert of cold water
uint32_t shower_alert_trigger_; // default 7 minutes, before trigger a shot of cold water
uint32_t shower_alert_coldshot_; // default 10 seconds for cold water before turning back hot water
bool ha_configdone_ = false; // for HA MQTT Discovery
bool shower_state_;
uint32_t timer_start_; // ms

File diff suppressed because it is too large Load Diff

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>
@@ -46,7 +45,7 @@ enum PHY_type : uint8_t { PHY_TYPE_NONE = 0, PHY_TYPE_LAN8720, PHY_TYPE_TLK110 }
class System {
public:
void start(uint32_t heap_start);
void start();
void loop();
// commands
@@ -62,31 +61,32 @@ class System {
static bool command_watch(const char * value, const int8_t id);
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_commands(const char * value, const int8_t id, JsonObject & output);
const std::string reset_reason(uint8_t cpu);
std::string reset_reason(uint8_t cpu) const;
void system_restart();
void format(uuid::console::Shell & shell);
void upload_status(bool in_progress);
bool upload_status();
void show_mem(const char * note);
void get_settings();
void reload_settings();
void wifi_tweak();
void syslog_start();
void syslog_init();
bool check_upgrade();
bool heartbeat_json(JsonObject & output);
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 void extractSettings(const char * filename, const char * section, JsonObject & output);
static bool saveSettings(const char * filename, const char * section, JsonObject & input);
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 +96,55 @@ 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_;
}
uint8_t bool_dashboard() {
return bool_dashboard_;
}
// 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;
}
void bool_dashboard(uint8_t format) {
bool_dashboard_ = 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() {
@@ -120,6 +163,16 @@ class System {
ethernet_connected_ = b;
}
void ntp_connected(bool b);
bool ntp_connected() {
// timeout 2 hours, ntp sync is normally every hour.
if ((uuid::get_uptime_sec() - ntp_last_check_ > 7201) && ntp_connected_) {
ntp_connected(false);
}
return ntp_connected_;
}
bool network_connected() {
#ifndef EMSESP_STANDALONE
return (ethernet_connected() || WiFi.isConnected());
@@ -128,13 +181,24 @@ 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);
private:
static uuid::log::Logger logger_;
static uint32_t heap_start_;
static bool restart_requested_;
// button
@@ -148,46 +212,63 @@ 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;
bool ntp_connected_ = false;
uint32_t ntp_last_check_ = 0;
// EMS-ESP settings
// copies from WebSettings class in WebSettingsService.h and loaded with reload_settings()
std::string hostname_ = FACTORY_WIFI_HOSTNAME;
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_dashboard_;
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,8 +263,8 @@ 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
void TxService::send_poll() {
// sends a 1 byte poll which is our own deviceID
void TxService::send_poll() const {
//LOG_DEBUG(F("Ack %02X"),ems_bus_id() ^ ems_mask());
if (tx_mode()) {
EMSuart::send_poll(ems_bus_id() ^ ems_mask());
@@ -358,14 +359,20 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
telegram_raw[message_p++] = telegram->message_data[i];
}
}
// make a copy of the telegram with new dest (without read-flag)
telegram_last_ = std::make_shared<Telegram>(
telegram->operation, telegram->src, dest & 0x7F, telegram->type_id, telegram->offset, telegram->message_data, telegram->message_length);
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 +380,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;
}
@@ -522,13 +536,13 @@ void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t lengt
void TxService::read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t length) {
LOG_DEBUG(F("Tx read request to device 0x%02X for type ID 0x%02X"), dest, type_id);
uint8_t message_data[1] = {EMS_MAX_TELEGRAM_LENGTH}; // request all data, 32 bytes
uint8_t message_data = (type_id > 0xFF) ? (EMS_MAX_TELEGRAM_MESSAGE_LENGTH - 2) : EMS_MAX_TELEGRAM_MESSAGE_LENGTH;
// if length set, publish result and set telegram to front
if (length) {
message_data[0] = length;
message_data = length;
EMSESP::set_read_id(type_id);
}
add(Telegram::Operation::TX_READ, dest, type_id, offset, message_data, 1, 0, length != 0);
add(Telegram::Operation::TX_READ, dest, type_id, offset, &message_data, 1, 0, length != 0);
}
// Send a raw telegram to the bus, telegram is a text string of hex values
@@ -560,7 +574,7 @@ void TxService::send_raw(const char * telegram_data) {
while (p != 0) {
if ((p = strtok(nullptr, " ,"))) {
strlcpy(value, p, sizeof(value));
uint8_t val = (uint8_t)strtol(value, 0, 16);
auto val = (uint8_t)strtol(value, 0, 16);
data[++count] = val;
}
}
@@ -577,9 +591,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"),
@@ -645,9 +663,10 @@ uint16_t TxService::post_send_query() {
if (post_typeid) {
uint8_t dest = (this->telegram_last_->dest & 0x7F);
// when set a value with large offset before and validate on same type, we have to add offset 0, 26, 52, ...
uint8_t offset = (this->telegram_last_->type_id == post_typeid) ? ((this->telegram_last_->offset / 26) * 26) : 0;
uint8_t message_data[1] = {EMS_MAX_TELEGRAM_LENGTH}; // request all data, 32 bytes
this->add(Telegram::Operation::TX_READ, dest, post_typeid, offset, message_data, 1, 0, true); // add to top/front of queue
uint8_t offset = (this->telegram_last_->type_id == post_typeid) ? ((this->telegram_last_->offset / 26) * 26) : 0;
uint8_t message_data =
(this->telegram_last_->type_id > 0xFF) ? (EMS_MAX_TELEGRAM_MESSAGE_LENGTH - 2) : EMS_MAX_TELEGRAM_MESSAGE_LENGTH; // request all data, 32 bytes
this->add(Telegram::Operation::TX_READ, dest, post_typeid, offset, &message_data, 1, 0, true); // add to top/front of queue
// read_request(telegram_last_post_send_query_, dest, 0); // no offset
LOG_DEBUG(F("Sending post validate read, type ID 0x%02X to dest 0x%02X"), post_typeid, dest);
set_post_send_query(0); // reset

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
@@ -87,11 +87,12 @@ class Telegram {
// reads a bit value from a given telegram position
bool read_bitvalue(uint8_t & value, const uint8_t index, const uint8_t bit) const {
uint8_t abs_index = (index - this->offset);
if (abs_index >= this->message_length) {
if ((abs_index >= this->message_length) || (abs_index > EMS_MAX_TELEGRAM_MESSAGE_LENGTH)) {
return false; // out of bounds
}
uint8_t val = value;
value = (uint8_t)(((this->message_data[abs_index]) >> (bit)) & 0x01);
value = (uint8_t)(((this->message_data[abs_index]) >> bit) & 0x01);
return (val != value);
}
@@ -105,14 +106,17 @@ class Telegram {
bool read_value(Value & value, const uint8_t index, uint8_t s = 0) const {
uint8_t num_bytes = (!s) ? sizeof(Value) : s;
// check for out of bounds, if so don't modify the value
if ((index < this->offset) || ((index - this->offset + num_bytes - 1) >= this->message_length)) {
auto msg_size = (index - this->offset + num_bytes - 1);
if ((index < this->offset) || (msg_size >= this->message_length) || (msg_size > EMS_MAX_TELEGRAM_MESSAGE_LENGTH)) {
return false;
}
auto val = value;
value = 0;
Value val = value;
value = 0;
for (uint8_t i = 0; i < num_bytes; i++) {
value = (value << 8) + this->message_data[index - this->offset + i]; // shift by byte
}
return (val != value);
}
@@ -120,6 +124,7 @@ class Telegram {
if ((index < this->offset) || ((index - this->offset) >= this->message_length)) {
return false;
}
uint8_t val = value;
value = this->message_data[index - this->offset] - start;
return (val != value);
@@ -166,6 +171,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 +185,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 +216,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 +245,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%
@@ -248,7 +270,7 @@ class RxService : public EMSbus {
}
};
const std::deque<QueuedRxTelegram> queue() const {
std::deque<QueuedRxTelegram> queue() const {
return rx_telegrams_;
}
@@ -283,7 +305,7 @@ class TxService : public EMSbus {
void add(const uint8_t operation, const uint8_t * data, const uint8_t length, const uint16_t validateid, const bool front = false);
void read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset = 0, const uint8_t length = 0);
void send_raw(const char * telegram_data);
void send_poll();
void send_poll() const;
void retry_tx(const uint8_t operation, const uint8_t * data, const uint8_t length);
bool is_last_tx(const uint8_t src, const uint8_t dest) const;
uint16_t post_send_query();
@@ -301,56 +323,68 @@ class TxService : public EMSbus {
telegram_last_post_send_query_ = type_id;
}
uint16_t get_post_send_query() {
uint16_t get_post_send_query() const {
return telegram_last_post_send_query_;
}
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_;
@@ -366,10 +400,14 @@ class TxService : public EMSbus {
}
};
const std::deque<QueuedTxTelegram> queue() const {
std::deque<QueuedTxTelegram> queue() const {
return tx_telegrams_;
}
bool tx_queue_empty() const {
return tx_telegrams_.empty();
}
#if defined(EMSESP_DEBUG)
static constexpr uint8_t MAXIMUM_TX_RETRIES = 0; // when compiled with EMSESP_DEBUG don't retry
#else
@@ -381,9 +419,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 +431,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..."));
@@ -189,14 +221,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 +364,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 +381,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 +413,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,38 +495,25 @@ 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);
// Mqtt::ha_enabled(false);
Mqtt::nested_format(1); // is nested
// Mqtt::nested_format(2); // not nested
run_test("boiler");
run_test("thermostat");
// run_test("solar");
// run_test("mixer");
run_test("solar");
run_test("mixer");
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,9 +534,107 @@ 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"));
emsesp::EMSESP::dallassensor_.test();
}
if (command == "dallas_full") {
shell.printfln(F("Testing adding and changing 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 == "masked") {
shell.printfln(F("Testing masked entities"));
Mqtt::ha_enabled(true);
Mqtt::send_response(false);
run_test("boiler");
shell.invoke_command("call boiler wwseltemp");
shell.invoke_command("call system publish");
// toggle mode
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice->unique_id() == 1) { // boiler
std::string a = "07wwseltemp";
emsdevice->mask_entity(a);
break;
}
}
shell.invoke_command("call boiler wwseltemp");
shell.invoke_command("call system publish");
}
if (command == "dv2") {
shell.printfln(F("Testing device value lost"));
Mqtt::ha_enabled(true);
Mqtt::send_response(false);
run_test("boiler");
shell.invoke_command("call boiler wwseltemp");
shell.invoke_command("call system publish");
// Boiler -> Me, UBAParameterWW(0x33)
// wwseltemp = goes from 52 degrees (0x34) to void (0xFF)
// it should delete the HA config topic homeassistant/sensor/ems-esp/boiler_wwseltemp/config
@@ -517,6 +644,54 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
shell.invoke_command("call system publish");
}
if (command == "api_values") {
#if defined(EMSESP_STANDALONE)
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/values");
EMSESP::webAPIService.webAPIService_get(&request);
request.url("/api/boiler/wwcirc");
EMSESP::webAPIService.webAPIService_get(&request);
request.url("/api/boiler/wwcirc/fullname");
EMSESP::webAPIService.webAPIService_get(&request);
request.url("/api/boiler/selburnpow/value");
EMSESP::webAPIService.webAPIService_get(&request);
request.url("/api/boiler/wwchargetype/writeable");
EMSESP::webAPIService.webAPIService_get(&request);
request.url("/api/boiler/flamecurr/value");
EMSESP::webAPIService.webAPIService_get(&request);
request.url("/api/boiler/flamecurr/bad");
EMSESP::webAPIService.webAPIService_get(&request);
#endif
}
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 +784,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 +797,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}";
@@ -642,28 +820,28 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
// test command parse
int8_t id_n;
const char * cmd;
const char * ncmd;
char command_s[100];
id_n = -1;
strcpy(command_s, "hc2/seltemp");
cmd = Command::parse_command_string(command_s, id_n);
shell.printfln("test cmd parse cmd=%s id=%d", cmd, id_n);
strlcpy(command_s, "hc2/seltemp", sizeof(command_s));
ncmd = Command::parse_command_string(command_s, id_n);
shell.printfln("test cmd parse cmd=%s id=%d", ncmd, id_n);
id_n = -1;
strcpy(command_s, "seltemp");
cmd = Command::parse_command_string(command_s, id_n);
shell.printfln("test cmd parse cmd=%s id=%d", cmd, id_n);
strlcpy(command_s, "seltemp", sizeof(command_s));
ncmd = Command::parse_command_string(command_s, id_n);
shell.printfln("test cmd parse cmd=%s id=%d", ncmd, id_n);
id_n = -1;
strcpy(command_s, "xyz/seltemp");
cmd = Command::parse_command_string(command_s, id_n);
shell.printfln("test cmd parse cmd=%s id=%d", cmd, id_n);
strlcpy(command_s, "xyz/seltemp", sizeof(command_s));
ncmd = Command::parse_command_string(command_s, id_n);
shell.printfln("test cmd parse cmd=%s id=%d", ncmd, id_n);
id_n = -1;
strcpy(command_s, "wwc4/seltemp");
cmd = Command::parse_command_string(command_s, id_n);
shell.printfln("test cmd parse cmd=%s id=%d", cmd, id_n);
strlcpy(command_s, "wwc4/seltemp", sizeof(command_s));
ncmd = Command::parse_command_string(command_s, id_n);
shell.printfln("test cmd parse cmd=%s id=%d", ncmd, id_n);
id_n = -1;
strcpy(command_s, "hc3_seltemp");
cmd = Command::parse_command_string(command_s, id_n);
shell.printfln("test cmd parse cmd=%s id=%d", cmd, id_n);
strlcpy(command_s, "hc3_seltemp", sizeof(command_s));
ncmd = Command::parse_command_string(command_s, id_n);
shell.printfln("test cmd parse cmd=%s id=%d", ncmd, id_n);
#endif
@@ -702,6 +880,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 +906,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 +916,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 +1306,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 +1349,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");
strlcpy(boiler_topic, "ems-esp/boiler", sizeof(boiler_topic));
strlcpy(thermostat_topic, "ems-esp/thermostat", sizeof(thermostat_topic));
strlcpy(system_topic, "ems-esp/system", sizeof(system_topic));
EMSESP::mqtt_.incoming(boiler_topic, ""); // test if ignore empty payloads
// test publishing
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\"}");
@@ -1429,6 +1614,7 @@ void Test::listDir(fs::FS & fs, const char * dirname, uint8_t levels) {
if (levels) {
listDir(fs, file.name(), levels - 1);
}
Serial.println();
} else {
Serial.print(" FILE: ");
Serial.print(file.name());
@@ -1453,6 +1639,7 @@ void Test::debug(uuid::console::Shell & shell, const std::string & cmd) {
#ifndef EMSESP_STANDALONE
if (command == "ls") {
listDir(LITTLEFS, "/", 3);
Serial.println();
}
#endif
}

View File

@@ -30,22 +30,30 @@ 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 "masked"
// #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.1"
#define EMSESP_APP_VERSION "3.4.0"

View File

@@ -24,11 +24,20 @@ 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
server->on("/api", HTTP_GET, std::bind(&WebAPIService::webAPIService_get, this, _1)); // for GETS
server->addHandler(&_apiHandler);
// for settings
server->on(GET_SETTINGS_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WebAPIService::getSettings, this, _1), AuthenticationPredicates::IS_ADMIN));
server->on(GET_CUSTOMIZATIONS_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WebAPIService::getCustomizations, this, _1), AuthenticationPredicates::IS_ADMIN));
}
// HTTP GET
@@ -46,7 +55,7 @@ void WebAPIService::webAPIService_get(AsyncWebServerRequest * request) {
// POST /{device}[/{hc|id}][/{name}]
void WebAPIService::webAPIService_post(AsyncWebServerRequest * request, JsonVariant & json) {
// if no body then treat it as a secure GET
if (not json.is<JsonObject>()) {
if (!json.is<JsonObject>()) {
webAPIService_get(request);
return;
}
@@ -60,15 +69,42 @@ void WebAPIService::webAPIService_post(AsyncWebServerRequest * request, JsonVari
// reporting back any errors
void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject & input) {
// check if the user has admin privileges (token is included and authorized)
bool is_admin;
bool is_admin = false;
EMSESP::webSettingsService.read([&](WebSettings & settings) {
Authentication authentication = _securityManager->authenticateRequest(request);
is_admin = settings.notoken_api | AuthenticationPredicates::IS_ADMIN(authentication);
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();
auto * response = new PrettyAsyncJsonResponse(false, EMSESP_JSON_SIZE_XXLARGE_DYN);
JsonObject output = response->getRoot();
// call command
uint8_t return_code = Command::process(request->url().c_str(), is_admin, input, output);
@@ -81,14 +117,24 @@ 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";
}
}
// if we're returning single values, just sent as plain text
// https://github.com/emsesp/EMS-ESP32/issues/462#issuecomment-1093877210
if (output.containsKey("api_data")) {
JsonVariant data = output["api_data"];
request->send(200, "text/plain; charset=utf-8", data.as<String>());
api_count_++;
return;
}
// send the json that came back from the command call
// FAIL, OK, NOT_FOUND, ERROR, NOT_ALLOWED = 400 (bad request), 200 (OK), 400 (not found), 400 (bad request), 401 (unauthorized)
int ret_codes[5] = {400, 200, 400, 400, 401};
@@ -96,6 +142,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);
@@ -109,4 +156,37 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject & input) {
#endif
}
void WebAPIService::getSettings(AsyncWebServerRequest * request) {
auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_XLARGE_DYN);
JsonObject root = response->getRoot();
root["type"] = "settings";
JsonObject node = root.createNestedObject("System");
node["version"] = EMSESP_APP_VERSION;
System::extractSettings(NETWORK_SETTINGS_FILE, "Network", root);
System::extractSettings(AP_SETTINGS_FILE, "AP", root);
System::extractSettings(MQTT_SETTINGS_FILE, "MQTT", root);
System::extractSettings(NTP_SETTINGS_FILE, "NTP", root);
System::extractSettings(OTA_SETTINGS_FILE, "OTA", root);
System::extractSettings(SECURITY_SETTINGS_FILE, "Security", root);
System::extractSettings(EMSESP_SETTINGS_FILE, "Settings", root);
response->setLength();
request->send(response);
}
void WebAPIService::getCustomizations(AsyncWebServerRequest * request) {
auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_XLARGE_DYN);
JsonObject root = response->getRoot();
root["type"] = "customizations";
System::extractSettings(EMSESP_CUSTOMIZATION_FILE, "Customizations", root);
response->setLength();
request->send(response);
}
} // namespace emsesp

View File

@@ -19,15 +19,9 @@
#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"
#define GET_SETTINGS_PATH "/rest/getSettings"
#define GET_CUSTOMIZATIONS_PATH "/rest/getCustomizations"
namespace emsesp {
@@ -38,11 +32,25 @@ 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);
void getSettings(AsyncWebServerRequest * request);
void getCustomizations(AsyncWebServerRequest * request);
};
} // namespace emsesp

View File

@@ -0,0 +1,280 @@
/*
* 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)
, _masked_entities_handler(MASKED_ENTITIES_PATH,
securityManager->wrapCallback(std::bind(&WebCustomizationService::masked_entities, this, _1, _2),
AuthenticationPredicates::IS_AUTHENTICATED))
, _device_entities_handler(DEVICE_ENTITIES_PATH,
securityManager->wrapCallback(std::bind(&WebCustomizationService::device_entities, this, _1, _2),
AuthenticationPredicates::IS_AUTHENTICATED)) {
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));
_masked_entities_handler.setMethod(HTTP_POST);
_masked_entities_handler.setMaxContentLength(4096);
_masked_entities_handler.setMaxJsonBufferSize(4096);
server->addHandler(&_masked_entities_handler);
_device_entities_handler.setMethod(HTTP_POST);
_device_entities_handler.setMaxContentLength(256);
server->addHandler(&_device_entities_handler);
}
// this creates the customization file, saving it 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"] = sensor.id; // 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["gpio"] = sensor.gpio; // g
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
}
// Masked entities customization
JsonArray masked_entitiesJson = root.createNestedArray("masked_entities");
for (const EntityCustomization & entityCustomization : settings.entityCustomizations) {
JsonObject entityJson = masked_entitiesJson.createNestedObject();
entityJson["product_id"] = entityCustomization.product_id;
entityJson["device_id"] = entityCustomization.device_id;
JsonArray masked_entityJson = entityJson.createNestedArray("entity_ids");
for (std::string entity_id : entityCustomization.entity_ids) {
masked_entityJson.add(entity_id);
}
}
}
// call on initialization and also when the page is saved via web UI
// 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
auto sensor = SensorCustomization();
sensor.id = sensorJson["id"].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
auto sensor = AnalogCustomization();
sensor.gpio = analogJson["gpio"];
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 with masks, building up the object class
settings.entityCustomizations.clear();
if (root["masked_entities"].is<JsonArray>()) {
for (const JsonObject masked_entities : root["masked_entities"].as<JsonArray>()) {
auto new_entry = EntityCustomization();
new_entry.product_id = masked_entities["product_id"];
new_entry.device_id = masked_entities["device_id"];
for (const JsonVariant masked_entity_id : masked_entities["entity_ids"].as<JsonArray>()) {
if (masked_entity_id.is<std::string>()) {
new_entry.entity_ids.push_back(masked_entity_id.as<std::string>()); // add entity list
}
}
settings.entityCustomizations.push_back(new_entry); // save the new object
}
}
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 list of devices used to the customization web page
void WebCustomizationService::devices(AsyncWebServerRequest * request) {
auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_LARGE_DYN);
JsonObject root = response->getRoot();
JsonArray devices = root.createNestedArray("devices");
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice->has_entities()) {
JsonObject obj = devices.createNestedObject();
obj["i"] = emsdevice->unique_id(); // a unique id
/*
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) + " (" + emsdevice->name() + ")"; // shortname - we prefix the count to make it unique
} else {
obj["s"] = emsdevice->device_type_name() + " (" + emsdevice->name() + ")";
}
*/
obj["s"] = emsdevice->device_type_name() + " (" + emsdevice->name() + ")";
}
}
response->setLength();
request->send(response);
}
// send back list of device entities
void WebCustomizationService::device_entities(AsyncWebServerRequest * request, JsonVariant & json) {
if (json.is<JsonObject>()) {
auto * response = new MsgpackAsyncJsonResponse(true, EMSESP_JSON_SIZE_XXXLARGE_DYN);
for (const auto & emsdevice : EMSESP::emsdevices) {
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 updated entities with new masks from the web UI
// saves it in the customization service
// and updates the entity list real-time
void WebCustomizationService::masked_entities(AsyncWebServerRequest * request, JsonVariant & json) {
if (json.is<JsonObject>()) {
// find the device using the unique_id
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice) {
uint8_t unique_device_id = json["id"];
if (emsdevice->unique_id() == unique_device_id) {
uint8_t product_id = emsdevice->product_id();
uint8_t device_id = emsdevice->device_id();
// and set the mask immediately for the changed entities
JsonArray entity_ids_json = json["entity_ids"];
for (const JsonVariant id : entity_ids_json) {
emsdevice->mask_entity(id.as<std::string>());
}
// Save the list to the customization file
EMSESP::webCustomizationService.update(
[&](WebCustomization & settings) {
// see if we already have a mask list for this device, if so remove it
for (auto it = settings.entityCustomizations.begin(); it != settings.entityCustomizations.end();) {
if ((*it).product_id == product_id && (*it).device_id == device_id) {
it = settings.entityCustomizations.erase(it);
break;
} else {
++it;
}
}
if (!entity_ids_json.size()) {
return StateUpdateResult::UNCHANGED; // nothing to add
}
// create a new entry for this device if there are values
EntityCustomization new_entry;
new_entry.product_id = product_id;
new_entry.device_id = device_id;
// get list of entities that have masks
std::vector<std::string> entity_ids;
emsdevice->getMaskedEntities(entity_ids);
new_entry.entity_ids = entity_ids;
// add the record and save
settings.entityCustomizations.push_back(new_entry);
return StateUpdateResult::CHANGED;
},
"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,105 @@
/*
* 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 MASKED_ENTITIES_PATH "/rest/maskedEntities"
#define RESET_CUSTOMIZATION_SERVICE_PATH "/rest/resetCustomizations"
namespace emsesp {
// Customization for dallas sensor
class SensorCustomization {
public:
std::string id;
std::string name;
uint16_t offset;
};
class AnalogCustomization {
public:
uint8_t gpio;
std::string name;
float 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 gpio == a.gpio;
}
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<std::string> entity_ids; // array of entity ids with masks
};
class WebCustomization {
public:
std::list<SensorCustomization> sensorCustomizations; // for sensor names and offsets
std::list<AnalogCustomization> analogCustomizations; // for analog sensors
std::list<EntityCustomization> entityCustomizations; // for a list of entities that have a special mask set
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 masked_entities(AsyncWebServerRequest * request, JsonVariant & json);
void device_entities(AsyncWebServerRequest * request, JsonVariant & json);
void reset_customization(AsyncWebServerRequest * request);
AsyncCallbackJsonWebHandler _masked_entities_handler, _device_entities_handler;
};
} // namespace emsesp
#endif

View File

@@ -23,69 +23,137 @@ 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) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_XLARGE_DYN);
JsonObject root = response->getRoot();
// this is used in the dashboard and contains all ems device information
// /coreData endpoint
void WebDataService::core_data(AsyncWebServerRequest * request) {
auto * 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");
char buffer[3];
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice) {
if (emsdevice && (emsdevice->device_type() != EMSdevice::DeviceType::CONTROLLER || emsdevice->count_entities() > 0)) {
JsonObject obj = devices.createNestedObject();
obj["i"] = emsdevice->unique_id(); // 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["id"] = Helpers::smallitoa(buffer, emsdevice->unique_id()); // a unique id as a string
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) {
auto * 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["id"] = sensor.id(); // id as string
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
JsonArray analogs = root.createNestedArray("analogs");
if (EMSESP::analog_enabled() && EMSESP::analogsensor_.have_sensors()) {
uint8_t count = 0;
char buffer[3];
for (const auto & sensor : EMSESP::analogsensor_.sensors()) {
// don't send if it's marked for removal
if (sensor.type() != AnalogSensor::AnalogType::MARK_DELETED) {
count++;
JsonObject obj = analogs.createNestedObject();
obj["id"] = Helpers::smallitoa(buffer, count); // needed for sorting table
obj["g"] = sensor.gpio();
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(), 0); // is optional and is a float
} else {
obj["v"] = 0; // must have a value for web sorting to work
}
}
}
}
response->setLength();
@@ -96,33 +164,39 @@ 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);
auto * response = new MsgpackAsyncJsonResponse(false, EMSESP_JSON_SIZE_XXXLARGE_DYN);
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice) {
if (emsdevice->unique_id() == json["id"]) {
// wait max 2.5 sec for updated data (post_send_delay is 2 sec)
for (uint16_t i = 0; i < (emsesp::TxService::POST_SEND_DELAY + 500) && EMSESP::wait_validate(); i++) {
delay(1);
}
EMSESP::wait_validate(0); // reset in case of timeout
#ifndef EMSESP_STANDALONE
JsonObject root = response->getRoot();
emsdevice->generate_values_json_web(root);
#endif
response->setLength();
request->send(response);
return;
if (emsdevice->unique_id() == json["id"]) {
// wait max 2.5 sec for updated data (post_send_delay is 2 sec)
for (uint16_t i = 0; i < (emsesp::TxService::POST_SEND_DELAY + 500) && EMSESP::wait_validate(); i++) {
delay(1);
}
EMSESP::wait_validate(0); // reset in case of timeout
#ifndef EMSESP_STANDALONE
JsonObject output = response->getRoot();
emsdevice->generate_values_web(output);
#endif
// #ifdef EMSESP_USE_SERIAL
// #ifdef EMSESP_DEBUG
// serializeJson(output, Serial);
// #endif
// #endif
response->setLength();
request->send(response);
return;
}
}
}
// 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>()) {
@@ -132,46 +206,44 @@ void WebDataService::write_value(AsyncWebServerRequest * request, JsonVariant &
// using the unique ID from the web find the real device type
// id is the selected device
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice) {
if (emsdevice->unique_id() == unique_id) {
// parse the command as it could have a hc or wwc prefixed, e.g. hc2/seltemp
const char * cmd = dv["c"]; // the command
int8_t id = -1; // default
cmd = Command::parse_command_string(cmd, id); // extract hc or wwc
if (emsdevice->unique_id() == unique_id) {
// parse the command as it could have a hc or wwc prefixed, e.g. hc2/seltemp
const char * cmd = dv["c"]; // the command
int8_t id = -1; // default
cmd = Command::parse_command_string(cmd, id); // extract hc or wwc
// create JSON for output
AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_SMALL);
JsonObject output = response->getRoot();
// create JSON for output
auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_SMALL);
JsonObject output = response->getRoot();
// the data could be in any format, but we need string
// authenticated is always true
JsonVariant data = dv["v"]; // the value in any format
uint8_t return_code = CommandRet::OK;
uint8_t device_type = emsdevice->device_type();
if (data.is<const char *>()) {
return_code = Command::call(device_type, cmd, data.as<const char *>(), true, id, output);
} else if (data.is<int>()) {
char s[10];
return_code = Command::call(device_type, cmd, Helpers::render_value(s, data.as<int16_t>(), 0), true, id, output);
} else if (data.is<float>()) {
char s[10];
return_code = Command::call(device_type, cmd, Helpers::render_value(s, (float)data.as<float>(), 1), true, id, output);
} else if (data.is<bool>()) {
return_code = Command::call(device_type, cmd, data.as<bool>() ? "true" : "false", true, id, output);
}
// write debug
if (return_code != CommandRet::OK) {
EMSESP::logger().err(F("Write command failed %s (%s)"), (const char *)output["message"], Command::return_code_string(return_code).c_str());
} else {
EMSESP::logger().debug(F("Write command successful"));
}
response->setCode((return_code == CommandRet::OK) ? 200 : 204);
response->setLength();
request->send(response);
return;
// the data could be in any format, but we need string
// authenticated is always true
JsonVariant data = dv["v"]; // the value in any format
uint8_t return_code = CommandRet::OK;
uint8_t device_type = emsdevice->device_type();
if (data.is<const char *>()) {
return_code = Command::call(device_type, cmd, data.as<const char *>(), true, id, output);
} else if (data.is<int>()) {
char s[10];
return_code = Command::call(device_type, cmd, Helpers::render_value(s, data.as<int16_t>(), 0), true, id, output);
} else if (data.is<float>()) {
char s[10];
return_code = Command::call(device_type, cmd, Helpers::render_value(s, data.as<float>(), 1), true, id, output);
} else if (data.is<bool>()) {
return_code = Command::call(device_type, cmd, data.as<bool>() ? "true" : "false", true, id, output);
}
// write debug
if (return_code != CommandRet::OK) {
EMSESP::logger().err(F("Write command failed %s (%s)"), (const char *)output["message"], Command::return_code_string(return_code).c_str());
} else {
EMSESP::logger().debug(F("Write command successful"));
}
response->setCode((return_code == CommandRet::OK) ? 200 : 204);
response->setLength();
request->send(response);
return;
}
}
}
@@ -180,23 +252,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 = sensor["id"]; // 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, 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 gpio = analog["gpio"]; // this is the unique key, the GPIO
std::string name = analog["name"];
float factor = analog["factor"];
float offset = analog["offset"];
uint8_t uom = analog["uom"];
int8_t type = analog["type"];
ok = EMSESP::analogsensor_.update(gpio, 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));
@@ -44,7 +45,12 @@ void WebLogService::forbidden(AsyncWebServerRequest * request) {
request->send(403);
}
// start event source service
// start the log service with INFO level
void WebLogService::begin() {
uuid::log::Logger::register_handler(this, uuid::log::Level::INFO);
}
// apply the user settings
void WebLogService::start() {
EMSESP::webSettingsService.read([&](WebSettings & settings) {
maximum_log_messages_ = settings.weblog_buffer;
@@ -84,7 +90,7 @@ void WebLogService::maximum_log_messages(size_t count) {
"local");
}
bool WebLogService::compact() {
bool WebLogService::compact() const {
return compact_;
}
@@ -150,24 +156,24 @@ void WebLogService::loop() {
}
// convert time to real offset
char * WebLogService::messagetime(char * out, const uint64_t t) {
char * WebLogService::messagetime(char * out, const uint64_t t, const size_t bufsize) {
if (!time_offset_) {
strcpy(out, uuid::log::format_timestamp_ms(t, 3).c_str());
strlcpy(out, uuid::log::format_timestamp_ms(t, 3).c_str(), bufsize);
} else {
time_t t1 = time_offset_ + t / 1000ULL;
strftime(out, 25, "%F %T", localtime(&t1));
snprintf(out, 25, "%s.%03d", out, (uint16_t)(t % 1000));
strftime(out, bufsize, "%F %T", localtime(&t1));
snprintf(out, bufsize, "%s.%03d", out, (uint16_t)(t % 1000));
}
return out;
}
// send to web eventsource
void WebLogService::transmit(const QueuedLogMessage & message) {
DynamicJsonDocument jsonDocument = DynamicJsonDocument(EMSESP_JSON_SIZE_MEDIUM);
JsonObject logEvent = jsonDocument.to<JsonObject>();
char time_string[25];
auto jsonDocument = DynamicJsonDocument(EMSESP_JSON_SIZE_MEDIUM);
JsonObject logEvent = jsonDocument.to<JsonObject>();
char time_string[25];
logEvent["t"] = messagetime(time_string, message.content_->uptime_ms);
logEvent["t"] = messagetime(time_string, message.content_->uptime_ms, sizeof(time_string));
logEvent["l"] = message.content_->level;
logEvent["i"] = message.id_;
logEvent["n"] = message.content_->name;
@@ -184,9 +190,9 @@ 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
JsonObject root = response->getRoot();
JsonArray log = root.createNestedArray("events");
auto * response = new MsgpackAsyncJsonResponse(false, EMSESP_JSON_SIZE_LARGE_DYN + 192 * log_messages_.size());
JsonObject root = response->getRoot();
JsonArray log = root.createNestedArray("events");
log_message_id_tail_ = log_messages_.back().id_;
last_transmit_ = uuid::get_uptime_ms();
@@ -194,7 +200,7 @@ void WebLogService::fetchLog(AsyncWebServerRequest * request) {
JsonObject logEvent = log.createNestedObject();
char time_string[25];
logEvent["t"] = messagetime(time_string, message.content_->uptime_ms);
logEvent["t"] = messagetime(time_string, message.content_->uptime_ms, sizeof(time_string));
logEvent["l"] = message.content_->level;
logEvent["i"] = message.id_;
logEvent["n"] = message.content_->name;
@@ -207,7 +213,7 @@ void WebLogService::fetchLog(AsyncWebServerRequest * request) {
// sets the values like level after a POST
void WebLogService::setValues(AsyncWebServerRequest * request, JsonVariant & json) {
if (not json.is<JsonObject>()) {
if (!json.is<JsonObject>()) {
return;
}
@@ -227,11 +233,11 @@ void WebLogService::setValues(AsyncWebServerRequest * request, JsonVariant & jso
// return the current value settings after a GET
void WebLogService::getValues(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_SMALL);
JsonObject root = response->getRoot();
root["level"] = log_level();
root["max_messages"] = maximum_log_messages();
root["compact"] = compact();
auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_SMALL);
JsonObject root = response->getRoot();
root["level"] = log_level();
root["max_messages"] = maximum_log_messages();
root["compact"] = compact();
response->setLength();
request->send(response);
}

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"
@@ -39,12 +32,13 @@ class WebLogService : public uuid::log::Handler {
WebLogService(AsyncWebServer * server, SecurityManager * securityManager);
void begin();
void start();
uuid::log::Level log_level() const;
void log_level(uuid::log::Level level);
size_t maximum_log_messages() const;
void maximum_log_messages(size_t count);
bool compact();
bool compact() const;
void compact(bool compact);
void loop();
@@ -68,9 +62,10 @@ class WebLogService : public uuid::log::Handler {
void fetchLog(AsyncWebServerRequest * request);
void getValues(AsyncWebServerRequest * request);
char * messagetime(char * out, const uint64_t t);
char * messagetime(char * out, const uint64_t t, const size_t bufsize);
void setValues(AsyncWebServerRequest * request, JsonVariant & json);
void setValues(AsyncWebServerRequest * request, JsonVariant & json);
AsyncCallbackJsonWebHandler setValues_; // for POSTs
uint64_t last_transmit_ = 0; // Last transmit time

View File

@@ -37,76 +37,67 @@ WebSettingsService::WebSettingsService(AsyncWebServer * server, FS * fs, Securit
}
void WebSettings::read(WebSettings & settings, JsonObject & root) {
root["tx_mode"] = settings.tx_mode;
root["ems_bus_id"] = settings.ems_bus_id;
root["syslog_enabled"] = settings.syslog_enabled;
root["syslog_level"] = settings.syslog_level;
root["trace_raw"] = settings.trace_raw;
root["syslog_mark_interval"] = settings.syslog_mark_interval;
root["syslog_host"] = settings.syslog_host;
root["syslog_port"] = settings.syslog_port;
root["master_thermostat"] = settings.master_thermostat;
root["shower_timer"] = settings.shower_timer;
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["notoken_api"] = settings.notoken_api;
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["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["tx_mode"] = settings.tx_mode;
root["ems_bus_id"] = settings.ems_bus_id;
root["syslog_enabled"] = settings.syslog_enabled;
root["syslog_level"] = settings.syslog_level;
root["trace_raw"] = settings.trace_raw;
root["syslog_mark_interval"] = settings.syslog_mark_interval;
root["syslog_host"] = settings.syslog_host;
root["syslog_port"] = settings.syslog_port;
root["master_thermostat"] = settings.master_thermostat;
root["shower_timer"] = settings.shower_timer;
root["shower_alert"] = settings.shower_alert;
root["shower_alert_coldshot"] = settings.shower_alert_coldshot;
root["shower_alert_trigger"] = settings.shower_alert_trigger;
root["rx_gpio"] = settings.rx_gpio;
root["tx_gpio"] = settings.tx_gpio;
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["fahrenheit"] = settings.fahrenheit;
root["bool_format"] = settings.bool_format;
root["bool_dashboard"] = settings.bool_dashboard;
root["enum_format"] = settings.enum_format;
root["weblog_level"] = settings.weblog_level;
root["weblog_buffer"] = settings.weblog_buffer;
root["weblog_compact"] = settings.weblog_compact;
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
String old_board_profile = settings.board_profile;
settings.board_profile = root["board_profile"] | EMSESP_DEFAULT_BOARD_PROFILE;
std::vector<int8_t> data; // // led, dallas, rx, tx, button, phy_type, eth_power, eth_phy_addr, eth_clock_mode
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
settings.board_profile = "CUSTOM";
EMSESP::logger().info("No board profile found. Re-setting to %s", settings.board_profile.c_str());
} else {
EMSESP::logger().info("Loading 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];
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();
@@ -126,14 +117,15 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings)
prev = settings.syslog_enabled;
settings.syslog_enabled = root["syslog_enabled"] | EMSESP_DEFAULT_SYSLOG_ENABLED;
check_flag(prev, settings.syslog_enabled, ChangeFlags::SYSLOG);
prev = settings.syslog_level;
settings.syslog_level = root["syslog_level"] | EMSESP_DEFAULT_SYSLOG_LEVEL;
check_flag(prev, settings.syslog_level, ChangeFlags::SYSLOG);
prev = settings.syslog_mark_interval;
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;
@@ -143,15 +135,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;
@@ -172,6 +155,12 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings)
prev = settings.shower_alert;
settings.shower_alert = root["shower_alert"] | EMSESP_DEFAULT_SHOWER_ALERT;
check_flag(prev, settings.shower_alert, ChangeFlags::SHOWER);
prev = settings.shower_alert_trigger;
settings.shower_alert_trigger = root["shower_alert_trigger"] | EMSESP_DEFAULT_SHOWER_ALERT_TRIGGER;
check_flag(prev, settings.shower_alert_trigger, ChangeFlags::SHOWER);
prev = settings.shower_alert_coldshot;
settings.shower_alert_coldshot = root["shower_alert_coldshot"] | EMSESP_DEFAULT_SHOWER_ALERT_COLDSHOT;
check_flag(prev, settings.shower_alert_coldshot, ChangeFlags::SHOWER);
// led
prev = settings.led_gpio;
@@ -181,45 +170,76 @@ 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;
// ethernet
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);
//
// these need system restarts first before settings are activated...
//
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);
//
// without checks...
//
settings.trace_raw = root["trace_raw"] | EMSESP_DEFAULT_TRACELOG_RAW;
EMSESP::trace_raw(settings.trace_raw);
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.bool_dashboard = root["bool_dashboard"] | EMSESP_DEFAULT_BOOL_FORMAT;
EMSESP::system_.bool_dashboard(settings.bool_dashboard);
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;
}
@@ -235,15 +255,15 @@ void WebSettingsService::onUpdate() {
}
if (WebSettings::has_flags(WebSettings::ChangeFlags::UART)) {
EMSESP::init_uart();
EMSESP::uart_init();
}
if (WebSettings::has_flags(WebSettings::ChangeFlags::SYSLOG)) {
EMSESP::system_.syslog_start(); // re-start (or stop)
EMSESP::system_.syslog_init(); // re-start (or stop)
}
if (WebSettings::has_flags(WebSettings::ChangeFlags::ADC)) {
EMSESP::system_.adc_init(true); // reload settings
EMSESP::analogsensor_.start();
}
if (WebSettings::has_flags(WebSettings::ChangeFlags::BUTTON)) {
@@ -268,24 +288,22 @@ void WebSettingsService::save() {
// build the json profile to send back
void WebSettingsService::board_profile(AsyncWebServerRequest * request, JsonVariant & json) {
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;
}
auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_MEDIUM);
JsonObject root = response->getRoot();
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;
@@ -42,6 +34,8 @@ class WebSettings {
uint8_t master_thermostat;
bool shower_timer;
bool shower_alert;
uint8_t shower_alert_trigger;
uint8_t shower_alert_coldshot;
bool syslog_enabled;
int8_t syslog_level; // uuid::log::Level
uint32_t syslog_mark_interval;
@@ -55,38 +49,40 @@ 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 bool_dashboard;
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,25 +34,25 @@ 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) {
EMSESP::system_.send_heartbeat();
EMSESP::system_.syslog_start();
EMSESP::system_.syslog_init();
}
});
mDNS_start();
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,12 +68,12 @@ 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) {
EMSESP::system_.send_heartbeat();
EMSESP::system_.syslog_start();
EMSESP::system_.syslog_init();
}
});
EMSESP::system_.ethernet_connected(true);
@@ -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,12 +110,12 @@ 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();
EMSESP::system_.syslog_init();
mDNS_start();
break;
#endif
@@ -126,37 +126,94 @@ void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
}
void WebStatusService::webStatusService(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_MEDIUM_DYN);
JsonObject root = response->getRoot();
auto * 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["tx_mode"] = EMSESP::txservice_.tx_mode();
root["uptime"] = EMSbus::bus_uptime();
root["num_devices"] = EMSESP::count_devices(); // excluding Controller
root["num_sensors"] = EMSESP::dallassensor_.no_sensors();
root["num_analogs"] = EMSESP::analogsensor_.no_sensors();
JsonArray statsJson = root.createNestedArray("stats");
JsonObject statJson;
statJson = statsJson.createNestedObject();
statJson["id"] = "EMS Telegrams Received (Rx)";
statJson["s"] = EMSESP::rxservice_.telegram_count();
statJson["f"] = EMSESP::rxservice_.telegram_error_count();
statJson["q"] = EMSESP::rxservice_.quality();
statJson = statsJson.createNestedObject();
statJson["id"] = "EMS Reads (Tx)";
statJson["s"] = EMSESP::txservice_.telegram_read_count();
statJson["f"] = EMSESP::txservice_.telegram_read_fail_count();
statJson["q"] = EMSESP::txservice_.read_quality();
statJson = statsJson.createNestedObject();
statJson["id"] = "EMS Writes (Tx)";
statJson["s"] = EMSESP::txservice_.telegram_write_count();
statJson["f"] = EMSESP::txservice_.telegram_write_fail_count();
statJson["q"] = EMSESP::txservice_.write_quality();
statJson = statsJson.createNestedObject();
statJson["id"] = "Temperature Sensor Reads";
statJson["s"] = EMSESP::dallassensor_.reads();
statJson["f"] = EMSESP::dallassensor_.fails();
statJson["q"] = EMSESP::dallassensor_.reads() == 0 ? 100 : 100 - (uint8_t)((100 * EMSESP::dallassensor_.fails()) / EMSESP::dallassensor_.reads());
statJson = statsJson.createNestedObject();
statJson["id"] = "Analog Sensor Reads";
statJson["s"] = EMSESP::analogsensor_.reads();
statJson["f"] = EMSESP::analogsensor_.fails();
statJson["q"] = EMSESP::analogsensor_.reads() == 0 ? 100 : 100 - (uint8_t)((100 * EMSESP::analogsensor_.fails()) / EMSESP::analogsensor_.reads());
statJson = statsJson.createNestedObject();
statJson["id"] = "MQTT Publishes";
statJson["s"] = Mqtt::publish_count();
statJson["f"] = Mqtt::publish_fails();
statJson["q"] = Mqtt::publish_count() == 0 ? 100 : 100 - (Mqtt::publish_fails() * 100) / (Mqtt::publish_count() + Mqtt::publish_fails());
statJson = statsJson.createNestedObject();
statJson["id"] = "API Calls";
statJson["s"] = WebAPIService::api_count(); // + WebAPIService::api_fails();
statJson["f"] = WebAPIService::api_fails();
statJson["q"] = WebAPIService::api_count() == 0 ? 100 : 100 - (WebAPIService::api_fails() * 100) / (WebAPIService::api_count() + WebAPIService::api_fails());
response->setLength();
request->send(response);
}
// start the multicast UDP service so EMS-ESP is discoverable via .local
void WebStatusService::mDNS_start() {
void WebStatusService::mDNS_start() const {
#ifndef EMSESP_STANDALONE
if (!MDNS.begin(EMSESP::system_.hostname().c_str())) {
EMSESP::logger().warning(F("Failed to start mDNS responder service"));
return;
}
MDNS.end();
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) {
if (networkSettings.enableMDNS) {
if (!MDNS.begin(EMSESP::system_.hostname().c_str())) {
EMSESP::logger().warning(F("Failed to start mDNS responder service"));
return;
}
std::string address_s = EMSESP::system_.hostname() + ".local";
std::string address_s = EMSESP::system_.hostname() + ".local";
MDNS.addService("http", "tcp", 80); // add our web server and rest API
MDNS.addService("telnet", "tcp", 23); // add our telnet console
MDNS.addService("http", "tcp", 80); // add our web server and rest API
MDNS.addService("telnet", "tcp", 23); // add our telnet console
MDNS.addServiceTxt("http", "tcp", "version", EMSESP_APP_VERSION);
MDNS.addServiceTxt("http", "tcp", "address", address_s.c_str());
MDNS.addServiceTxt("http", "tcp", "version", EMSESP_APP_VERSION);
MDNS.addServiceTxt("http", "tcp", "address", address_s.c_str());
EMSESP::logger().info(F("mDNS responder service started"));
}
});
#else
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) {
if (networkSettings.enableMDNS) {
EMSESP::logger().info(F("mDNS responder service started"));
}
});
#endif
EMSESP::logger().info(F("mDNS responder service started"));
}
} // namespace emsesp

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 {
@@ -38,7 +32,7 @@ class WebStatusService {
private:
void webStatusService(AsyncWebServerRequest * request);
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
void mDNS_start();
void mDNS_start() const;
};
} // namespace emsesp