Merge branch 'dev'

This commit is contained in:
Proddy
2023-08-13 14:32:41 +02:00
589 changed files with 42115 additions and 37308 deletions

View File

@@ -128,7 +128,8 @@ void AnalogSensor::reload() {
[&](const char * value, const int8_t id) { return command_setvalue(value, sensor.gpio); },
sensor.type == AnalogType::COUNTER ? FL_(counter)
: sensor.type == AnalogType::DIGITAL_OUT ? FL_(digital_out)
: FL_(pwm));
: FL_(pwm),
CommandFlag::ADMIN_ONLY);
}
}
return true;
@@ -259,7 +260,7 @@ void AnalogSensor::measure() {
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 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
@@ -273,7 +274,7 @@ void AnalogSensor::measure() {
} 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
} 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);
@@ -300,7 +301,8 @@ void AnalogSensor::loop() {
}
// update analog information name and offset
bool AnalogSensor::update(uint8_t gpio, const std::string & name, double offset, double factor, uint8_t uom, int8_t type) {
// a type of -1 is used to delete the sensor
bool AnalogSensor::update(uint8_t gpio, const std::string & name, double offset, double factor, uint8_t uom, int8_t type, bool deleted) {
boolean found_sensor = false; // see if we can find the sensor in our customization list
EMSESP::webCustomizationService.update(
@@ -312,7 +314,7 @@ bool AnalogSensor::update(uint8_t gpio, const std::string & name, double offset,
if (AnalogCustomization.gpio == gpio) {
found_sensor = true; // found the record
// see if it's marked for deletion
if (type == AnalogType::MARK_DELETED) {
if (deleted) {
LOG_DEBUG("Removing analog sensor GPIO %02d", gpio);
settings.analogCustomizations.remove(AnalogCustomization);
} else {
@@ -379,7 +381,7 @@ void AnalogSensor::publish_sensor(const Sensor & sensor) const {
snprintf(topic, sizeof(topic), "%s%s/%s", F_(analogsensor), "_data", sensor.name().c_str());
}
char payload[10];
Mqtt::publish(topic, Helpers::render_value(payload, sensor.value(), 2)); // always publish as doubles
Mqtt::queue_publish(topic, Helpers::render_value(payload, sensor.value(), 2)); // always publish as doubles
}
}
@@ -388,14 +390,14 @@ void AnalogSensor::remove_ha_topic(const int8_t type, const uint8_t gpio) const
if (!Mqtt::ha_enabled()) {
return;
}
#ifdef EMSESP_DEBUG
LOG_DEBUG("Removing HA config for analog sensor GPIO %02d", gpio);
#endif
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
#if CONFIG_IDF_TARGET_ESP32
if (type == AnalogType::DIGITAL_OUT && gpio != 25 && gpio != 26) {
#else
if (type == AnalogType::DIGITAL_OUT)
if (type == AnalogType::DIGITAL_OUT) {
#endif
snprintf(topic, sizeof(topic), "switch/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), gpio);
} else if (type == AnalogType::DIGITAL_OUT) { // DAC
@@ -407,7 +409,7 @@ void AnalogSensor::remove_ha_topic(const int8_t type, const uint8_t gpio) const
} else {
snprintf(topic, sizeof(topic), "sensor/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), gpio);
}
Mqtt::publish_ha(topic);
Mqtt::queue_remove_topic(topic);
}
// send all sensor values as a JSON package to MQTT
@@ -442,13 +444,32 @@ void AnalogSensor::publish_values(const bool force) {
case AnalogType::PWM_2:
dataSensor["value"] = serialized(Helpers::render_value(s, sensor.value(), 2)); // double
break;
case AnalogType::DIGITAL_IN:
case AnalogType::DIGITAL_OUT:
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
dataSensor["value"] = sensor.value() != 0;
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
dataSensor["value"] = sensor.value() != 0 ? 1 : 0;
} else {
char result[12];
dataSensor["value"] = Helpers::render_boolean(result, sensor.value() != 0);
}
break;
default:
dataSensor["value"] = (uint8_t)sensor.value(); // convert to char for 1 or 0
break;
}
} else if (sensor.type() == AnalogType::DIGITAL_IN || sensor.type() == AnalogType::DIGITAL_OUT) {
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
doc[sensor.name()] = sensor.value() != 0;
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
doc[sensor.name()] = sensor.value() != 0 ? 1 : 0;
} else {
char result[12];
doc[sensor.name()] = Helpers::render_boolean(result, sensor.value() != 0);
}
} else {
// not nested
doc[sensor.name()] = sensor.value();
char s[10];
doc[sensor.name()] = serialized(Helpers::render_value(s, sensor.value(), 2));
}
// create HA config
@@ -458,22 +479,26 @@ void AnalogSensor::publish_values(const bool force) {
StaticJsonDocument<EMSESP_JSON_SIZE_MEDIUM> config;
char stat_t[50];
snprintf(stat_t, sizeof(stat_t), "%s/analogsensor_data", Mqtt::base().c_str()); // use base path
snprintf(stat_t, sizeof(stat_t), "%s/analogsensor_data", Mqtt::basename().c_str()); // use basename
config["stat_t"] = stat_t;
char val_obj[50];
char val_cond[65];
char val_cond[95];
if (Mqtt::is_nested()) {
snprintf(val_obj, sizeof(val_obj), "value_json['%02d'].value", sensor.gpio());
snprintf(val_cond, sizeof(val_cond), "value_json['%02d'] is defined", sensor.gpio());
snprintf(val_cond, sizeof(val_cond), "value_json['%02d'] is defined and %s is defined", sensor.gpio(), val_obj);
} else {
snprintf(val_obj, sizeof(val_obj), "value_json['%s']", sensor.name().c_str());
snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj);
}
config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + "}}";
char sample_val[12] = "0";
if (sensor.type() == AnalogType::DIGITAL_IN || sensor.type() == AnalogType::DIGITAL_OUT) {
Helpers::render_boolean(sample_val, false);
}
config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + " else " + sample_val + "}}";
char uniq_s[70];
if (Mqtt::entity_format() == 2) {
if (Mqtt::entity_format() == Mqtt::entityFormat::MULTI_SHORT) {
snprintf(uniq_s, sizeof(uniq_s), "%s_analogsensor_%02d", Mqtt::basename().c_str(), sensor.gpio());
} else {
snprintf(uniq_s, sizeof(uniq_s), "analogsensor_%02d", sensor.gpio());
@@ -497,7 +522,7 @@ void AnalogSensor::publish_values(const bool force) {
#if CONFIG_IDF_TARGET_ESP32
if (sensor.type() == AnalogType::DIGITAL_OUT && sensor.gpio() != 25 && sensor.gpio() != 26) {
#else
if (sensor.type() == AnalogType::DIGITAL_OUT)
if (sensor.type() == AnalogType::DIGITAL_OUT) {
#endif
snprintf(topic, sizeof(topic), "switch/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), sensor.gpio());
snprintf(command_topic, sizeof(command_topic), "%s/analogsensor/%s", Mqtt::basename().c_str(), sensor.name().c_str());
@@ -538,6 +563,17 @@ void AnalogSensor::publish_values(const bool force) {
// config["step"] = sensor.factor();
} else if (sensor.type() == AnalogType::DIGITAL_IN) {
snprintf(topic, sizeof(topic), "binary_sensor/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), sensor.gpio());
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
config["pl_on"] = true;
config["pl_off"] = false;
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
config["pl_on"] = 1;
config["pl_off"] = 0;
} else {
char result[12];
config["pl_on"] = Helpers::render_boolean(result, true);
config["pl_off"] = Helpers::render_boolean(result, false);
}
} else {
snprintf(topic, sizeof(topic), "sensor/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), sensor.gpio());
config["stat_cla"] = "measurement";
@@ -545,19 +581,17 @@ void AnalogSensor::publish_values(const bool force) {
JsonObject dev = config.createNestedObject("dev");
JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp");
ids.add(Mqtt::basename());
// add "availability" section
Mqtt::add_avty_to_doc(stat_t, config.as<JsonObject>(), val_cond);
Mqtt::publish_ha(topic, config.as<JsonObject>());
sensor.ha_registered = true;
sensor.ha_registered = Mqtt::queue_ha(topic, config.as<JsonObject>());
}
}
}
Mqtt::publish("analogsensor_data", doc.as<JsonObject>());
Mqtt::queue_publish("analogsensor_data", doc.as<JsonObject>());
}
// called from emsesp.cpp, similar to the emsdevice->get_value_info
@@ -579,7 +613,7 @@ bool AnalogSensor::get_value_info(JsonObject & output, const char * cmd, const i
}
for (const auto & sensor : sensors_) {
if (strcmp(command_s, sensor.name().c_str()) == 0 || Helpers::atoint(command_s) == sensor.gpio()) {
if (Helpers::toLower(command_s) == Helpers::toLower(sensor.name().c_str()) || Helpers::atoint(command_s) == sensor.gpio()) {
output["gpio"] = sensor.gpio();
output["name"] = sensor.name();
output["type"] = F_(number);
@@ -651,7 +685,7 @@ bool AnalogSensor::command_info(const char * value, const int8_t id, JsonObject
dataSensor["value"] = sensor.value();
} else if (id == 0) { // output values command
output[sensor.name()] = sensor.value();
} else { // if someone wants gpio numbers
} else { // if someone wants gpio numbers
char gpio_str[9];
snprintf(gpio_str, sizeof(gpio_str), "gpio_%02d", sensor.gpio());
output[gpio_str] = sensor.value();
@@ -760,7 +794,7 @@ bool AnalogSensor::command_commands(const char * value, const int8_t id, JsonObj
}
// hard coded tests
#ifdef EMSESP_DEBUG
#ifdef EMSESP_TEST
void AnalogSensor::test() {
sensors_.emplace_back(36, "test12", 0, 0.1, 17, AnalogType::ADC);
sensors_.back().set_value(12.4);
@@ -770,4 +804,4 @@ void AnalogSensor::test() {
}
#endif
} // namespace emsesp
} // namespace emsesp

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -112,8 +112,7 @@ class AnalogSensor {
~AnalogSensor() = default;
enum AnalogType : int8_t {
MARK_DELETED = -1, // mark for deletion
NOTUSED, // 0 - disabled
NOTUSED, // 0 - disabled
DIGITAL_IN,
COUNTER,
ADC,
@@ -157,10 +156,10 @@ class AnalogSensor {
return sensors_.size();
}
bool update(uint8_t gpio, const std::string & name, double offset, double factor, uint8_t uom, int8_t type);
bool update(uint8_t gpio, const std::string & name, double offset, double factor, uint8_t uom, int8_t type, bool deleted = false);
bool get_value_info(JsonObject & output, const char * cmd, const int8_t id) const;
#ifdef EMSESP_DEBUG
#if defined(EMSESP_TEST)
void test();
#endif

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -46,18 +46,12 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
if (!strncmp(path, Mqtt::base().c_str(), Mqtt::base().length())) {
char new_path[Mqtt::MQTT_TOPIC_MAX_SIZE];
strlcpy(new_path, path, sizeof(new_path));
p.parse(new_path + Mqtt::base().length() + 1); // re-parse the stripped path
p.parse(new_path + Mqtt::base().length() + 1); // re-parse the stripped path
} else {
return message(CommandRet::ERROR, "unrecognized path", output); // error
}
}
#ifdef EMSESP_DEBUG
#if defined(EMSESP_USE_SERIAL)
// Serial.println(p.path().c_str()); // dump paths, for debugging
#endif
#endif
// re-calculate new path
// if there is only a path (URL) and no body then error!
size_t num_paths = p.paths().size();
@@ -151,6 +145,40 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
data = input["value"];
}
// check if data is entity like device/hc/name/value
if (data.is<const char *>()) {
const char * d = data.as<const char *>();
if (strlen(d)) {
char * device_end = (char *)strchr(d, '/');
if (device_end != nullptr) {
char device_s[15] = {'\0'};
const char * device_p = device_s;
const char * data_p = nullptr;
strlcpy(device_s, d, device_end - d + 1);
data_p = device_end + 1;
int8_t id_d = -1;
data_p = parse_command_string(data_p, id_d);
if (data_p == nullptr) {
return CommandRet::INVALID;
}
char data_s[40];
strlcpy(data_s, Helpers::toLower(data_p).c_str(), 30);
if (strstr(data_s, "/value") == nullptr) {
strcat(data_s, "/value");
}
uint8_t device_type = EMSdevice::device_name_2_device_type(device_p);
if (CommandRet::OK != Command::call(device_type, data_s, "", true, id_d, output)) {
return CommandRet::INVALID;
}
if (!output.containsKey("api_data")) {
return CommandRet::INVALID;
}
data = output["api_data"];
output.clear();
}
}
}
// call the command based on the type
uint8_t return_code = CommandRet::ERROR;
if (data.is<const char *>()) {
@@ -166,7 +194,7 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
} else if (data.isNull()) {
return_code = Command::call(device_type, command_p, "", is_admin, id_n, output); // empty, will do a query instead
} else {
return message(CommandRet::ERROR, "cannot parse command", output); // can't process
return message(CommandRet::ERROR, "cannot parse command", output); // can't process
}
return return_code;
}
@@ -231,11 +259,14 @@ const char * Command::parse_command_string(const char * command, int8_t & id) {
id = command[2] - '1' + DeviceValueTAG::TAG_HS1 - DeviceValueTAG::TAG_HC1 + 1; //20;
command += 3;
}
// remove separator
if (command[0] == '/' || command[0] == '.' || command[0] == '_') {
command++;
}
free(lowerCmd);
// return null for empty command
if (command[0] == '\0') {
return nullptr;
@@ -273,9 +304,7 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char *
// except for system commands as this is a special device without any queryable entities (device values)
if ((device_type > EMSdevice::DeviceType::SYSTEM) && (!value || !strlen(value))) {
if (!cf || !cf->cmdfunction_json_) {
#if defined(EMSESP_DEBUG)
LOG_DEBUG("[DEBUG] Calling %s command '%s' to retrieve attributes", dname, cmd);
#endif
LOG_DEBUG("Calling %s command '%s' to retrieve attributes", dname, cmd);
return EMSESP::get_device_value_info(output, cmd, id, device_type) ? CommandRet::OK : CommandRet::ERROR; // entity = cmd
}
}
@@ -350,7 +379,8 @@ void Command::add(const uint8_t device_type, const uint8_t device_id, const char
cmdfunctions_.emplace_back(device_type, device_id, flags, cmd, cb, nullptr, description); // callback for json is nullptr
}
// same for system/dallas/analog devices with device_id 0
// add a command with no json output
// system/temperature/analog devices uses device_id 0
void Command::add(const uint8_t device_type, const char * cmd, const cmd_function_p cb, const char * const * description, uint8_t flags) {
add(device_type, 0, cmd, cb, description, flags);
}
@@ -415,7 +445,9 @@ bool Command::list(const uint8_t device_type, JsonObject & output) {
for (const auto & cf : cmdfunctions_) {
if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN) && cf.description_ && (cl == std::string(cf.cmd_))) {
if (cf.has_flags(CommandFlag::MQTT_SUB_FLAG_WW)) {
output[cl] = EMSdevice::tag_to_string(DeviceValueTAG::TAG_DEVICE_DATA_WW) + " " + Helpers::translated_word(cf.description_);
char s[100];
snprintf(s, sizeof(s), "%s %s", EMSdevice::tag_to_string(DeviceValueTAG::TAG_DEVICE_DATA_WW), Helpers::translated_word(cf.description_));
output[cl] = s;
} else {
output[cl] = Helpers::translated_word(cf.description_);
}
@@ -503,8 +535,16 @@ bool Command::device_has_commands(const uint8_t device_type) {
return true; // we always have System
}
if (device_type == EMSdevice::DeviceType::DALLASSENSOR) {
return (EMSESP::dallassensor_.have_sensors());
if (device_type == EMSdevice::DeviceType::SCHEDULER) {
return EMSESP::webSchedulerService.has_commands();
}
if (device_type == EMSdevice::DeviceType::CUSTOM) {
return (EMSESP::webEntityService.count_entities() != 0);
}
if (device_type == EMSdevice::DeviceType::TEMPERATURESENSOR) {
return (EMSESP::temperaturesensor_.have_sensors());
}
if (device_type == EMSdevice::DeviceType::ANALOGSENSOR) {
@@ -529,8 +569,11 @@ bool Command::device_has_commands(const uint8_t device_type) {
void Command::show_devices(uuid::console::Shell & shell) {
shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::SYSTEM));
if (EMSESP::dallassensor_.have_sensors()) {
shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::DALLASSENSOR));
if (EMSESP::webSchedulerService.has_commands()) {
shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::SCHEDULER));
}
if (EMSESP::temperaturesensor_.have_sensors()) {
shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::TEMPERATURESENSOR));
}
if (EMSESP::analogsensor_.have_sensors()) {
shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::ANALOGSENSOR));
@@ -559,13 +602,39 @@ void Command::show_all(uuid::console::Shell & shell) {
shell.print(COLOR_RESET);
show(shell, EMSdevice::DeviceType::SYSTEM, true);
// show sensors
if (EMSESP::dallassensor_.have_sensors()) {
// show Custom
if (EMSESP::webEntityService.has_commands()) {
shell.print(COLOR_BOLD_ON);
shell.print(COLOR_YELLOW);
shell.printf(" %s: ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::DALLASSENSOR));
shell.printf(" %s: ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::CUSTOM));
shell.println(COLOR_RESET);
shell.printf(" info: %slists all values %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_RED);
shell.println(COLOR_RESET);
shell.printf(" commands: %slists all commands %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_RED);
shell.print(COLOR_RESET);
show(shell, EMSdevice::DeviceType::DALLASSENSOR, true);
show(shell, EMSdevice::DeviceType::CUSTOM, true);
}
// show scheduler
if (EMSESP::webSchedulerService.has_commands()) {
shell.print(COLOR_BOLD_ON);
shell.print(COLOR_YELLOW);
shell.printf(" %s: ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::SCHEDULER));
shell.println(COLOR_RESET);
shell.printf(" info: %slists all values %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_RED);
shell.println(COLOR_RESET);
shell.printf(" commands: %slists all commands %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_RED);
shell.print(COLOR_RESET);
show(shell, EMSdevice::DeviceType::SCHEDULER, true);
}
// show sensors
if (EMSESP::temperaturesensor_.have_sensors()) {
shell.print(COLOR_BOLD_ON);
shell.print(COLOR_YELLOW);
shell.printf(" %s: ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::TEMPERATURESENSOR));
shell.print(COLOR_RESET);
show(shell, EMSdevice::DeviceType::TEMPERATURESENSOR, true);
}
if (EMSESP::analogsensor_.have_sensors()) {
shell.print(COLOR_BOLD_ON);

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -56,7 +56,7 @@ class Command {
struct CmdFunction {
uint8_t device_type_; // DeviceType::
uint8_t device_id_;
uint8_t flags_; // mqtt flags for command subscriptions
uint8_t flags_; // mqtt flags for command subscriptions
const char * cmd_;
cmd_function_p cmdfunction_;
cmd_json_function_p cmdfunction_json_;
@@ -107,7 +107,7 @@ class Command {
const char * const * description,
uint8_t flags = CommandFlag::MQTT_SUB_FLAG_DEFAULT);
// same for system/dallas/analog devices
// same for system/temperature/analog devices
static void add(const uint8_t device_type,
const char * cmd,
const cmd_function_p cb,

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -19,11 +19,16 @@
#ifndef EMSESP_COMMON_H
#define EMSESP_COMMON_H
// logging
#include <uuid/log.h>
using uuid::log::Level;
#if defined(EMSESP_DEBUG)
#define LOG_DEBUG(...) logger_.debug(__VA_ARGS__)
#else
#define LOG_DEBUG(...)
#endif
#define LOG_INFO(...) logger_.info(__VA_ARGS__)
#define LOG_TRACE(...) logger_.trace(__VA_ARGS__)
#define LOG_NOTICE(...) logger_.notice(__VA_ARGS__)
@@ -41,13 +46,31 @@ using string_vector = std::vector<const char *>;
// clang-format off
#define FPSTR(pstr_pointer) pstr_pointer
#define MAKE_PSTR(string_name, string_literal) static const char __pstr__##string_name[] = string_literal;
#define MAKE_PSTR_WORD(string_name) MAKE_PSTR(string_name, #string_name)
#define MAKE_WORD_CUSTOM(string_name, string_literal) static const char __pstr__##string_name[] = string_literal;
#define MAKE_WORD(string_name) MAKE_WORD_CUSTOM(string_name, #string_name)
#define F_(string_name) (__pstr__##string_name)
#define FL_(list_name) (__pstr__L_##list_name)
#define MAKE_PSTR_LIST(list_name, ...) static const char * const __pstr__L_##list_name[] = {__VA_ARGS__, nullptr};
#define MAKE_PSTR_ENUM(enum_name, ...) static const char * const * __pstr__L_##enum_name[] = {__VA_ARGS__, nullptr};
#if defined(EMSESP_TEST) || defined(EMSESP_EN_ONLY)
// In testing just take one language (en) to save on Flash space
#define MAKE_WORD_TRANSLATION(list_name, en, ...) static const char * const __pstr__L_##list_name[] = {en, nullptr};
#define MAKE_TRANSLATION(list_name, shortname, en, ...) static const char * const __pstr__L_##list_name[] = {shortname, en, nullptr};
#elif defined(EMSESP_DE_ONLY)
#define MAKE_WORD_TRANSLATION(list_name, en, de, ...) static const char * const __pstr__L_##list_name[] = {de, nullptr};
#define MAKE_TRANSLATION(list_name, shortname, en, de, ...) static const char * const __pstr__L_##list_name[] = {shortname, de, nullptr};
#else
#define MAKE_WORD_TRANSLATION(list_name, ...) static const char * const __pstr__L_##list_name[] = {__VA_ARGS__, nullptr};
#define MAKE_TRANSLATION(list_name, ...) static const char * const __pstr__L_##list_name[] = {__VA_ARGS__, nullptr};
#endif
#define MAKE_NOTRANSLATION(list_name, ...) static const char * const __pstr__L_##list_name[] = {__VA_ARGS__, nullptr};
// fixed strings, no translations
#define MAKE_ENUM_FIXED(enum_name, ...) static const char * const __pstr__L_##enum_name[] = {__VA_ARGS__, nullptr};
// with translations
#define MAKE_ENUM(enum_name, ...) static const char * const * __pstr__L_##enum_name[] = {__VA_ARGS__, nullptr};
// clang-format on

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -26,85 +26,47 @@
#include "system.h"
#include "mqtt.h"
using uuid::console::Commands;
using uuid::console::Shell;
static constexpr uint32_t INVALID_PASSWORD_DELAY_MS = 2000;
namespace emsesp {
using LogLevel = ::uuid::log::Level;
using LogFacility = ::uuid::log::Facility;
#ifdef LOCAL
#undef LOCAL
#endif
namespace emsesp {
enum CommandFlags : uint8_t { USER = 0, ADMIN = (1 << 0), LOCAL = (1 << 1) };
enum ShellContext : uint8_t {
MAIN = 0,
SYSTEM,
};
enum ShellContext : uint8_t { MAIN = 0, SYSTEM, END };
class EMSESPShell : virtual public uuid::console::Shell {
class EMSESP;
class EMSESPShell : public uuid::console::Shell {
public:
~EMSESPShell() override = default;
virtual std::string console_name() = 0;
static std::shared_ptr<uuid::console::Commands> commands;
static std::shared_ptr<EMSESPShell> shell;
static void main_help_function(Shell & shell, const std::vector<std::string> & arguments);
static void main_exit_function(Shell & shell, const std::vector<std::string> & arguments);
EMSESP & emsesp_;
protected:
EMSESPShell();
EMSESPShell(EMSESP & emsesp, Stream & stream, unsigned int context, unsigned int flags);
static std::shared_ptr<uuid::console::Commands> commands_;
// our custom functions for Shell
void started() override;
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;
private:
void add_console_commands();
bool console_commands_loaded_ = false; // set to true when the initial commands are loaded
std::string console_hostname_;
};
class EMSESPStreamConsole : public uuid::console::StreamConsole, public EMSESPShell {
public:
EMSESPStreamConsole(Stream & stream, bool local);
EMSESPStreamConsole(Stream & stream, const IPAddress & addr, uint16_t port);
~EMSESPStreamConsole() override;
std::string console_name() override;
private:
static std::vector<bool> ptys_;
std::string name_;
size_t pty_;
IPAddress addr_;
uint16_t port_;
};
class Console {
public:
void loop();
void start_serial();
void start_telnet();
uuid::log::Level log_level();
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
#endif

50
src/console_stream.h Normal file
View File

@@ -0,0 +1,50 @@
/*
* mcu-app - Microcontroller application framework
* Copyright 2022 Simon Arlott
*
* 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
#include <uuid/console.h>
#include "console.h"
namespace emsesp {
class EMSESPConsole : public EMSESPShell {
public:
EMSESPConsole(EMSESP & emsesp, Stream & stream, bool local);
#ifndef ENV_NATIVE
EMSESPConsole(EMSESP & emsesp, Stream & stream, const IPAddress & addr, uint16_t port);
#endif
~EMSESPConsole() override;
std::string console_name();
private:
#ifndef EMSESP_STANDALONE
static std::vector<bool> ptys_;
#endif
std::string name_;
#ifndef EMSESP_STANDALONE
size_t pty_;
IPAddress addr_;
uint16_t port_;
#endif
};
} // namespace emsesp

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -61,10 +61,6 @@
#define EMSESP_DEFAULT_TRACELOG_RAW false
#endif
#ifndef EMSESP_DEFAULT_BOILER_HEATINGOFF
#define EMSESP_DEFAULT_BOILER_HEATINGOFF false
#endif
#ifndef EMSESP_DEFAULT_SHOWER_TIMER
#define EMSESP_DEFAULT_SHOWER_TIMER false
#endif
@@ -173,6 +169,10 @@
#define EMSESP_DEFAULT_DISCOVERY_PREFIX "homeassistant"
#endif
#ifndef EMSESP_DEFAULT_DISCOVERY_TYPE
#define EMSESP_DEFAULT_DISCOVERY_TYPE 0 // HA
#endif
#ifndef EMSESP_DEFAULT_PUBLISH_SINGLE
#define EMSESP_DEFAULT_PUBLISH_SINGLE false
#endif

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -32,7 +32,7 @@
{115, DeviceType::BOILER, "Topline/GB162", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{121, DeviceType::BOILER, "Cascade MCM10", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{122, DeviceType::BOILER, "Proline", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{123, DeviceType::BOILER, "GBx72/Trendline/Cerapur/Greenstar Si/27i-30i", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{123, DeviceType::BOILER, "GBx72/Trendline/Cerapur/Greenstar Si", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{131, DeviceType::BOILER, "GB212", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{132, DeviceType::BOILER, "GC7000F", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{133, DeviceType::BOILER, "Logano GB125/KB195i/Logamatic MC110", DeviceFlags::EMS_DEVICE_FLAG_NONE},
@@ -48,6 +48,7 @@
{208, DeviceType::BOILER, "Logamax Plus/GB192/Condens GC9000/Greenstar ErP", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{210, DeviceType::BOILER, "Cascade MC400", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{211, DeviceType::BOILER, "EasyControl Adapter", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{219, DeviceType::BOILER, "Greenstar HIU", DeviceFlags::EMS_DEVICE_FLAG_HIU},
{234, DeviceType::BOILER, "Logamax Plus GB122/Condense 2300", DeviceFlags::EMS_DEVICE_FLAG_NONE},
// Controllers - 0x09 / 0x10 / 0x50
@@ -96,9 +97,10 @@
{158, DeviceType::THERMOSTAT, "RC300/RC310/Moduline 3000/1010H/CW400/Sense II/HPC410", DeviceFlags::EMS_DEVICE_FLAG_RC300}, // 0x10
{165, DeviceType::THERMOSTAT, "RC100/Moduline 1000/1010", DeviceFlags::EMS_DEVICE_FLAG_RC100}, // 0x18, 0x38
{172, DeviceType::THERMOSTAT, "Rego 2000/3000", DeviceFlags::EMS_DEVICE_FLAG_RC300}, // 0x10
{215, DeviceType::THERMOSTAT, "Comfort RF", DeviceFlags::EMS_DEVICE_FLAG_CRF | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18
{216, DeviceType::THERMOSTAT, "CRF200S", DeviceFlags::EMS_DEVICE_FLAG_CRF | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18
{246, DeviceType::THERMOSTAT, "Comfort+2RF", DeviceFlags::EMS_DEVICE_FLAG_CRF | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18
{253, DeviceType::THERMOSTAT, "Rego 3000", DeviceFlags::EMS_DEVICE_FLAG_RC300}, // 0x10
{253, DeviceType::THERMOSTAT, "Rego 3000/UI800", DeviceFlags::EMS_DEVICE_FLAG_RC300}, // 0x10
// Thermostat - Sieger - 0x10 / 0x17
{ 66, DeviceType::THERMOSTAT, "ES72/RC20", DeviceFlags::EMS_DEVICE_FLAG_RC20_N}, // 0x17 or remote
@@ -121,7 +123,7 @@
// Thermostat remote - 0x38
{ 3, DeviceType::THERMOSTAT, "RT800", DeviceFlags::EMS_DEVICE_FLAG_RC100H},
{200, DeviceType::THERMOSTAT, "RC100H", DeviceFlags::EMS_DEVICE_FLAG_RC100H},
{249, DeviceType::THERMOSTAT, "TR120RF", DeviceFlags::EMS_DEVICE_FLAG_RC100H},
{249, DeviceType::THERMOSTAT, "TR120RF/CR20RF", DeviceFlags::EMS_DEVICE_FLAG_RC100H},
// Solar Modules - 0x30 (for solar), 0x2A, 0x41 (for ww)
{ 73, DeviceType::SOLAR, "SM10", DeviceFlags::EMS_DEVICE_FLAG_SM10},
@@ -148,6 +150,9 @@
// Heat Pumps - 0x53
{248, DeviceType::HEATPUMP, "Hybrid Manager HM200", DeviceFlags::EMS_DEVICE_FLAG_NONE},
// Ventilation - 0x51
{231, DeviceType::VENTILATION, "Logavent HRV176", DeviceFlags::EMS_DEVICE_FLAG_NONE},
// Heatsource - 0x60
{228, DeviceType::HEATSOURCE, "AM200", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // alternative heatsource
@@ -175,4 +180,4 @@
// Generic - 0x40 or other with no product-id and no version
{0, DeviceType::GENERIC, "unknown", DeviceFlags::EMS_DEVICE_FLAG_NONE}
// clang-format on
// clang-format on

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -27,7 +27,6 @@ uuid::log::Logger Boiler::logger_{F_(boiler), uuid::log::Facility::CONSOLE};
Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand)
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
// register values for master boiler/cascade module
// reserve_telegram_functions(25); // reserve some space for the telegram registries, to avoid memory fragmentation
// the telegram handlers...
// common for all boilers
@@ -76,8 +75,8 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_telegram_type(0x4A2, "HpInput", true, MAKE_PF_CB(process_HpInput));
register_telegram_type(0x485, "HpCooling", true, MAKE_PF_CB(process_HpCooling));
register_telegram_type(0x486, "HpInConfig", true, MAKE_PF_CB(process_HpInConfig));
register_telegram_type(0x492, "HpHeaterConfig", true, MAKE_PF_CB(process_HpHeaterConfig));
register_telegram_type(0x492, "HpHeaterConfig", true, MAKE_PF_CB(process_HpHeaterConfig));
register_telegram_type(0x488, "HPValve", true, MAKE_PF_CB(process_HpValve));
register_telegram_type(0x484, "HPSilentMode", true, MAKE_PF_CB(process_HpSilentMode));
register_telegram_type(0x48B, "HPPumps", true, MAKE_PF_CB(process_HpPumps));
@@ -87,6 +86,26 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_telegram_type(0x49D, "HPSettings3", true, MAKE_PF_CB(process_HpSettings3));
}
if (model() == EMSdevice::EMS_DEVICE_FLAG_HIU) {
register_telegram_type(0x772, "HIUSettings", false, MAKE_PF_CB(process_HIUSettings));
register_telegram_type(0x779, "HIUMonitor", false, MAKE_PF_CB(process_HIUMonitor));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&keepWarmTemp_,
DeviceValueType::UINT,
FL_(keepWarmTemp),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_keepWarmTemp));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&setReturnTemp_,
DeviceValueType::UINT,
FL_(setReturnTemp),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_returnTemp));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &cwFlowRate_, DeviceValueType::USHORT, FL_(cwFlowRate), DeviceValueUOM::LMIN);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &netFlowTemp_, DeviceValueType::USHORT, FL_(netFlowTemp), DeviceValueUOM::DEGREES);
}
/*
* Hybrid heatpump with telegram 0xBB is readable and writeable in boiler and thermostat
* thermostat always overwrites settings in boiler
@@ -100,12 +119,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
// reset is a command uses a dummy variable which is always zero, shown as blank, but provides command enum options
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &reset_, DeviceValueType::CMD, FL_(enum_reset), FL_(reset), DeviceValueUOM::NONE, MAKE_CF_CB(set_reset));
has_update(reset_, 0);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&forceHeatingOff_,
DeviceValueType::BOOL,
FL_(forceHeatingOff),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_forceHeatingOff));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &heatingActive_, DeviceValueType::BOOL, FL_(heatingActive), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &tapwaterActive_, DeviceValueType::BOOL, FL_(tapwaterActive), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &selFlowTemp_, DeviceValueType::UINT, FL_(selFlowTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_flow_temp));
@@ -576,7 +590,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
FL_(tempDiffHeat),
DeviceValueUOM::K,
MAKE_CF_CB(set_tempDiffHeat),
3,
2,
10);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&tempDiffCool_,
@@ -585,7 +599,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
FL_(tempDiffCool),
DeviceValueUOM::K,
MAKE_CF_CB(set_tempDiffCool),
3,
2,
10);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &vp_cooling_, DeviceValueType::BOOL, FL_(vp_cooling), DeviceValueUOM::NONE, MAKE_CF_CB(set_vp_cooling));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &heatCable_, DeviceValueType::BOOL, FL_(heatCable), DeviceValueUOM::NONE, MAKE_CF_CB(set_heatCable));
@@ -839,7 +853,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
// Check if hot tap water or heating is active
// Values will always be posted first time as heatingActive_ and tapwaterActive_ will have values EMS_VALUE_BOOL_NOTSET
void Boiler::check_active(const bool force) {
void Boiler::check_active() {
if (!Helpers::hasValue(boilerState_)) {
return;
}
@@ -850,10 +864,10 @@ void Boiler::check_active(const bool force) {
// check if heating is active, bits 2 and 4 must be set
b = ((boilerState_ & 0x09) == 0x09);
val = b ? EMS_VALUE_BOOL_ON : EMS_VALUE_BOOL_OFF;
if (heatingActive_ != val || force) {
if (heatingActive_ != val) {
heatingActive_ = val;
char s[12];
Mqtt::publish(F_(heating_active), Helpers::render_boolean(s, b));
Mqtt::queue_publish(F_(heating_active), Helpers::render_boolean(s, b));
}
// check if we can use tapactivated in flow systems
@@ -874,17 +888,12 @@ void Boiler::check_active(const bool force) {
}
val = b ? EMS_VALUE_BOOL_ON : EMS_VALUE_BOOL_OFF;
if (tapwaterActive_ != val || force) {
if (tapwaterActive_ != val) {
tapwaterActive_ = val;
char s[12];
Mqtt::publish(F_(tapwater_active), Helpers::render_boolean(s, b));
Mqtt::queue_publish(F_(tapwater_active), Helpers::render_boolean(s, b));
EMSESP::tap_water_active(b); // let EMS-ESP know, used in the Shower class
}
if (!Helpers::hasValue(forceHeatingOff_, EMS_VALUE_BOOL)) {
EMSESP::webSettingsService.read([&](WebSettings & settings) { forceHeatingOff_ = (settings.boiler_heatingoff || selFlowTemp_ == 0) ? 1 : 0; });
has_update(&forceHeatingOff_);
}
}
// 0x18
@@ -971,13 +980,13 @@ void Boiler::process_UBASettingsWW(std::shared_ptr<const Telegram> telegram) {
// Boiler(0x08) -> Me(0x0B), UBAParameterWW(0x33), data: 08 FF 30 FB FF 28 FF 07 46 00 00
void Boiler::process_UBAParameterWW(std::shared_ptr<const Telegram> telegram) {
// has_bitupdate(telegram, wwEquipt_,0,3); // 8=boiler has ww
has_update(telegram, wwActivated_, 1); // 0xFF means on
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); // 0=off, 1=1x3min 6=6x3min 7=continuous
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); // 0=off, 1=1x3min 6=6x3min 7=continuous
has_update(telegram, wwDisinfectionTemp_, 8);
has_bitupdate(telegram, wwChargeType_, 10, 0); // 0 = charge pump, 0xff = 3-way valve
@@ -1043,6 +1052,8 @@ void Boiler::process_UBAMonitorFastPlus(std::shared_ptr<const Telegram> telegram
//has_update(telegram, temperatur_, 13); // unknown temperature
//has_update(telegram, temperatur_, 27); // unknown temperature
has_update(telegram, exhaustTemp_, 31);
// read 3 char service code / installation status as appears on the display
if ((telegram->message_length > 3) && (telegram->offset == 0)) {
char serviceCode[4] = {0};
@@ -1078,18 +1089,13 @@ void Boiler::process_UBAMonitorSlow(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, outdoorTemp_, 0);
has_update(telegram, boilTemp_, 2);
has_update(telegram, exhaustTemp_, 4);
has_update(telegram, switchTemp_, 25); // only if there is a mixer module present
has_update(telegram, switchTemp_, 25); // only if there is a mixer module present
has_update(telegram, heatingPumpMod_, 9);
has_update(telegram, burnStarts_, 10, 3); // force to 3 bytes
has_update(telegram, burnWorkMin_, 13, 3); // force to 3 bytes
has_update(telegram, burn2WorkMin_, 16, 3); // force to 3 bytes
has_update(telegram, heatWorkMin_, 19, 3); // force to 3 bytes
has_update(telegram, heatStarts_, 22, 3); // force to 3 bytes
if (forceHeatingOff_ == EMS_VALUE_BOOL_ON && telegram->dest == 0) {
uint8_t data[] = {0, 0, 0, 0};
write_command(EMS_TYPE_UBASetPoints, 0, data, sizeof(data), 0);
}
}
/*
@@ -1112,7 +1118,7 @@ void Boiler::process_UBAMonitorSlowPlus(std::shared_ptr<const Telegram> telegram
has_bitupdate(telegram, ignWork_, 2, 3);
has_bitupdate(telegram, heatingPump_, 2, 5);
has_bitupdate(telegram, wwCirc_, 2, 7);
has_update(telegram, exhaustTemp_, 6);
// has_update(telegram, exhaustTemp_, 6); // Disabled until verified as valid location, see #1147.
has_update(telegram, burnStarts_, 10, 3); // force to 3 bytes
has_update(telegram, burnWorkMin_, 13, 3); // force to 3 bytes
has_update(telegram, burn2WorkMin_, 16, 3); // force to 3 bytes
@@ -1120,11 +1126,6 @@ void Boiler::process_UBAMonitorSlowPlus(std::shared_ptr<const Telegram> telegram
has_update(telegram, heatStarts_, 22, 3); // force to 3 bytes
has_update(telegram, heatingPumpMod_, 25);
// temperature measurements at 4, see #620
if (forceHeatingOff_ == EMS_VALUE_BOOL_ON && telegram->dest == 0) {
uint8_t data[] = {0, 0, 0, 0};
write_command(EMS_TYPE_UBASetPoints, 0, data, sizeof(data), 0);
}
}
/*
@@ -1310,28 +1311,36 @@ void Boiler::process_HpInput(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, hpInput[3].state, 5);
}
// Heatpump inputs settings- type 0x486
// Heatpump inputs settings- type 0x486 (https://github.com/emsesp/EMS-ESP32/issues/600)
// Boiler(0x08) -> All(0x00), ?(0x0486), data: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
// Boiler(0x08) -> All(0x00), ?(0x0486), data: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 01 00 00 00 00 00 (offset 25)
// Boiler(0x08) -> All(0x00), ?(0x0486), data: 00 00 (offset 51)
void Boiler::process_HpInConfig(std::shared_ptr<const Telegram> telegram) {
char option[12];
for (uint8_t i = 0; i < 2; i++) {
for (uint8_t j = 0; j < 11; j++) {
char option[16];
// inputs 1,2,3 <inv>[<evu1><evu2><evu3><comp><aux><cool><heat><dhw><pv><prot><pres><mod>]
uint8_t index[] = {0, 3, 6, 9, 12, 15, 18, 21, 24, 39, 36, 30, 27};
for (uint8_t i = 0; i < 3; i++) {
for (uint8_t j = 0; j < 12; j++) {
option[j] = hpInput[i].option[j] - '0';
telegram->read_value(option[j], j * 4 + i);
option[j] = option[j] ? '1' : '0';
telegram->read_value(option[j], index[j] + i);
option[j] = option[j] == 1 ? '1' : '0';
}
option[11] = '\0'; // terminate string
has_update(hpInput[i].option, option, 12);
option[12] = atoi(&hpInput[i].option[12]);
telegram->read_value(option[12], 27 + i); // modulation
Helpers::smallitoa(&option[12], (uint16_t)option[12]);
has_update(hpInput[i].option, option, 16);
}
// input 4 <inv>[<comp><aux><cool><heat><dhw><pv><prot><pres><mod>]
uint8_t index4[] = {42, 43, 44, 45, 46, 47, 52, 50, 49, 48};
for (uint8_t j = 0; j < 9; j++) {
option[j] = hpInput[3].option[j] - '0';
telegram->read_value(option[j], 42 + j);
option[j] = option[j] ? '1' : '0';
telegram->read_value(option[j], index4[j]);
option[j] = option[j] == 1 ? '1' : '0';
}
option[9] = '\0'; // terminate string
has_update(hpInput[3].option, option, 12);
option[9] = atoi(&hpInput[3].option[9]);
telegram->read_value(option[9], 48); // modulation
Helpers::smallitoa(&option[9], (uint16_t)option[9]);
has_update(hpInput[3].option, option, 13);
}
// Boiler(0x08) -W-> Me(0x0B), HpHeaterConfig(0x0485)
@@ -1405,7 +1414,7 @@ void Boiler::process_UBAErrorMessage(std::shared_ptr<const Telegram> telegram) {
// data: displaycode(2), errornumber(2), year, month, hour, day, minute, duration(2), src-addr
if (telegram->message_data[4] & 0x80) { // valid date
static uint32_t lastCodeDate_ = 0; // last code date
static uint32_t lastCodeDate_ = 0; // last code date
char code[3] = {0};
uint16_t codeNo = EMS_VALUE_SHORT_NOTSET;
code[0] = telegram->message_data[0];
@@ -1476,7 +1485,8 @@ void Boiler::process_UBAErrorMessage2(std::shared_ptr<const Telegram> telegram)
snprintf(&code[3], sizeof(code) - 3, "(%d) %02d.%02d.%04d %02d:%02d - now", codeNo, start_day, start_month, start_year, start_hour, start_min);
}
} else { // no clock, the uptime is stored https://github.com/emsesp/EMS-ESP32/issues/121
uint32_t starttime, endtime;
uint32_t starttime = 0;
uint32_t endtime = 0;
telegram->read_value(starttime, 11, 3);
telegram->read_value(endtime, 16, 3);
snprintf(&code[3], sizeof(code) - 3, "(%d) @uptime %d - %d min", codeNo, starttime, endtime);
@@ -1520,12 +1530,12 @@ void Boiler::process_HpSilentMode(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, wwAltOpPrioWw_, 3); // range 30-120 minutes on Buderus WSW196i
has_update(telegram, silentMode_, 10); // enum off-auto-on
has_update(telegram, minTempSilent_, 11);
has_update(telegram, hpHystHeat_, 37); // is / 5
has_update(telegram, hpHystCool_, 35); // is / 5, maybe offset swapped with pool
has_update(telegram, hpHystPool_, 33); // is / 5
has_update(telegram, hpHystHeat_, 37); // is / 5
has_update(telegram, hpHystCool_, 35); // is / 5, maybe offset swapped with pool
has_update(telegram, hpHystPool_, 33); // is / 5
has_update(telegram, hpCircPumpWw_, 46);
has_update(telegram, silentFrom_, 52); // in steps of 15 min
has_update(telegram, silentTo_, 53); // in steps of 15 min
has_update(telegram, silentFrom_, 52); // in steps of 15 min
has_update(telegram, silentTo_, 53); // in steps of 15 min
}
// Boiler(0x08) -B-> All(0x00), ?(0x0488), data: 8E 00 00 00 00 00 01 03
@@ -1546,7 +1556,7 @@ void Boiler::process_HpAdditionalHeater(std::shared_ptr<const Telegram> telegram
has_update(telegram, manDefrost_, 0); // off/on
has_update(telegram, auxHeaterOnly_, 1);
has_update(telegram, auxHeaterOff_, 2);
has_update(telegram, auxHeatMode_, 4); // eco/comfort
has_update(telegram, auxHeatMode_, 4); // eco/comfort
has_update(telegram, tempParMode_, 5);
has_update(telegram, auxMaxLimit_, 14); // is * 10
has_update(telegram, auxLimitStart_, 15); // is * 10
@@ -1580,6 +1590,39 @@ void Boiler::process_HpSettings3(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, elHeatStep3_, 9);
}
// HIU unit
// boiler(0x08) -B-> All(0x00), ?(0x0779), data: 06 05 01 01 AD 02 EF FF FF 00 00 7F FF
void Boiler::process_HIUMonitor(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, netFlowTemp_, 5); // is * 10
has_update(telegram, cwFlowRate_, 9); // is * 10
}
// Boiler(0x08) -W-> ME(0x0x), ?(0x0772), data: 00 00 00 00 00
void Boiler::process_HIUSettings(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, keepWarmTemp_, 1);
has_update(telegram, setReturnTemp_, 2);
}
// HIU Settings
bool Boiler::set_keepWarmTemp(const char * value, const int8_t id) {
int v;
if (!Helpers::value2temperature(value, v)) {
return false;
}
write_command(0x772, 1, v, 0x772);
return true;
}
bool Boiler::set_returnTemp(const char * value, const int8_t id) {
int v;
if (!Helpers::value2temperature(value, v)) {
return false;
}
write_command(0x772, 2, v, 0x772);
return true;
}
/*
* Hybrid heatpump with telegram 0xBB is readable and writeable in boiler and thermostat
* thermostat always overwrites settings in boiler
@@ -1731,9 +1774,8 @@ bool Boiler::set_flow_temp(const char * value, const int8_t id) {
}
// no write/verify if there is no change, see https://github.com/emsesp/EMS-ESP32/issues/654
// put it to end of tx-queue
if (v == selFlowTemp_) {
EMSESP::txservice_.add(Telegram::Operation::TX_WRITE, device_id(), EMS_TYPE_UBASetPoints, 0, (uint8_t *) &v, 1, 0, false);
EMSESP::txservice_.add(Telegram::Operation::TX_WRITE, device_id(), EMS_TYPE_UBASetPoints, 0, (uint8_t *)&v, 1, 0, false);
return true;
}
@@ -2322,38 +2364,41 @@ bool Boiler::set_emergency_ops(const char * value, const int8_t id) {
}
bool Boiler::set_HpInLogic(const char * value, const int8_t id) {
if (id == 0 || id > 4) {
if (id == 0 || id > 4 || strlen(value) > 15) {
return false;
}
bool v;
if (Helpers::value2bool(value, v)) {
if (strlen(value) == 1 && Helpers::value2bool(value, v)) {
write_command(0x486, id == 4 ? 42 : id - 1, v ? 1 : 0, 0x486);
return true;
}
if (strlen(value) == 11 && id != 4) {
uint8_t v[11];
for (uint8_t i = 0; i < 11; i++) {
v[i] = value[i] - '0';
if (v[i] > 1) {
return false;
char option[] = {"xxxxxxxxxxxxxxx"};
strncpy(option, value, strlen(value)); // copy without termination
// inputs 1,2,3 <inv>[<evu1><evu2><evu3><comp><aux><cool><heat><dhw><pv><prot><pres><mod>]
if (id < 4) {
uint8_t index[] = {0, 3, 6, 9, 12, 15, 18, 21, 24, 39, 36, 30};
for (uint8_t i = 0; i < 12; i++) {
if (option[i] == '0' || option[i] == '1') {
write_command(0x486, index[i] + id - 1, option[i] - '0');
}
write_command(0x486, i * 3 + id - 1, v[i]);
}
if (option[12] >= '0' && option[12] <= '9') {
write_command(0x486, 27, (uint8_t)atoi(&option[12]));
}
return true;
}
// input 4
if (strlen(value) == 8 && id == 4) {
uint8_t v[11];
for (uint8_t i = 0; i < 8; i++) {
v[i] = value[i] - '0';
if (v[i] > 1) {
return false;
}
write_command(0x486, 42 + i, v[i]);
// input 4: <inv>[<comp><aux><cool><heat><dhw><pv><prot><pres><mod>]
uint8_t index4[] = {42, 43, 44, 45, 46, 47, 52, 50, 49};
for (uint8_t i = 0; i < 9; i++) {
if (option[i] == '0' || option[i] == '1') {
write_command(0x486, index4[i], option[i] - '0');
}
return true;
}
return false;
if (option[9] >= '0' && option[9] <= '9') {
write_command(0x486, 48, (uint8_t)atoi(&option[9]));
}
return true;
}
bool Boiler::set_maxHeat(const char * value, const int8_t id) {
@@ -2596,17 +2641,4 @@ bool Boiler::set_wwAltOpPrio(const char * value, const int8_t id) {
return false;
}
bool Boiler::set_forceHeatingOff(const char * value, const int8_t id) {
bool v;
if (Helpers::value2bool(value, v)) {
has_update(forceHeatingOff_, v);
if (!v && Helpers::hasValue(heatingTemp_)) {
uint8_t data[] = {heatingTemp_, (Helpers::hasValue(burnMaxPower_) ? burnMaxPower_ : (uint8_t)100), (Helpers::hasValue(pumpModMax_) ? pumpModMax_ : (uint8_t)0), 0};
write_command(EMS_TYPE_UBASetPoints, 0, data, sizeof(data), 0);
}
return true;
}
return false;
}
} // namespace emsesp

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -35,7 +35,7 @@ class Boiler : public EMSdevice {
return (flags() & 0x0F);
}
void check_active(const bool force = false);
void check_active();
uint8_t boilerState_ = EMS_VALUE_UINT_NOTSET; // Boiler state flag - FOR INTERNAL USE
@@ -87,17 +87,13 @@ class Boiler : public EMSdevice {
uint32_t wwWorkM_; // DHW minutes
int8_t wwHystOn_;
int8_t wwHystOff_;
uint16_t wwMixerTemp_; // mixing temperature
uint16_t wwCylMiddleTemp_; // Cyl middle temperature (TS3)
uint8_t wwTapActivated_; // maintenance-mode to switch DHW off
uint16_t wwMixerTemp_; // mixing temperature
uint16_t wwCylMiddleTemp_; // Cyl middle temperature (TS3)
uint16_t wwSolarTemp_;
uint8_t wwAlternatingOper_; // alternating operation on/off
uint8_t wwAltOpPrioHeat_; // alternating operation, prioritise heat time
uint8_t wwAltOpPrioWw_; // alternating operation, prioritise dhw time
// special function
uint8_t forceHeatingOff_;
uint8_t wwTapActivated_; // maintenance-mode to switch DHW off
uint8_t wwAltOpPrioHeat_; // alternating operation, prioritize heat time
uint8_t wwAltOpPrioWw_; // alternating operation, prioritize dhw time
// main
uint8_t reset_; // for reset command
@@ -212,7 +208,7 @@ class Boiler : public EMSdevice {
// Inputs
struct {
uint8_t state;
char option[12]; // logic, block_comp, block_dhw, block_heat, block_cool, overheat_protect, evu_blocktime1,2,3, block_heater, Solar
char option[16]; // logic, block_comp, block_dhw, block_heat, block_cool, overheat_protect, evu_blocktime1,2,3, block_heater, Solar, brine lowpressure, brine pump modulation
} hpInput[4];
// Heater limits
@@ -256,6 +252,12 @@ class Boiler : public EMSdevice {
uint8_t elHeatStep2_;
uint8_t elHeatStep3_;
// HIU
uint16_t cwFlowRate_; // cold water flow rate *10
uint16_t netFlowTemp_; // heat network flow temperature *10
uint8_t keepWarmTemp_;
uint8_t setReturnTemp_;
/*
// Hybrid heatpump with telegram 0xBB is readable and writeable in boiler and thermostat
// thermostat always overwrites settings in boiler
@@ -310,6 +312,12 @@ class Boiler : public EMSdevice {
void process_HpDhwSettings(std::shared_ptr<const Telegram> telegram);
void process_HpSettings2(std::shared_ptr<const Telegram> telegram);
void process_HpSettings3(std::shared_ptr<const Telegram> telegram);
// HIU
void process_HIUSettings(std::shared_ptr<const Telegram> telegram);
void process_HIUMonitor(std::shared_ptr<const Telegram> telegram);
bool set_keepWarmTemp(const char * value, const int8_t id);
bool set_returnTemp(const char * value, const int8_t id);
// commands - none of these use the additional id parameter
bool set_ww_mode(const char * value, const int8_t id);
@@ -451,8 +459,6 @@ class Boiler : public EMSdevice {
inline bool set_wwAltOpPrioWw(const char * value, const int8_t id) {
return set_wwAltOpPrio(value, 3);
}
bool set_forceHeatingOff(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);
@@ -466,4 +472,4 @@ class Boiler : public EMSdevice {
} // namespace emsesp
#endif
#endif

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -212,10 +212,11 @@ void Heatpump::process_HPSettings(std::shared_ptr<const Telegram> telegram) {
// Broadcast (0x099C), data: 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 76 00 00
// data: 00 2B 00 03 04 13 00 00 00 00 00 02 02 02 (offset 24)
void Heatpump::process_HPComp(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, hpCompSpd_, 51);
has_update(telegram, hpCompSpd_, 15);
}
// 0x999 HPFunctionTest
// HPFunctionTest(0x0999), data: 00 00 00 32 00 00 00 00 00 00 00
void Heatpump::process_HPFunctionTest(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, airPurgeMode_, 0);
has_update(telegram, heatPumpOutput_, 2);

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -52,7 +52,7 @@ class Heatpump : public EMSdevice {
uint8_t heatDrainPan_;
uint8_t heatCable_;
// HM200 temperature
// HM200 temperatures
int16_t flowTemp_; // TH1
int16_t retTemp_; // TH2
int16_t sysRetTemp_; // TH3
@@ -84,7 +84,6 @@ class Heatpump : public EMSdevice {
bool set_energyPriceEl(const char * value, const int8_t id);
bool set_energyPricePV(const char * value, const int8_t id);
bool set_switchOverTemp(const char * value, const int8_t id);
bool set_airPurgeMode(const char * value, const int8_t id);
bool set_heatPumpOutput(const char * value, const int8_t id);
bool set_coolingCircuit(const char * value, const int8_t id);

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -24,155 +24,53 @@ REGISTER_FACTORY(Heatsource, EMSdevice::DeviceType::HEATSOURCE);
Heatsource::Heatsource(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand)
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
if (device_id >= EMSdevice::EMS_DEVICE_ID_AHS1 && device_id < EMSdevice::EMS_DEVICE_ID_HS1) {
uint8_t ahs = device_id - EMSdevice::EMS_DEVICE_ID_AHS1; // heating source id, count from 0
// AM200 alternative heatsource
if (device_id == EMSdevice::EMS_DEVICE_ID_BOILER || (device_id >= EMSdevice::EMS_DEVICE_ID_AHS1 && device_id < EMSdevice::EMS_DEVICE_ID_HS1)) {
uint8_t tag = device_id == EMSdevice::EMS_DEVICE_ID_BOILER
? DeviceValueTAG::TAG_DEVICE_DATA
: DeviceValueTAG::TAG_AHS1 + device_id - EMSdevice::EMS_DEVICE_ID_AHS1; // heating source id, count from 0
register_telegram_type(0x54D, "AmTemperatures", false, MAKE_PF_CB(process_amTempMessage));
register_telegram_type(0x54E, "AmStatus", false, MAKE_PF_CB(process_amStatusMessage));
register_telegram_type(0x54F, "AmCommand", false, MAKE_PF_CB(process_amCommandMessage)); // not broadcasted, but actually not used
register_telegram_type(0x550, "AmExtra", false, MAKE_PF_CB(process_amExtraMessage));
register_telegram_type(0x54C, "AmSettings", true, MAKE_PF_CB(process_amSettingMessage)); // not broadcasted
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs,
&curFlowTemp_,
DeviceValueType::SHORT,
DeviceValueNumOp::DV_NUMOP_DIV10,
FL_(sysFlowTemp),
DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs,
&retTemp_,
DeviceValueType::SHORT,
DeviceValueNumOp::DV_NUMOP_DIV10,
FL_(sysRetTemp),
DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs,
&aFlowTemp_,
DeviceValueType::SHORT,
DeviceValueNumOp::DV_NUMOP_DIV10,
FL_(aFlowTemp),
DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs,
&aRetTemp_,
DeviceValueType::SHORT,
DeviceValueNumOp::DV_NUMOP_DIV10,
FL_(aRetTemp),
DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs,
&cylTopTemp_,
DeviceValueType::SHORT,
DeviceValueNumOp::DV_NUMOP_DIV10,
FL_(aCylTopTemp),
DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs,
&cylCenterTemp_,
DeviceValueType::SHORT,
DeviceValueNumOp::DV_NUMOP_DIV10,
FL_(aCylCenterTemp),
DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs,
&cylBottomTemp_,
DeviceValueType::SHORT,
DeviceValueNumOp::DV_NUMOP_DIV10,
FL_(aCylBottomTemp),
DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs,
&flueGasTemp_,
DeviceValueType::SHORT,
DeviceValueNumOp::DV_NUMOP_DIV10,
FL_(flueGasTemp),
DeviceValueUOM::DEGREES);
// register_device_value(DeviceValueTAG::TAG_AHS1 + ahs, &valveByPass_, DeviceValueType::BOOL, nullptr, FL_(valveByPass), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs, &valveBuffer_, DeviceValueType::UINT, FL_(valveBuffer), DeviceValueUOM::PERCENT);
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs, &valveReturn_, DeviceValueType::UINT, FL_(valveReturn), DeviceValueUOM::PERCENT);
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs, &aPumpMod_, DeviceValueType::UINT, FL_(aPumpMod), DeviceValueUOM::PERCENT);
// register_device_value(DeviceValueTAG::TAG_AHS1 + ahs, &heatSource_, DeviceValueType::BOOL, nullptr, FL_(heatSource), DeviceValueUOM::NONE);
register_device_value(tag, &curFlowTemp_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(sysFlowTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &retTemp_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(sysRetTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &aFlowTemp_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(aFlowTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &aRetTemp_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(aRetTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &cylTopTemp_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(aCylTopTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &cylCenterTemp_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(aCylCenterTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &cylBottomTemp_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(aCylBottomTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &flueGasTemp_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(flueGasTemp), DeviceValueUOM::DEGREES);
// register_device_value(tag, &valveByPass_, DeviceValueType::BOOL, nullptr, FL_(valveByPass), DeviceValueUOM::NONE);
register_device_value(tag, &valveBuffer_, DeviceValueType::UINT, FL_(valveBuffer), DeviceValueUOM::PERCENT);
register_device_value(tag, &valveReturn_, DeviceValueType::UINT, FL_(valveReturn), DeviceValueUOM::PERCENT);
register_device_value(tag, &aPumpMod_, DeviceValueType::UINT, FL_(aPumpMod), DeviceValueUOM::PERCENT);
// register_device_value(tag, &heatSource_, DeviceValueType::BOOL, nullptr, FL_(heatSource), DeviceValueUOM::NONE);
// Settings:
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs,
&vr2Config_,
DeviceValueType::ENUM,
FL_(enum_vr2Config),
FL_(vr2Config),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_vr2Config));
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs,
&ahsActivated_,
DeviceValueType::BOOL,
FL_(ahsActivated),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_ahsActivated));
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs,
&aPumpConfig_,
DeviceValueType::BOOL,
FL_(aPumpConfig),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_aPumpConfig));
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs,
&aPumpSignal_,
DeviceValueType::ENUM,
FL_(enum_aPumpSignal),
FL_(aPumpSignal),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_aPumpSignal));
register_device_value(
DeviceValueTAG::TAG_AHS1 + ahs, &aPumpMin_, DeviceValueType::UINT, FL_(aPumpMin), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_aPumpMin), 12, 50);
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs, &tempRise_, DeviceValueType::BOOL, FL_(tempRise), DeviceValueUOM::NONE, MAKE_CF_CB(set_tempRise));
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs,
&setReturnTemp_,
DeviceValueType::UINT,
FL_(setReturnTemp),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_setReturnTemp),
40,
75);
register_device_value(
DeviceValueTAG::TAG_AHS1 + ahs, &mixRuntime_, DeviceValueType::USHORT, FL_(mixRuntime), DeviceValueUOM::SECONDS, MAKE_CF_CB(set_mixRuntime), 0, 600);
register_device_value(
DeviceValueTAG::TAG_AHS1 + ahs, &setFlowTemp_, DeviceValueType::UINT, FL_(setFlowTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_setFlowTemp), 40, 75);
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs,
&bufBypass_,
DeviceValueType::ENUM,
FL_(enum_bufBypass),
FL_(bufBypass),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_bufBypass));
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs,
&bufMixRuntime_,
DeviceValueType::USHORT,
FL_(bufMixRuntime),
DeviceValueUOM::SECONDS,
MAKE_CF_CB(set_bufMixRuntime),
0,
600);
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs,
&bufConfig_,
DeviceValueType::ENUM,
FL_(enum_bufConfig),
FL_(bufConfig),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_bufConfig));
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs,
&blockMode_,
DeviceValueType::ENUM,
FL_(enum_blockMode),
FL_(blockMode),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_blockMode));
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs,
&blockTerm_,
DeviceValueType::ENUM,
FL_(enum_blockTerm),
FL_(blockTerm),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_blockTerm));
register_device_value(
DeviceValueTAG::TAG_AHS1 + ahs, &blockHyst_, DeviceValueType::INT, FL_(blockHyst), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_blockHyst), 0, 50);
register_device_value(
DeviceValueTAG::TAG_AHS1 + ahs, &releaseWait_, DeviceValueType::UINT, FL_(releaseWait), DeviceValueUOM::MINUTES, MAKE_CF_CB(set_releaseWait), 0, 240);
register_device_value(tag, &vr2Config_, DeviceValueType::ENUM, FL_(enum_vr2Config), FL_(vr2Config), DeviceValueUOM::NONE, MAKE_CF_CB(set_vr2Config));
register_device_value(tag, &ahsActivated_, DeviceValueType::BOOL, FL_(ahsActivated), DeviceValueUOM::NONE, MAKE_CF_CB(set_ahsActivated));
register_device_value(tag, &aPumpConfig_, DeviceValueType::BOOL, FL_(aPumpConfig), DeviceValueUOM::NONE, MAKE_CF_CB(set_aPumpConfig));
register_device_value(tag, &aPumpSignal_, DeviceValueType::ENUM, FL_(enum_aPumpSignal), FL_(aPumpSignal), DeviceValueUOM::NONE, MAKE_CF_CB(set_aPumpSignal));
register_device_value(tag, &aPumpMin_, DeviceValueType::UINT, FL_(aPumpMin), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_aPumpMin), 12, 50);
register_device_value(tag, &tempRise_, DeviceValueType::BOOL, FL_(tempRise), DeviceValueUOM::NONE, MAKE_CF_CB(set_tempRise));
register_device_value(tag, &setReturnTemp_, DeviceValueType::UINT, FL_(setReturnTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_setReturnTemp), 40, 75);
register_device_value(tag, &mixRuntime_, DeviceValueType::USHORT, FL_(mixRuntime), DeviceValueUOM::SECONDS, MAKE_CF_CB(set_mixRuntime), 0, 600);
register_device_value(tag, &setFlowTemp_, DeviceValueType::UINT, FL_(setFlowTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_setFlowTemp), 40, 75);
register_device_value(tag, &bufBypass_, DeviceValueType::ENUM, FL_(enum_bufBypass), FL_(bufBypass), DeviceValueUOM::NONE, MAKE_CF_CB(set_bufBypass));
register_device_value(tag, &bufMixRuntime_, DeviceValueType::USHORT, FL_(bufMixRuntime), DeviceValueUOM::SECONDS, MAKE_CF_CB(set_bufMixRuntime), 0, 600);
register_device_value(tag, &bufConfig_, DeviceValueType::ENUM, FL_(enum_bufConfig), FL_(bufConfig), DeviceValueUOM::NONE, MAKE_CF_CB(set_bufConfig));
register_device_value(tag, &blockMode_, DeviceValueType::ENUM, FL_(enum_blockMode), FL_(blockMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_blockMode));
register_device_value(tag, &blockTerm_, DeviceValueType::ENUM, FL_(enum_blockTerm), FL_(blockTerm), DeviceValueUOM::NONE, MAKE_CF_CB(set_blockTerm));
register_device_value(tag, &blockHyst_, DeviceValueType::INT, FL_(blockHyst), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_blockHyst), 0, 50);
register_device_value(tag, &releaseWait_, DeviceValueType::UINT, FL_(releaseWait), DeviceValueUOM::MINUTES, MAKE_CF_CB(set_releaseWait), 0, 240);
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs, &burner_, DeviceValueType::BOOL, FL_(burner), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs, &aPump_, DeviceValueType::BOOL, FL_(aPump), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs, &heatRequest_, DeviceValueType::UINT, FL_(heatRequest), DeviceValueUOM::PERCENT);
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs, &blockRemain_, DeviceValueType::UINT, FL_(blockRemain), DeviceValueUOM::MINUTES);
register_device_value(DeviceValueTAG::TAG_AHS1 + ahs, &blockRemainWw_, DeviceValueType::UINT, FL_(blockRemainWw), DeviceValueUOM::MINUTES);
register_device_value(tag, &burner_, DeviceValueType::BOOL, FL_(burner), DeviceValueUOM::NONE);
register_device_value(tag, &aPump_, DeviceValueType::BOOL, FL_(aPump), DeviceValueUOM::NONE);
register_device_value(tag, &heatRequest_, DeviceValueType::UINT, FL_(heatRequest), DeviceValueUOM::PERCENT);
register_device_value(tag, &blockRemain_, DeviceValueType::UINT, FL_(blockRemain), DeviceValueUOM::MINUTES);
register_device_value(tag, &blockRemainWw_, DeviceValueType::UINT, FL_(blockRemainWw), DeviceValueUOM::MINUTES);
}
// cascaded heating sources, only some values per individual heatsource (hs)

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -170,7 +170,7 @@ void Mixer::process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> teleg
void Mixer::process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, flowTempHc_, 0); // is * 10
has_bitupdate(telegram, pumpStatus_, 2, 0);
has_update(telegram, status_, 11); // temp status
has_update(telegram, status_, 11); // temp status
}
// Mixer IPM - 0x010C
@@ -220,7 +220,7 @@ void Mixer::process_MMStatusMessage(std::shared_ptr<const Telegram> telegram) {
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
has_update(telegram, status_, 4); // valve status -100 to 100
}
/*
@@ -347,7 +347,9 @@ bool Mixer::set_pump(const char * value, const int8_t id) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_MM10) {
write_command(0xAC, 1, b ? 0x64 : 0, 0xAB);
// AC telegram can only be written with offset 0
uint8_t dat[2] = {flowSetTemp_, b ? (uint8_t)0x64 : (uint8_t)0};
write_command(0xAC, 0, dat, sizeof(dat), 0xAB);
return true;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_IPM) {

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -685,9 +685,9 @@ void Solar::process_SM100Monitor(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, cylBottomTemp2_, 16); // is *10 - TS5: Temperature sensor 2 cylinder, bottom, or swimming pool
has_update(telegram, heatExchangerTemp_, 20); // is *10 - TS6: Heat exchanger temperature sensor
has_update(telegram, collector2Temp_, 6); // is *10 - TS7: Temperature sensor for collector array 2
has_update(telegram, cylMiddleTemp_, 8); // is *10 - TS14: cylinder middle temperature
has_update(telegram, retHeatAssist_, 10); // is *10 - TS15: return temperature heating assistance
has_update(telegram, collector2Temp_, 6); // is *10 - TS7: Temperature sensor for collector array 2
has_update(telegram, cylMiddleTemp_, 8); // is *10 - TS14: cylinder middle temperature
has_update(telegram, retHeatAssist_, 10); // is *10 - TS15: return temperature heating assistance
}
// SM100wwTemperature - 0x07D6
@@ -749,8 +749,8 @@ void Solar::process_SM100Monitor2(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(heatCntFlowTemp_, 0)); // is *10
has_update(telegram->read_value(heatCntRetTemp_, 2)); // is *10
has_update(telegram->read_value(heatCnt_, 12));
has_update(telegram->read_value(swapRetTemp_, 6)); // is *10
has_update(telegram->read_value(swapFlowTemp_, 8)); // is *10
has_update(telegram->read_value(swapRetTemp_, 6)); // is *10
has_update(telegram->read_value(swapFlowTemp_, 8)); // is *10
}
#pragma GCC diagnostic push
@@ -879,7 +879,7 @@ void Solar::process_ISM1StatusMessage(std::shared_ptr<const Telegram> telegram)
has_update(telegram, collectorTemp_, 4); // Collector Temperature
has_update(telegram, cylBottomTemp_, 6); // Temperature Bottom of Solar Boiler cyl
uint16_t Wh = energyLastHour_ / 10;
telegram->read_value(Wh, 2); // Solar Energy produced in last hour only ushort, is not * 10
telegram->read_value(Wh, 2); // Solar Energy produced in last hour only ushort, is not * 10
if (energyLastHour_ != Wh * 10) {
energyLastHour_ = Wh * 10;
has_update(&energyLastHour_);

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -58,9 +58,9 @@ class Solar : public EMSdevice {
uint32_t energyLastHour_;
uint32_t energyToday_;
uint32_t energyTotal_;
uint32_t pumpWorkTime_; // Total solar pump operating time
uint32_t pump2WorkTime_; // Total solar pump 2 operating time
uint32_t m1WorkTime_; // differential control work time
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

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -136,6 +136,8 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
summer_typeids = {0x02AF, 0x02B0, 0x02B1, 0x02B2, 0x02B3, 0x02B4, 0x02B5, 0x02B6};
curve_typeids = {0x029B, 0x029C, 0x029D, 0x029E, 0x029F, 0x02A0, 0x02A1, 0x02A2};
summer2_typeids = {0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0476, 0x0477, 0x0478};
hp_typeids = {0x0467, 0x0468, 0x0469, 0x046A};
hpmode_typeids = {0x0291, 0x0292, 0x0293, 0x0294};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(monitor_typeids[i], "RC300Monitor", false, MAKE_PF_CB(process_RC300Monitor));
register_telegram_type(set_typeids[i], "RC300Set", false, MAKE_PF_CB(process_RC300Set));
@@ -145,6 +147,8 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
}
for (uint8_t i = 0; i < set2_typeids.size(); i++) {
register_telegram_type(set2_typeids[i], "RC300Set2", false, MAKE_PF_CB(process_RC300Set2));
register_telegram_type(hp_typeids[i], "HPSet", false, MAKE_PF_CB(process_HPSet));
register_telegram_type(hpmode_typeids[i], "HPMode", true, MAKE_PF_CB(process_HPMode));
}
register_telegram_type(0x2F5, "RC300WWmode", true, MAKE_PF_CB(process_RC300WWmode));
register_telegram_type(0x31B, "RC300WWtemp", true, MAKE_PF_CB(process_RC300WWtemp));
@@ -216,9 +220,7 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(const ui
return heating_circuit;
}
}
#if defined(EMSESP_DEBUG)
LOG_DEBUG("Heating circuit not fond on device 0x%02X", device_id());
#endif
return nullptr; // not found
}
@@ -308,6 +310,25 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
}
}
// not found, search heatpump message types
if (hc_num == 0) {
for (uint8_t i = 0; i < hp_typeids.size(); i++) {
if (hp_typeids[i] == telegram->type_id) {
hc_num = i + 1;
break;
}
}
}
if (hc_num == 0) {
for (uint8_t i = 0; i < hpmode_typeids.size(); i++) {
if (hpmode_typeids[i] == telegram->type_id) {
hc_num = i + 1;
break;
}
}
}
// not found, search device-id types for remote thermostats
if (hc_num == 0 && telegram->src >= 0x18 && telegram->src <= 0x1F) {
hc_num = telegram->src - 0x17;
@@ -385,6 +406,9 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
if (summer2_typeids.size()) {
toggle_fetch(summer2_typeids[hc_num - 1], toggle_);
}
if (hp_typeids.size()) {
toggle_fetch(hp_typeids[hc_num - 1], toggle_);
}
return new_hc; // return back point to new HC object
}
@@ -398,9 +422,9 @@ void Thermostat::add_ha_climate(std::shared_ptr<HeatingCircuit> hc) const {
if (Helpers::hasValue(hc->selTemp) && is_readable(&hc->selTemp)) {
if (Helpers::hasValue(hc->roomTemp) && is_readable(&hc->roomTemp)) {
hc->climate = 1;
hc->climate = 1; // use roomTemp as we have a sensor
} else {
hc->climate = 0;
hc->climate = 0; // use selTemp, as there is no sensor present in the thermostat
}
} else {
hc->climate = EMS_VALUE_UINT_NOTSET;
@@ -637,8 +661,8 @@ void Thermostat::process_RC20Set_2(std::shared_ptr<const Telegram> telegram) {
return;
}
has_update(telegram, hc->heatingtype, 0);
has_update(telegram, hc->nighttemp, 1); // is * 2,
has_update(telegram, hc->daytemp, 2); // is * 2,
has_update(telegram, hc->nighttemp, 1); // is * 2,
has_update(telegram, hc->daytemp, 2); // is * 2,
has_update(telegram, hc->mode, 3);
has_enumupdate(telegram, hc->program, 11, 1); // 1 .. 9 predefined programs
has_update(telegram, hc->tempautotemp, 13);
@@ -774,8 +798,8 @@ void Thermostat::process_RC35wwSettings(std::shared_ptr<const Telegram> telegram
has_update(telegram, wwDisinfecting_, 4); // 0-off, 0xFF on
has_update(telegram, wwDisinfectDay_, 5); // 0-6 Day of week, 7 every day
has_update(telegram, wwDisinfectHour_, 6);
has_update(telegram, wwMaxTemp_, 8); // Limiter 60 degrees
has_update(telegram, wwOneTimeKey_, 9); // 0-off, 0xFF on
has_update(telegram, wwMaxTemp_, 8); // Limiter 60 degrees
has_update(telegram, wwOneTimeKey_, 9); // 0-off, 0xFF on
}
// Settings WW 0x3A - RC30
@@ -927,7 +951,7 @@ void Thermostat::process_RC300Monitor(std::shared_ptr<const Telegram> telegram)
return;
}
has_update(telegram, hc->roomTemp, 0); // is * 10
has_update(telegram, hc->roomTemp, 0); // is * 10
has_bitupdate(telegram, hc->modetype, 10, 1);
has_bitupdate(telegram, hc->mode, 10, 0); // bit 1, mode (auto=1 or manual=0)
@@ -952,6 +976,7 @@ void Thermostat::process_RC300Monitor(std::shared_ptr<const Telegram> telegram)
}
has_update(telegram, hc->targetflowtemp, 4);
has_update(telegram, hc->curroominfl, 27);
has_update(telegram, hc->coolingon, 32);
add_ha_climate(hc);
}
@@ -988,13 +1013,14 @@ void Thermostat::process_RC300Set(std::shared_ptr<const Telegram> telegram) {
}
has_update(hc->tempautotemp, tat);
has_update(telegram, hc->manualtemp, 10); // is * 2
has_enumupdate(telegram, hc->program, 11, 1); // timer program 1 or 2
has_update(telegram, hc->manualtemp, 10); // is * 2
has_enumupdate(telegram, hc->program, 11, 1); // timer program 1 or 2
has_enumupdate(telegram, hc->reducemode, 5, 1); // 1-outdoor temp threshold, 2-room temp threshold, 3-reduced mode
has_update(telegram, hc->reducetemp, 9);
has_update(telegram, hc->noreducetemp, 12);
has_update(telegram, hc->remoteseltemp, 17); // see https://github.com/emsesp/EMS-ESP32/issues/590
has_update(telegram, hc->cooling, 28);
}
// types 0x2AF ff
@@ -1071,7 +1097,7 @@ void Thermostat::process_RC300WWtemp(std::shared_ptr<const Telegram> telegram) {
// RC300WWmode(0x2F5), data: 01 FF 04 00 00 00 08 05 00 08 04 00 00 00 00 00 00 00 00 00 01
void Thermostat::process_RC300WWmode(std::shared_ptr<const Telegram> telegram) {
// circulation pump see: https://github.com/Th3M3/buderus_ems-wiki/blob/master/Einstellungen%20der%20Bedieneinheit%20RC310.md
has_update(telegram, wwCircPump_, 1); // FF=off, 0=on ?
has_update(telegram, wwCircPump_, 1); // FF=off, 0=on ?
has_update(telegram, wwMode_, 2); // 0=off, 1=low, 2=high, 3=auto, 4=own prog
has_update(telegram, wwCircMode_, 3); // 0=off, 1=on, 2=auto, 4=own?
@@ -1114,8 +1140,8 @@ void Thermostat::process_RC300Settings(std::shared_ptr<const Telegram> telegram)
// 0x2CC - e.g. wwprio for RC310 hcx parameter
void Thermostat::process_RC300Set2(std::shared_ptr<const Telegram> telegram) {
// typeids are not in a raw. hc:0x2CC, hc2: 0x2CE for RC310
// telegram is either offset 3 with data lenght of 1 and values 0/1 (radiators) - 10 0B FF 03 01 CC 01 F6
// or offset 0 with data lenght of 6 bytes - offset 3 values are 0x00 or 0xFF - 10 0B FF 00 01 CE FF 13 0A FF 1E 00 20
// telegram is either offset 3 with data length of 1 and values 0/1 (radiators) - 10 0B FF 03 01 CC 01 F6
// or offset 0 with data length of 6 bytes - offset 3 values are 0x00 or 0xFF - 10 0B FF 00 01 CE FF 13 0A FF 1E 00 20
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
@@ -1130,6 +1156,26 @@ void Thermostat::process_RC300Floordry(std::shared_ptr<const Telegram> telegram)
has_update(telegram, floordrytemp_, 1);
}
// 0x291 ff. HP mode
void Thermostat::process_HPMode(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
has_update(telegram, hc->hpmode, 5);
}
// 0x467 ff HP settings
void Thermostat::process_HPSet(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
has_update(telegram, hc->dewoffset, 4); // 7-35°C
has_update(telegram, hc->roomtempdiff, 3); // 1-10K
has_update(telegram, hc->hpminflowtemp, 0); // 2-10K
}
// type 0x41 - data from the RC30 thermostat(0x10) - 14 bytes long
// RC30Monitor(0x41), data: 80 20 00 AC 00 00 00 02 00 05 09 00 AC 00
void Thermostat::process_RC30Monitor(std::shared_ptr<const Telegram> telegram) {
@@ -1161,11 +1207,11 @@ void Thermostat::process_RC30Set(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, mixingvalves_, 17); // Number of Mixing Valves: (0x00=0, 0x01=1, 0x02=2)
has_update(telegram, brightness_, 18); // Screen brightness 0F=dark F1=light
has_update(telegram, hc->mode, 23);
has_update(telegram, hc->nofrosttemp, 24); // Set Temperature when mode is Off / 10 (e.g.: 0x0F = 7.5 degrees Celsius)
has_update(telegram, heatingpid_, 25); // PID setting 00=1 01=2 02=3
has_update(telegram, preheating_, 26); // Preheating in the clock program: (0x00 = off, 0xFF = on)
has_update(telegram, hc->tempautotemp, 28); // is * 2
has_update(telegram, hc->manualtemp, 29); // manualtemp is * 2
has_update(telegram, hc->nofrosttemp, 24); // Set Temperature when mode is Off / 10 (e.g.: 0x0F = 7.5 degrees Celsius)
has_update(telegram, heatingpid_, 25); // PID setting 00=1 01=2 02=3
has_update(telegram, preheating_, 26); // Preheating in the clock program: (0x00 = off, 0xFF = on)
has_update(telegram, hc->tempautotemp, 28); // is * 2
has_update(telegram, hc->manualtemp, 29); // manualtemp is * 2
}
// type 0x40 (HC1) - for reading the operating mode from the RC30 thermostat (0x10)
@@ -1193,6 +1239,7 @@ void Thermostat::process_RC30Temp(std::shared_ptr<const Telegram> telegram) {
// type 0x3E (HC1), 0x48 (HC2), 0x52 (HC3), 0x5C (HC4) - data from the RC35 thermostat (0x10) - 16 bytes
void Thermostat::process_RC35Monitor(std::shared_ptr<const Telegram> telegram) {
// Check if heatingciruit is active, see https://github.com/emsesp/EMS-ESP32/issues/786
// roomtemp is measured value or 7D00 on active hc's, zero on inactive
uint16_t active = 0;
if (!telegram->read_value(active, 3)) {
return;
@@ -1227,13 +1274,13 @@ void Thermostat::process_RC35Set(std::shared_ptr<const Telegram> telegram) {
return;
}
has_update(telegram, hc->heatingtype, 0); // 0- off, 1-radiator, 2-convector, 3-floor
has_update(telegram, hc->nighttemp, 1); // is * 2
has_update(telegram, hc->daytemp, 2); // is * 2
has_update(telegram, hc->holidaytemp, 3); // is * 2
has_update(telegram, hc->roominfluence, 4); // is * 1
has_update(telegram, hc->offsettemp, 6); // is * 2
has_update(telegram, hc->mode, 7); // night, day, auto
has_update(telegram, hc->heatingtype, 0); // 0- off, 1-radiator, 2-convector, 3-floor
has_update(telegram, hc->nighttemp, 1); // is * 2
has_update(telegram, hc->daytemp, 2); // is * 2
has_update(telegram, hc->holidaytemp, 3); // is * 2
has_update(telegram, hc->roominfluence, 4); // is * 1
has_update(telegram, hc->offsettemp, 6); // is * 2
has_update(telegram, hc->mode, 7); // night, day, auto
has_update(telegram, hc->wwprio, 21); // 0xFF for on
has_update(telegram, hc->summertemp, 22); // is * 1
@@ -1244,10 +1291,10 @@ void Thermostat::process_RC35Set(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, hc->control, 26); // 0-off, 1-RC20 (remote), 2-RC35
has_update(telegram, hc->controlmode, 33); // 0-outdoortemp, 1-roomtemp
has_update(telegram, hc->tempautotemp, 37);
has_update(telegram, hc->noreducetemp, 38); // outdoor temperature for no reduce
has_update(telegram, hc->reducetemp, 39); // temperature for off/reduce
has_update(telegram, hc->vacreducetemp, 40); // temperature for off/reduce in vacations
has_update(telegram, hc->vacreducemode, 41); // vacations reduce mode
has_update(telegram, hc->noreducetemp, 38); // outdoor temperature for no reduce
has_update(telegram, hc->reducetemp, 39); // temperature for off/reduce
has_update(telegram, hc->vacreducetemp, 40); // temperature for off/reduce in vacations
has_update(telegram, hc->vacreducemode, 41); // vacations reduce mode
has_update(telegram, hc->minflowtemp, 16);
// RC35 stores values for floorheating in different position
@@ -1369,7 +1416,7 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
bool ivtclock = (telegram->message_data[0] & 0x80) == 0x80; // dont sync ivt-clock, #439
bool junkersclock = model() == EMSdevice::EMS_DEVICE_FLAG_JUNKERS;
time_t ttime = mktime(tm_); // thermostat time
time_t ttime = mktime(tm_); // thermostat time
// correct thermostat clock if we have valid ntp time, and could write the command
if (!ivtclock && !junkersclock && tset_ && EMSESP::system_.ntp_connected() && !EMSESP::system_.readonly_mode() && has_command(&dateTime_)) {
double difference = difftime(now, ttime);
@@ -1379,7 +1426,7 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
}
}
#ifndef EMSESP_STANDALONE
if (!tset_ && tm_->tm_year > 110) { // emsesp clock not set, but thermostat clock
if (!tset_ && tm_->tm_year > 110) { // emsesp clock not set, but thermostat clock
if (ivtclock) {
tm_->tm_isdst = -1; // determine dst
ttime = mktime(tm_); // thermostat time
@@ -1448,6 +1495,62 @@ void Thermostat::process_RCErrorMessage(std::shared_ptr<const Telegram> telegram
*
*/
// hp mode RC300
bool Thermostat::set_roomtempdiff(const char * value, const int8_t id) {
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id;
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num);
if (hc == nullptr) {
return false;
}
int v;
if (Helpers::value2number(value, v)) {
write_command(hp_typeids[hc->hc()], 3, v, hp_typeids[hc->hc()]);
return true;
}
return false;
}
bool Thermostat::set_dewoffset(const char * value, const int8_t id) {
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id;
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num);
if (hc == nullptr) {
return false;
}
int v;
if (Helpers::value2number(value, v)) {
write_command(hp_typeids[hc->hc()], 4, v, hp_typeids[hc->hc()]);
return true;
}
return false;
}
bool Thermostat::set_hpminflowtemp(const char * value, const int8_t id) {
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id;
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num);
if (hc == nullptr) {
return false;
}
int v;
if (Helpers::value2temperature(value, v)) {
write_command(hp_typeids[hc->hc()], 0, v, hp_typeids[hc->hc()]);
return true;
}
return false;
}
bool Thermostat::set_hpmode(const char * value, const int8_t id) {
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id;
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num);
if (hc == nullptr) {
return false;
}
uint8_t v;
if (!Helpers::value2enum(value, v, FL_(enum_hpmode))) {
return false;
}
write_command(hpmode_typeids[hc->hc()], 5, v, hpmode_typeids[hc->hc()]);
return true;
}
// 0xBB Hybrid pump
bool Thermostat::set_hybridStrategy(const char * value, const int8_t id) {
uint8_t v;
@@ -1869,6 +1972,20 @@ bool Thermostat::set_wwprio(const char * value, const int8_t id) {
return true;
}
// set cooling
bool Thermostat::set_cooling(const char * value, const int8_t id) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit((id == -1) ? AUTO_HEATING_CIRCUIT : id);
if (hc == nullptr) {
return false;
}
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(set_typeids[hc->hc()], 28, b ? 0x01 : 0x00, set_typeids[hc->hc()]);
return true;
}
// sets the thermostat ww circulation working mode, where mode is a string
bool Thermostat::set_wwcircmode(const char * value, const int8_t id) {
@@ -2185,7 +2302,7 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) {
data[6] = (tm_->tm_wday + 6) % 7; // Bosch counts from Mo, time from Su
data[7] = tm_->tm_isdst + 2; // set DST and flag for ext. clock
if (model() == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
data[6]++; // Junkers use 1-7;
data[6]++; // Junkers use 1-7;
data[7] = 0;
}
} else if (dt.length() == 23) {
@@ -2383,7 +2500,7 @@ bool Thermostat::set_mode_n(const uint8_t mode, const uint8_t hc_num) {
if (mode == HeatingCircuit::Mode::AUTO) {
set_mode_value = 0xFF; // special value for auto
} else {
set_mode_value = 0; // everything else, like manual/day etc..
set_mode_value = 0; // everything else, like manual/day etc..
}
break;
case EMSdevice::EMS_DEVICE_FLAG_JUNKERS:
@@ -2611,7 +2728,7 @@ bool Thermostat::set_controlmode(const char * value, const int8_t id) {
return false;
}
// sets the thermostat time for nightmode for RC10, telegrm 0xB0
// sets the thermostat time for nightmode for RC10, telegram 0xB0
bool Thermostat::set_reducehours(const char * value, const int8_t id) {
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id;
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num);
@@ -3004,7 +3121,7 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
offset = 0x0A; // manual offset
break;
case HeatingCircuit::Mode::TEMPAUTO:
offset = 0x08; // auto offset
offset = 0x08; // auto offset
if (temperature == -1) {
factor = 1; // to write 0xFF
}
@@ -3294,9 +3411,7 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
write_command(set_typeid, offset, (uint8_t)(temperature * (float)factor), validate_typeid);
return true;
}
#if defined(EMSESP_DEBUG)
LOG_DEBUG("temperature mode %d not found", mode);
#endif
return false;
}
@@ -3339,7 +3454,6 @@ void Thermostat::register_device_values() {
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &errorCode_, DeviceValueType::STRING, FL_(errorCode), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &lastCode_, DeviceValueType::STRING, FL_(lastCode), DeviceValueUOM::NONE);
switch (this->model()) {
case EMS_DEVICE_FLAG_RC100:
case EMS_DEVICE_FLAG_RC300:
@@ -3995,7 +4109,7 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
register_device_value(tag, &hc->selTemp, DeviceValueType::SHORT, seltemp_divider, FL_(selRoomTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_temp), 0, 30);
}
register_device_value(tag, &hc->roomTemp, DeviceValueType::SHORT, roomtemp_divider, FL_(roomTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &hc->climate, DeviceValueType::ENUM, FL_(enum_climate), FL_(climate), DeviceValueUOM::NONE, nullptr, 5, 30);
register_device_value(tag, &hc->climate, DeviceValueType::ENUM, FL_(enum_climate), FL_(haclimate), DeviceValueUOM::NONE, nullptr, 5, 30);
switch (model) {
case EMS_DEVICE_FLAG_RC10:
@@ -4080,6 +4194,14 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
register_device_value(tag, &hc->noreducetemp, DeviceValueType::INT, FL_(noreducetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_noreducetemp));
register_device_value(tag, &hc->reducetemp, DeviceValueType::INT, FL_(reducetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_reducetemp));
register_device_value(tag, &hc->wwprio, DeviceValueType::BOOL, FL_(wwprio), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwprio));
register_device_value(tag, &hc->cooling, DeviceValueType::BOOL, FL_(hpcooling), DeviceValueUOM::NONE, MAKE_CF_CB(set_cooling));
register_device_value(tag, &hc->coolingon, DeviceValueType::BOOL, FL_(coolingOn), DeviceValueUOM::NONE);
register_device_value(tag, &hc->hpmode, DeviceValueType::ENUM, FL_(enum_hpmode), FL_(hpmode), DeviceValueUOM::NONE, MAKE_CF_CB(set_hpmode));
register_device_value(tag, &hc->dewoffset, DeviceValueType::UINT, FL_(dewoffset), DeviceValueUOM::K, MAKE_CF_CB(set_dewoffset), 2, 10);
register_device_value(tag, &hc->roomtempdiff, DeviceValueType::UINT, FL_(roomtempdiff), DeviceValueUOM::K, MAKE_CF_CB(set_roomtempdiff));
register_device_value(tag, &hc->hpminflowtemp, DeviceValueType::UINT, FL_(hpminflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_hpminflowtemp));
break;
case EMS_DEVICE_FLAG_CRF:
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode5), FL_(mode), DeviceValueUOM::NONE);
@@ -4246,7 +4368,7 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
MAKE_CF_CB(set_tempautotemp),
0,
30);
register_device_value(tag, &hc->noreducetemp, DeviceValueType::INT, FL_(noreducetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_noreducetemp), -30, 10);
register_device_value(tag, &hc->noreducetemp, DeviceValueType::INT, FL_(noreducetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_noreducetemp), -31, 10);
register_device_value(tag, &hc->reducetemp, DeviceValueType::INT, FL_(reducetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_reducetemp), -20, 10);
register_device_value(tag, &hc->vacreducetemp, DeviceValueType::INT, FL_(vacreducetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_vacreducetemp), -20, 10);
register_device_value(

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -89,6 +89,13 @@ class Thermostat : public EMSdevice {
uint16_t reduceminutes; // remaining minutes to night->day
// FW100 temperature
uint8_t roomsensor; // 1-intern, 2-extern, 3-autoselect the lower value
// hp
uint8_t dewoffset;
uint8_t roomtempdiff;
uint8_t hpminflowtemp;
uint8_t hpmode;
uint8_t cooling;
uint8_t coolingon;
uint8_t hc_num() const {
return hc_num_;
@@ -170,6 +177,8 @@ class Thermostat : public EMSdevice {
std::vector<uint16_t> summer_typeids;
std::vector<uint16_t> summer2_typeids;
std::vector<uint16_t> curve_typeids;
std::vector<uint16_t> hp_typeids;
std::vector<uint16_t> hpmode_typeids;
// standard for all thermostats
char status_[20]; // online or offline
@@ -247,9 +256,9 @@ class Thermostat : public EMSdevice {
static constexpr uint16_t EMS_TYPE_RCOutdoorTemp = 0xA3; // is an automatic thermostat broadcast, outdoor external temp
// Type offsets
static constexpr uint8_t EMS_OFFSET_RC10StatusMessage_setpoint = 1; // setpoint temp
static constexpr uint8_t EMS_OFFSET_RC10StatusMessage_curr = 2; // current temp
static constexpr uint8_t EMS_OFFSET_RC10Set_temp = 4; // position of thermostat setpoint temperature
static constexpr uint8_t EMS_OFFSET_RC10StatusMessage_setpoint = 1; // setpoint temp
static constexpr uint8_t EMS_OFFSET_RC10StatusMessage_curr = 2; // current temp
static constexpr uint8_t EMS_OFFSET_RC10Set_temp = 4; // position of thermostat setpoint temperature
static constexpr uint8_t EMS_OFFSET_RC20StatusMessage_setpoint = 1; // setpoint temp
static constexpr uint8_t EMS_OFFSET_RC20StatusMessage_curr = 2; // current temp
@@ -258,9 +267,9 @@ class Thermostat : public EMSdevice {
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
static constexpr uint8_t EMS_OFFSET_RC20_2_Set_temp_day = 2; // ES72
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
static constexpr uint8_t EMS_OFFSET_RC20_2_Set_temp_day = 2; // ES72
static constexpr uint8_t EMS_OFFSET_RC30StatusMessage_setpoint = 1; // setpoint temp
static constexpr uint8_t EMS_OFFSET_RC30StatusMessage_curr = 2; // current temp
@@ -299,8 +308,8 @@ class Thermostat : public EMSdevice {
static constexpr uint8_t EMS_OFFSET_RC35Set_temp_summer = 22;
static constexpr uint8_t EMS_OFFSET_RC35Set_temp_nofrost = 23;
static constexpr uint8_t EMS_OFFSET_EasyStatusMessage_setpoint = 10; // setpoint temp
static constexpr uint8_t EMS_OFFSET_EasyStatusMessage_curr = 8; // current temp
static constexpr uint8_t EMS_OFFSET_EasyStatusMessage_setpoint = 10; // setpoint temp
static constexpr uint8_t EMS_OFFSET_EasyStatusMessage_curr = 8; // current temp
static constexpr uint8_t EMS_OFFSET_RCPLUSStatusMessage_mode = 10; // thermostat mode (auto, manual)
static constexpr uint8_t EMS_OFFSET_RCPLUSStatusMessage_setpoint = 3; // setpoint temp
@@ -392,6 +401,8 @@ class Thermostat : public EMSdevice {
void process_RemoteTemp(std::shared_ptr<const Telegram> telegram);
void process_RemoteHumidity(std::shared_ptr<const Telegram> telegram);
void process_RemoteCorrection(std::shared_ptr<const Telegram> telegram);
void process_HPSet(std::shared_ptr<const Telegram> telegram);
void process_HPMode(std::shared_ptr<const Telegram> telegram);
// internal helper functions
bool set_mode_n(const uint8_t mode, const uint8_t hc_num);
@@ -551,8 +562,14 @@ class Thermostat : public EMSdevice {
bool set_pvEnableWw(const char * value, const int8_t id);
bool set_pvRaiseHeat(const char * value, const int8_t id);
bool set_pvLowerCool(const char * value, const int8_t id);
bool set_roomtempdiff(const char * value, const int8_t id);
bool set_dewoffset(const char * value, const int8_t id);
bool set_hpminflowtemp(const char * value, const int8_t id);
bool set_hpmode(const char * value, const int8_t id);
bool set_cooling(const char * value, const int8_t id);
};
} // namespace emsesp
#endif
#endif

118
src/devices/ventilation.cpp Normal file
View File

@@ -0,0 +1,118 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2023 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 "ventilation.h"
namespace emsesp {
REGISTER_FACTORY(Ventilation, EMSdevice::DeviceType::VENTILATION);
Ventilation::Ventilation(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand)
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
// HRV176 module, device_id 0x51
register_telegram_type(0x56B, "VentilationMode", true, MAKE_PF_CB(process_ModeMessage));
register_telegram_type(0x585, "Blowerspeed", false, MAKE_PF_CB(process_BlowerMessage));
register_telegram_type(0x583, "VentilationMonitor", false, MAKE_PF_CB(process_MonitorMessage));
register_telegram_type(0x5D9, "Airquality", false, MAKE_PF_CB(process_VOCMessage));
register_telegram_type(0x587, "Bypass", false, MAKE_PF_CB(process_BypassMessage));
// register_telegram_type(0x5, "VentilationSet", true, MAKE_PF_CB(process_SetMessage));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&outFresh_,
DeviceValueType::SHORT,
DeviceValueNumOp::DV_NUMOP_DIV10,
FL_(outFresh),
DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &inFresh_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(inFresh), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &outEx_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(outEx), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &inEx_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(inEx), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &ventInSpeed_, DeviceValueType::UINT, FL_(ventInSpeed), DeviceValueUOM::PERCENT);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &ventOutSpeed_, DeviceValueType::UINT, FL_(ventOutSpeed), DeviceValueUOM::PERCENT);
register_device_value(
DeviceValueTAG::TAG_DEVICE_DATA, &mode_, DeviceValueType::ENUM, FL_(enum_ventMode), FL_(ventMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_ventMode));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &voc_, DeviceValueType::USHORT, FL_(airquality), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &bypass_, DeviceValueType::BOOL, FL_(bypass), DeviceValueUOM::NONE, MAKE_CF_CB(set_bypass));
}
// message
void Ventilation::process_SetMessage(std::shared_ptr<const Telegram> telegram) {
}
// message 583
void Ventilation::process_MonitorMessage(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, outEx_, 0); // Fortluft
has_update(telegram, inEx_, 7); // Abluft
has_update(telegram, outFresh_, 13); // Außenluft
has_update(telegram, inFresh_, 15); // Zuluft
}
// message 575 10 bytes
// data: 02 02 46 46 00 00 FF 80 00 01
// 0-level out, 1-level in, 2-mod out, 3-mod in, 9-mode:1-manual/2-auto/3-prog
// message 585 26 bytes long
// Data: 46 46 00 00 00 77 00 03 F4 09 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
void Ventilation::process_BlowerMessage(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, ventOutSpeed_, 0);
has_update(telegram, ventInSpeed_, 1);
}
// message 0x05D9, data: 03 9C FF
void Ventilation::process_VOCMessage(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, voc_, 0);
}
// message 0x56B
// level 0=0, 1=1, 2=2, 3=3, 4= 4, Auto 0xFF, demand 5, sleep 6, intense 7, bypass-8, party 9, fireplace 0A
void Ventilation::process_ModeMessage(std::shared_ptr<const Telegram> telegram) {
has_enumupdate(telegram, mode_, 0, -1);
}
// message 0x0587, data: 01 00
void Ventilation::process_BypassMessage(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, bypass_, 1);
}
bool Ventilation::set_ventMode(const char * value, const int8_t id) {
uint8_t v;
if (!Helpers::value2enum(value, v, FL_(enum_ventMode))) {
return false;
}
write_command(0x56B, 0, v - 1, 0x56B);
return true;
}
bool Ventilation::set_filter(const char * value, const int8_t id) {
int v;
if (!Helpers::value2number(value, v)) {
return false;
}
// write_command(0x5xx, 0, v, 0x5xx);
return true;
}
bool Ventilation::set_bypass(const char * value, const int8_t id) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x55C, 1, b ? 1 : 0, 0x587);
return true;
}
} // namespace emsesp

119
src/devices/ventilation.h Normal file
View File

@@ -0,0 +1,119 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2023 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_VENTILATION_H
#define EMSESP_VENTILATION_H
#include "emsesp.h"
namespace emsesp {
class Ventilation : public EMSdevice {
public:
Ventilation(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand);
private:
uint8_t mode_;
int16_t outFresh_;
int16_t inFresh_;
int16_t inEx_;
int16_t outEx_;
uint16_t voc_;
uint8_t bypass_;
// uint16_t filterRemain_;
uint8_t ventInSpeed_;
uint8_t ventOutSpeed_;
// handlers: 0x056B 0x0575 0x0583 0x0585 0x0586 0x0587 0x0588 0x058D 0x058E 0x058F 0x0590 0x05CF 0x05D9 0x05E3
void process_SetMessage(std::shared_ptr<const Telegram> telegram);
void process_MonitorMessage(std::shared_ptr<const Telegram> telegram);
void process_ModeMessage(std::shared_ptr<const Telegram> telegram); // 0x56B
void process_BlowerMessage(std::shared_ptr<const Telegram> telegram); // 0x56B
void process_VOCMessage(std::shared_ptr<const Telegram> telegram); // 0x56B
void process_BypassMessage(std::shared_ptr<const Telegram> telegram); // 0x56B
bool set_ventMode(const char * value, const int8_t id);
bool set_bypass(const char * value, const int8_t id);
bool set_filter(const char * value, const int8_t id);
/* Sensors:
outdoor air temp (außenluft)
supply air temp (zuluft)
extract air temp (abluft)
away air temp (fortluft)
supply blower (zuluftgebläse)
supply blower mod (zuluftebläse drehzahl)
away blower (abluftgebläse)
away blower mod (abluftgebläse drehzahl)
Anschlussvariante
el. vorheizer
ext. el. vorheizreg.
nachheiz zulufttemp
mischer öffnen
mischer schließen
mischerposition
zuluft temp soll
zuluft temp ist
leistung nachheizreg.
erdwärmetauscher klappe
solekreispumpe
abluftfeuchte
abluftqualität
raumluftfechte
raumluftqualität
luftfeuchte fernbed. 1..4
*/
/* Parameters:
Gerätetyp,
Nennvolumentstrom,
Filterlaufzeit 1-6-12 m
Filterwechsel confirm CMD
Lüftungsfrostschutz: _el._preheat_, Disballance | Interval
Ext. Frostschutz: on/_off_
Bypass _on_, off
min. outdoortemp 12 15 19 °C
max. outdoortemp 21-24-30 C
Enthalpie Wärmetauscher instaliert nein-ja
Feuchteschutz AUs/ 1-24 h
Lüfterstufe 1-4, Drehzahlanpassung
ext. Luftfeuchtefühler inst.? _nein_, ja
Abluftfeuchtefühler inst.? _nein_, ja
Luftfeuchte Fernbed. _nein_, ja
Luftfeuchte: trocken, _normal_, feucht
Abluftqualitätsfühler inst. _ja_, nein
ext. Luftqualfühl? _nein_, ja
Lufqualität: ausreichend, _normal_, hoch
el. Nachheizregister inst. _nein_, ja
Nachheiz-Zuluft temp: 10-22-30 °C
Erdwärmetauscher inst? _nein_, Luft, Sole
Taster Funktion: nein, einschlafen, intensiv, bypass, party, kamin
ext. Störung aktivieren: _nein_, ja, invertiert
Dauer einschlafen: 15-60-120 min
Dauer Intensiv: 5-15-60 min
Dauer Bypass Abluft: 1-8-12 h
Dauer Bypass: 1-8-12 h
Dauer PArty 1-8-12 h
Dauer Kamin: 5-10-15 min
Volumenstromabgleich 90-100-110 %
*/
};
} // namespace emsesp
#endif

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -52,16 +52,18 @@ bool EMSdevice::has_entities() const {
return found;
}
std::string EMSdevice::tag_to_string(uint8_t tag, const bool translate) {
return (translate ? Helpers::translated_word(DeviceValue::DeviceValueTAG_s[tag]) : DeviceValue::DeviceValueTAG_s[tag][0]);
// return translated tag name based on tag id
const char * EMSdevice::tag_to_string(uint8_t tag, const bool translate) {
uint8_t tag_n = tag > DeviceValue::NUM_TAGS ? 0 : tag;
return (translate ? Helpers::translated_word(DeviceValue::DeviceValueTAG_s[tag_n]) : DeviceValue::DeviceValueTAG_s[tag_n][0]);
}
std::string EMSdevice::tag_to_mqtt(uint8_t tag) {
return (DeviceValue::DeviceValueTAG_mqtt[tag]);
const char * EMSdevice::tag_to_mqtt(uint8_t tag) {
return (DeviceValue::DeviceValueTAG_mqtt[tag > DeviceValue::NUM_TAGS ? 0 : tag]);
}
// convert UOM to a string - translating only for hours/minutes/seconds
std::string EMSdevice::uom_to_string(uint8_t uom) {
// convert UOM to a char string - translating only for hours/minutes/seconds
const char * EMSdevice::uom_to_string(uint8_t uom) {
switch (uom) {
case DeviceValueUOM::DEGREES:
case DeviceValueUOM::DEGREES_R:
@@ -77,24 +79,24 @@ std::string EMSdevice::uom_to_string(uint8_t uom) {
}
}
const std::string EMSdevice::brand_to_string() {
const char * EMSdevice::brand_to_char() {
switch (brand_) {
case EMSdevice::Brand::BOSCH:
return "Bosch";
return F_(bosch);
case EMSdevice::Brand::JUNKERS:
return "Junkers";
return F_(junkers);
case EMSdevice::Brand::BUDERUS:
return "Buderus";
return F_(buderus);
case EMSdevice::Brand::NEFIT:
return "Nefit";
return F_(nefit);
case EMSdevice::Brand::SIEGER:
return "Sieger";
return F_(sieger);
case EMSdevice::Brand::WORCESTER:
return "Worcester";
return F_(worcester);
case EMSdevice::Brand::IVT:
return "IVT";
return F_(ivt);
default:
return "";
return F_(no_brand);
}
}
@@ -103,6 +105,8 @@ const char * EMSdevice::device_type_2_device_name(const uint8_t device_type) {
switch (device_type) {
case DeviceType::SYSTEM:
return F_(system);
case DeviceType::SCHEDULER:
return F_(scheduler);
case DeviceType::BOILER:
return F_(boiler);
case DeviceType::THERMOSTAT:
@@ -115,8 +119,8 @@ const char * EMSdevice::device_type_2_device_name(const uint8_t device_type) {
return F_(connect);
case DeviceType::MIXER:
return F_(mixer);
case DeviceType::DALLASSENSOR:
return F_(dallassensor);
case DeviceType::TEMPERATURESENSOR:
return F_(temperaturesensor);
case DeviceType::ANALOGSENSOR:
return F_(analogsensor);
case DeviceType::CONTROLLER:
@@ -131,13 +135,17 @@ const char * EMSdevice::device_type_2_device_name(const uint8_t device_type) {
return F_(pump);
case DeviceType::HEATSOURCE:
return F_(heatsource);
case DeviceType::CUSTOM:
return F_(custom);
case DeviceType::VENTILATION:
return F_(ventilation);
default:
return Helpers::translated_word(FL_(unknown), true);
}
}
// returns the translated name of a specific EMS device
// excludes dallassensor, analogsensor and system
// excludes temperaturesensor, analogsensor and system
const char * EMSdevice::device_type_2_device_name_translated() {
switch (device_type_) {
case DeviceType::BOILER:
@@ -164,6 +172,8 @@ const char * EMSdevice::device_type_2_device_name_translated() {
return Helpers::translated_word(FL_(pump_device));
case DeviceType::HEATSOURCE:
return Helpers::translated_word(FL_(heatsource_device));
case DeviceType::VENTILATION:
return Helpers::translated_word(FL_(ventilation_device));
default:
break;
}
@@ -192,6 +202,9 @@ uint8_t EMSdevice::device_name_2_device_type(const char * topic) {
if (!strcmp(lowtopic, F_(system))) {
return DeviceType::SYSTEM;
}
if (!strcmp(lowtopic, F_(scheduler))) {
return DeviceType::SCHEDULER;
}
if (!strcmp(lowtopic, F_(heatpump))) {
return DeviceType::HEATPUMP;
}
@@ -201,8 +214,8 @@ uint8_t EMSdevice::device_name_2_device_type(const char * topic) {
if (!strcmp(lowtopic, F_(mixer))) {
return DeviceType::MIXER;
}
if (!strcmp(lowtopic, F_(dallassensor))) {
return DeviceType::DALLASSENSOR;
if (!strcmp(lowtopic, F_(temperaturesensor))) {
return DeviceType::TEMPERATURESENSOR;
}
if (!strcmp(lowtopic, F_(analogsensor))) {
return DeviceType::ANALOGSENSOR;
@@ -222,6 +235,12 @@ uint8_t EMSdevice::device_name_2_device_type(const char * topic) {
if (!strcmp(lowtopic, F_(heatsource))) {
return DeviceType::HEATSOURCE;
}
if (!strcmp(lowtopic, F_(custom))) {
return DeviceType::CUSTOM;
}
if (!strcmp(lowtopic, F_(ventilation))) {
return DeviceType::VENTILATION;
}
return DeviceType::UNKNOWN;
}
@@ -264,8 +283,8 @@ const std::string EMSdevice::to_string() {
return std::string(name_) + " (DeviceID:" + Helpers::hextoa(device_id_) + ", ProductID:" + Helpers::itoa(product_id_) + ", Version:" + version_ + ")";
}
return brand_to_string() + " " + name_ + " (DeviceID:" + Helpers::hextoa(device_id_) + ", ProductID:" + Helpers::itoa(product_id_) + ", Version:" + version_
+ ")";
return std::string(brand_to_char()) + " " + name_ + " (DeviceID:" + Helpers::hextoa(device_id_) + ", ProductID:" + Helpers::itoa(product_id_)
+ ", Version:" + version_ + ")";
}
// returns out brand + device name
@@ -275,12 +294,14 @@ const std::string EMSdevice::to_string_short() {
return std::string(device_type_2_device_name_translated()) + ": " + name_;
}
return std::string(device_type_2_device_name_translated()) + ": " + brand_to_string() + " " + name_;
return std::string(device_type_2_device_name_translated()) + ": " + brand_to_char() + " " + name_;
}
// for each telegram that has the fetch value set (true) do a read request
void EMSdevice::fetch_values() {
#if defined(EMSESP_DEBUG)
EMSESP::logger().debug("Fetching values for deviceID 0x%02X", device_id());
#endif
for (const auto & tf : telegram_functions_) {
if (tf.fetch_) {
@@ -291,7 +312,9 @@ void EMSdevice::fetch_values() {
// toggle on/off automatic fetch for a telegramID
void EMSdevice::toggle_fetch(uint16_t telegram_id, bool toggle) {
#if defined(EMSESP_DEBUG)
EMSESP::logger().debug("Toggling fetch for deviceID 0x%02X, telegramID 0x%02X to %d", device_id(), telegram_id, toggle);
#endif
for (auto & tf : telegram_functions_) {
if (tf.telegram_type_id_ == telegram_id) {
@@ -311,7 +334,7 @@ bool EMSdevice::is_fetch(uint16_t telegram_id) const {
}
// check for a tag to create a nest
bool EMSdevice::has_tag(const uint8_t tag) const {
bool EMSdevice::has_tags(const uint8_t tag) const {
for (const auto & dv : devicevalues_) {
if (dv.tag == tag && tag >= DeviceValueTAG::TAG_HC1) {
return true;
@@ -339,8 +362,9 @@ void EMSdevice::list_device_entries(JsonObject & output) const {
if (!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && dv.type != DeviceValueType::CMD && !fullname.empty()) {
// if we have a tag prefix it
char key[50];
if (!EMSdevice::tag_to_mqtt(dv.tag).empty()) {
snprintf(key, sizeof(key), "%s.%s", EMSdevice::tag_to_mqtt(dv.tag).c_str(), dv.short_name);
auto tag_s = EMSdevice::tag_to_mqtt(dv.tag);
if (strlen(tag_s)) {
snprintf(key, sizeof(key), "%s.%s", tag_s, dv.short_name);
} else {
snprintf(key, sizeof(key), "%s", dv.short_name);
}
@@ -351,7 +375,7 @@ void EMSdevice::list_device_entries(JsonObject & output) const {
details.add(fullname);
// add uom
if (!uom_to_string(dv.uom).empty() && uom_to_string(dv.uom) != " ") {
if (dv.uom != DeviceValueUOM::NONE) {
details.add(EMSdevice::uom_to_string(dv.uom));
}
}
@@ -454,48 +478,37 @@ void EMSdevice::register_telegram_type(const uint16_t telegram_type_id, const ch
}
// add to device value library, also know now as a "device entity"
// arguments are:
// tag: to be used to group mqtt together, either as separate topics as a nested object
// value_p: pointer to the value from the .h file
// type: one of DeviceValueType
// options: options for enum, which are translated as a list of lists
// options_single: list of names
// numeric_operator: to divide or multiply, see DeviceValueNumOps::
// short_name: used in MQTT as keys
// fullname: used in Web and Console unless empty (nullptr) - can be translated
// uom: unit of measure from DeviceValueUOM
// has_cmd: true if this is an associated command
// min: min allowed value
// max: max allowed value
void EMSdevice::add_device_value(uint8_t tag,
void * value_p,
uint8_t type,
const char * const ** options,
const char * const * options_single,
int8_t numeric_operator,
const char * const * name,
uint8_t uom,
const cmd_function_p f,
int16_t min,
uint16_t max) {
void EMSdevice::add_device_value(uint8_t tag, // to be used to group mqtt together, either as separate topics as a nested object
void * value_p, // pointer to the value from the .h file
uint8_t type, // one of DeviceValueType
const char * const ** options, // options for enum, which are translated as a list of lists
const char * const * options_single, // list of names
int8_t numeric_operator, // to divide or multiply, see DeviceValueNumOps::
const char * const * name, // list of names, including shortname and translations
uint8_t uom, // unit of measure from DeviceValueUOM
const cmd_function_p f, // command function pointer
int16_t min, // min allowed value
uint16_t max // max allowed value
) {
// initialize the device value depending on it's type
// ignoring DeviceValueType::CMD and DeviceValueType::TIME
if (type == DeviceValueType::STRING) {
*(char *)(value_p) = {'\0'}; // this is important for string functions like strlen() to work later
} else if (type == DeviceValueType::INT) {
*(int8_t *)(value_p) = EMS_VALUE_DEFAULT_INT;
*(int8_t *)(value_p) = System::test_set_all_active() ? EMS_VALUE_DEFAULT_INT_DUMMY : EMS_VALUE_DEFAULT_INT;
} else if (type == DeviceValueType::UINT) {
*(uint8_t *)(value_p) = EMS_VALUE_DEFAULT_UINT;
*(uint8_t *)(value_p) = System::test_set_all_active() ? EMS_VALUE_DEFAULT_UINT_DUMMY : EMS_VALUE_DEFAULT_UINT;
} else if (type == DeviceValueType::SHORT) {
*(int16_t *)(value_p) = EMS_VALUE_DEFAULT_SHORT;
*(int16_t *)(value_p) = System::test_set_all_active() ? EMS_VALUE_DEFAULT_SHORT_DUMMY : EMS_VALUE_DEFAULT_SHORT;
} else if (type == DeviceValueType::USHORT) {
*(uint16_t *)(value_p) = EMS_VALUE_DEFAULT_USHORT;
*(uint16_t *)(value_p) = System::test_set_all_active() ? EMS_VALUE_DEFAULT_USHORT_DUMMY : EMS_VALUE_DEFAULT_USHORT;
} else if ((type == DeviceValueType::ULONG) || (type == DeviceValueType::TIME)) {
*(uint32_t *)(value_p) = EMS_VALUE_DEFAULT_ULONG;
*(uint32_t *)(value_p) = System::test_set_all_active() ? EMS_VALUE_DEFAULT_ULONG_DUMMY : EMS_VALUE_DEFAULT_ULONG;
} else if (type == DeviceValueType::BOOL) {
*(int8_t *)(value_p) = EMS_VALUE_DEFAULT_BOOL; // bool is uint8_t, but other initial value
*(int8_t *)(value_p) = System::test_set_all_active() ? EMS_VALUE_DEFAULT_BOOL_DUMMY : EMS_VALUE_DEFAULT_BOOL; // bool is uint8_t, but other initial value
} else if (type == DeviceValueType::ENUM) {
*(uint8_t *)(value_p) = EMS_VALUE_DEFAULT_ENUM; // enums behave as uint8_t
*(uint8_t *)(value_p) = System::test_set_all_active() ? EMS_VALUE_DEFAULT_ENUM_DUMMY : EMS_VALUE_DEFAULT_ENUM; // enums behave as uint8_t
}
uint8_t state = DeviceValueState::DV_DEFAULT; // determine state
@@ -507,7 +520,7 @@ void EMSdevice::add_device_value(uint8_t tag,
// get fullname, getting translation if it exists
const char * const * fullname;
if (Helpers::count_items(name) == 1) {
fullname = nullptr; // no translations available, use empty to prevent crash
fullname = nullptr; // no translations available, use empty
} else {
fullname = &name[1]; // translations start at index 1
}
@@ -516,7 +529,13 @@ void EMSdevice::add_device_value(uint8_t tag,
EMSESP::webCustomizationService.read([&](WebCustomization & settings) {
for (EntityCustomization entityCustomization : settings.entityCustomizations) {
if ((entityCustomization.product_id == product_id()) && (entityCustomization.device_id == device_id())) {
std::string entity = tag < DeviceValueTAG::TAG_HC1 ? short_name : tag_to_mqtt(tag) + "/" + short_name;
char entity[70];
if (tag < DeviceValueTAG::TAG_HC1) {
strncpy(entity, short_name, sizeof(entity));
} else {
snprintf(entity, sizeof(entity), "%s/%s", tag_to_mqtt(tag), short_name);
}
for (std::string entity_id : entityCustomization.entity_ids) {
// if there is an appended custom name, strip it to get the true entity name
// and extract the new custom name
@@ -705,12 +724,12 @@ void EMSdevice::publish_value(void * value_p) const {
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
if (Mqtt::publish_single2cmd()) {
if (dv.tag >= DeviceValueTAG::TAG_HC1) {
snprintf(topic, sizeof(topic), "%s/%s/%s", device_type_2_device_name(device_type_), tag_to_mqtt(dv.tag).c_str(), dv.short_name);
snprintf(topic, sizeof(topic), "%s/%s/%s", device_type_2_device_name(device_type_), tag_to_mqtt(dv.tag), dv.short_name);
} else {
snprintf(topic, sizeof(topic), "%s/%s", device_type_2_device_name(device_type_), (dv.short_name));
}
} else if (Mqtt::is_nested() && dv.tag >= DeviceValueTAG::TAG_HC1) {
snprintf(topic, sizeof(topic), "%s/%s/%s", Mqtt::tag_to_topic(device_type_, dv.tag).c_str(), tag_to_mqtt(dv.tag).c_str(), dv.short_name);
snprintf(topic, sizeof(topic), "%s/%s/%s", Mqtt::tag_to_topic(device_type_, dv.tag).c_str(), tag_to_mqtt(dv.tag), dv.short_name);
} else {
snprintf(topic, sizeof(topic), "%s/%s", Mqtt::tag_to_topic(device_type_, dv.tag).c_str(), dv.short_name);
}
@@ -766,7 +785,7 @@ void EMSdevice::publish_value(void * value_p) const {
}
if (payload[0] != '\0') {
Mqtt::publish(topic, payload);
Mqtt::queue_publish(topic, payload);
}
}
}
@@ -780,7 +799,7 @@ std::string EMSdevice::get_value_uom(const char * key) const {
strlcpy(new_key, key, sizeof(new_key));
char * key_p = new_key;
for (uint8_t i = 0; i < DeviceValue::tag_count; i++) {
for (uint8_t i = 0; i < DeviceValue::NUM_TAGS; i++) {
auto tag = Helpers::translated_word(DeviceValue::DeviceValueTAG_s[i]);
if (tag) {
std::string key2 = key; // copy string to a std::string so we can use the find function
@@ -812,8 +831,9 @@ std::string EMSdevice::get_value_uom(const char * key) const {
// except additional data is stored in the JSON document needed for the Web UI like the UOM and command
// v=value, u=uom, n=name, c=cmd, h=help string, s=step, m=min, x=max
void EMSdevice::generate_values_web(JsonObject & output) {
output["label"] = to_string_short();
JsonArray data = output.createNestedArray("data");
// output["label"] = to_string_short();
// output["label"] = name_;
JsonArray data = output.createNestedArray("data");
for (auto & dv : devicevalues_) {
auto fullname = dv.get_fullname();
@@ -826,7 +846,7 @@ void EMSdevice::generate_values_web(JsonObject & output) {
JsonObject obj = data.createNestedObject(); // create the object, we know there is a value
uint8_t fahrenheit = 0;
// handle Booleans (true, false), use strings, no native true/false)
// handle Booleans (true, false), output as strings according to the user settings
if (dv.type == DeviceValueType::BOOL) {
auto value_b = (bool)*(uint8_t *)(dv.value_p);
char s[12];
@@ -871,17 +891,19 @@ void EMSdevice::generate_values_web(JsonObject & output) {
auto mask = Helpers::hextoa((uint8_t)(dv.state >> 4), false); // create mask to a 2-char string
// add name, prefixing the tag if it exists. This is the id used in the WebUI table and must be unique
if ((dv.tag == DeviceValueTAG::TAG_NONE) || tag_to_string(dv.tag).empty()) {
obj["id"] = mask + fullname;
} else {
if (dv.has_tag()) {
obj["id"] = mask + tag_to_string(dv.tag) + " " + fullname;
} else {
obj["id"] = mask + fullname;
}
// add commands and options
if (dv.has_cmd && !dv.has_state(DeviceValueState::DV_READONLY)) {
// add the name of the Command function
if (dv.tag >= DeviceValueTAG::TAG_HC1) {
obj["c"] = tag_to_mqtt(dv.tag) + "/" + dv.short_name;
char c_s[50];
snprintf(c_s, sizeof(c_s), "%s/%s", tag_to_mqtt(dv.tag), dv.short_name);
obj["c"] = c_s;
} else {
obj["c"] = dv.short_name;
}
@@ -908,20 +930,19 @@ void EMSdevice::generate_values_web(JsonObject & output) {
}
}
// handle INTs
// add steps to numeric values with numeric_operator
// add min and max values and steps, as integer values
else {
char s[10];
if (dv.numeric_operator > 0) {
obj["s"] = Helpers::render_value(s, (float)1 / dv.numeric_operator, 1);
obj["s"] = (float)1 / dv.numeric_operator;
} else if (dv.numeric_operator < 0) {
obj["s"] = Helpers::render_value(s, (float)(-1) * dv.numeric_operator, 0);
obj["s"] = (float)(-1) * dv.numeric_operator;
}
int16_t dv_set_min;
uint16_t dv_set_max;
if (dv.get_min_max(dv_set_min, dv_set_max)) {
obj["m"] = Helpers::render_value(s, dv_set_min, 0);
obj["x"] = Helpers::render_value(s, dv_set_max, 0);
obj["m"] = dv_set_min;
obj["x"] = dv_set_max;
}
}
}
@@ -976,7 +997,9 @@ void EMSdevice::generate_values_web_customization(JsonArray & output) {
// id holds the shortname and must always have a value for the WebUI table to work
if (dv.tag >= DeviceValueTAG::TAG_HC1) {
obj["id"] = tag_to_mqtt(dv.tag) + "/" + dv.short_name;
char id_s[50];
snprintf(id_s, sizeof(id_s), "%s/%s", tag_to_mqtt(dv.tag), dv.short_name);
obj["id"] = id_s;
} else {
obj["id"] = dv.short_name;
}
@@ -986,12 +1009,12 @@ void EMSdevice::generate_values_web_customization(JsonArray & output) {
auto fullname = Helpers::translated_word(dv.fullname);
if (dv.type != DeviceValueType::CMD) {
if (fullname) {
if ((dv.tag == DeviceValueTAG::TAG_NONE) || tag_to_string(dv.tag).empty()) {
obj["n"] = fullname;
} else {
if (dv.has_tag()) {
char name[50];
snprintf(name, sizeof(name), "%s %s", tag_to_string(dv.tag).c_str(), fullname);
snprintf(name, sizeof(name), "%s %s", tag_to_string(dv.tag), fullname);
obj["n"] = name;
} else {
obj["n"] = fullname;
}
}
@@ -1012,15 +1035,15 @@ void EMSdevice::generate_values_web_customization(JsonArray & output) {
int16_t dv_set_min;
uint16_t dv_set_max;
if (dv.get_min_max(dv_set_min, dv_set_max)) {
char s[10];
obj["mi"] = Helpers::render_value(s, dv_set_min, 0, fahrenheit);
obj["ma"] = Helpers::render_value(s, dv_set_max, 0, fahrenheit);
obj["mi"] = dv_set_min;
obj["ma"] = dv_set_max;
}
}
}
EMSESP::webCustomizationService.read([&](WebCustomization & settings) {
for (EntityCustomization entityCustomization : settings.entityCustomizations) {
if ((entityCustomization.device_id == device_id())) {
if (entityCustomization.device_id == device_id()) {
for (std::string entity_id : entityCustomization.entity_ids) {
uint8_t mask = Helpers::hextoint(entity_id.substr(0, 2).c_str());
if (mask & 0x80) {
@@ -1038,12 +1061,11 @@ void EMSdevice::generate_values_web_customization(JsonArray & output) {
void EMSdevice::set_climate_minmax(uint8_t tag, int16_t min, uint16_t max) {
for (auto & dv : devicevalues_) {
if (dv.tag == tag && dv.short_name == FL_(climate[0])) {
if (dv.tag == tag && (strcmp(dv.short_name, FL_(haclimate[0])) == 0)) {
if (dv.min != min || dv.max != max) {
dv.min = min;
dv.max = max;
dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED);
Mqtt::publish_ha_climate_config(dv.tag, false, true); // delete topic (remove = true)
}
return;
}
@@ -1054,7 +1076,12 @@ void EMSdevice::set_climate_minmax(uint8_t tag, int16_t min, uint16_t max) {
// returns true if the entity has a mask set (not 0 the default)
void EMSdevice::setCustomEntity(const std::string & entity_id) {
for (auto & dv : devicevalues_) {
std::string entity_name = dv.tag < DeviceValueTAG::TAG_HC1 ? dv.short_name : tag_to_mqtt(dv.tag) + "/" + dv.short_name;
char entity_name[70];
if (dv.tag < DeviceValueTAG::TAG_HC1) {
strncpy(entity_name, dv.short_name, sizeof(entity_name));
} else {
snprintf(entity_name, sizeof(entity_name), "%s/%s", tag_to_mqtt(dv.tag), dv.short_name);
}
// extra shortname
auto custom_name_pos = entity_id.find('|');
@@ -1101,9 +1128,18 @@ void EMSdevice::setCustomEntity(const std::string & entity_id) {
// populate a string vector with entities that have masks set or have a custom name
void EMSdevice::getCustomEntities(std::vector<std::string> & entity_ids) {
for (const auto & dv : devicevalues_) {
std::string entity_name = dv.tag < DeviceValueTAG::TAG_HC1 ? dv.short_name : tag_to_mqtt(dv.tag) + "/" + dv.short_name;
uint8_t mask = dv.state >> 4;
bool is_set = false;
char name[100];
name[0] = '\0';
if (dv.tag >= DeviceValueTAG::TAG_HC1) {
// prefix tag
strcpy(name, tag_to_mqtt(dv.tag));
strcat(name, "/");
}
strcat(name, dv.short_name);
std::string entity_name = name;
uint8_t mask = dv.state >> 4;
bool is_set = false;
for (auto & eid : entity_ids) {
if (DeviceValue::get_name(eid) == entity_name) {
is_set = true;
@@ -1120,7 +1156,7 @@ void EMSdevice::getCustomEntities(std::vector<std::string> & entity_ids) {
}
}
#if defined(EMSESP_STANDALONE_DUMP)
#if defined(EMSESP_STANDALONE)
// dumps all entity values in native English
// the code is intended to run only once standalone, outside the ESP32 so not optimized for memory efficiency
// pipe symbols (|) are escaped so they can be converted to Markdown in the Wiki
@@ -1228,7 +1264,6 @@ void EMSdevice::dump_value_info() {
char entityid[500];
char entity_name[100];
for (uint8_t count = 0; count < 2; count++) {
if (count) {
// new name, comes as last
@@ -1247,7 +1282,7 @@ void EMSdevice::dump_value_info() {
sizeof(entity_with_tag),
"%s_%s_%s",
device_type_2_device_name(device_type_),
EMSdevice::tag_to_mqtt(dv.tag).c_str(),
EMSdevice::tag_to_mqtt(dv.tag),
entity_name);
} else {
snprintf(entity_with_tag, sizeof(entity_with_tag), "%s_%s", device_type_2_device_name(device_type_), entity_name);
@@ -1276,7 +1311,7 @@ void EMSdevice::dump_value_info() {
if (dv.type == DeviceValueType::BOOL) {
snprintf(entityid, sizeof(entityid), "binary_sensor.%s", entity_with_tag); // binary sensor (for booleans)
} else {
snprintf(entityid, sizeof(entityid), "sensor.%s", entity_with_tag); // normal HA sensor
snprintf(entityid, sizeof(entityid), "sensor.%s", entity_with_tag); // normal HA sensor
}
}
@@ -1315,7 +1350,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
// search device value with this tag
for (auto & dv : devicevalues_) {
if (!strcmp(command_s, dv.short_name) && (tag <= 0 || tag == dv.tag)) {
if (Helpers::toLower(command_s) == Helpers::toLower(dv.short_name) && (tag <= 0 || tag == dv.tag)) {
uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (dv.uom == DeviceValueUOM::DEGREES) ? 2 : (dv.uom == DeviceValueUOM::DEGREES_R) ? 1 : 0;
const char * type = "type";
@@ -1325,14 +1360,16 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
auto fullname = dv.get_fullname();
if (!fullname.empty()) {
if ((dv.tag == DeviceValueTAG::TAG_NONE) || tag_to_string(dv.tag).empty()) {
json["fullname"] = fullname;
if (dv.has_tag()) {
char name[50];
snprintf(name, sizeof(name), "%s %s", tag_to_string(dv.tag), fullname.c_str());
json["fullname"] = name;
} else {
json["fullname"] = tag_to_string(dv.tag) + " " + fullname;
json["fullname"] = fullname;
}
}
if (!tag_to_mqtt(dv.tag).empty()) {
if (dv.tag != DeviceValueTAG::TAG_NONE) {
json["circuit"] = tag_to_mqtt(dv.tag);
}
@@ -1444,7 +1481,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
}
// add uom if it's not a " " (single space)
if (!uom_to_string(dv.uom).empty() && uom_to_string(dv.uom) != " ") {
if (dv.uom != DeviceValueUOM::NONE) {
json["uom"] = uom_to_string(dv.uom);
}
@@ -1460,7 +1497,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
// if we're filtering on an attribute, go find it
if (attribute_s) {
#if defined(EMSESP_DEBUG)
EMSESP::logger().debug("[DEBUG] Attribute '%s'", attribute_s);
EMSESP::logger().debug("Attribute '%s'", attribute_s);
#endif
if (json.containsKey(attribute_s)) {
JsonVariant data = json[attribute_s];
@@ -1480,6 +1517,9 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
}
}
char error[100];
snprintf(error, sizeof(error), "cannot find values for entity '%s'", cmd);
json["message"] = error;
return false;
}
@@ -1492,7 +1532,7 @@ void EMSdevice::publish_all_values() {
// For each value in the device create the json object pair and add it to given json
// return false if empty
// this is used to create both the MQTT payloads, Console messages and Web API calls
// this is used to create the MQTT payloads, Console messages and Web API calls
bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, const bool nested, const uint8_t output_target) {
bool has_values = false; // to see if we've added a value. it's faster than doing a json.size() at the end
uint8_t old_tag = 255; // NAN
@@ -1501,11 +1541,7 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
for (auto & dv : devicevalues_) {
// check if it exists, there is a value for the entity. Set the flag to ACTIVE
// not that this will override any previously removed states
if (dv.hasValue()) {
dv.add_state(DeviceValueState::DV_ACTIVE);
} else {
dv.remove_state(DeviceValueState::DV_ACTIVE);
}
(dv.hasValue()) ? dv.add_state(DeviceValueState::DV_ACTIVE) : dv.remove_state(DeviceValueState::DV_ACTIVE);
auto fullname = dv.get_fullname();
@@ -1519,16 +1555,22 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
has_values = true; // flagged if we actually have data
// we have a tag if it matches the filter given, and that the tag name is not empty/""
bool have_tag = ((dv.tag != tag_filter) && !tag_to_string(dv.tag).empty());
bool have_tag = ((dv.tag != tag_filter) && dv.has_tag());
// create the name for the JSON key
char name[80];
if (output_target == OUTPUT_TARGET::API_VERBOSE || output_target == OUTPUT_TARGET::CONSOLE) {
if (have_tag) {
snprintf(name, 80, "%s %s", tag_to_string(dv.tag).c_str(), fullname.c_str()); // prefix the tag
char short_name[20];
if (output_target == OUTPUT_TARGET::CONSOLE) {
snprintf(short_name, sizeof(short_name), " (%s)", dv.short_name);
} else {
strlcpy(name, fullname.c_str(), sizeof(name)); // use full name
strcpy(short_name, "");
}
if (have_tag) {
snprintf(name, sizeof(name), "%s %s%s", tag_to_string(dv.tag), fullname.c_str(), short_name); // prefix the tag
} else {
snprintf(name, sizeof(name), "%s%s", fullname.c_str(), short_name);
}
} else {
strlcpy(name, (dv.short_name), sizeof(name)); // use short name
@@ -1581,7 +1623,7 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
: (dv.uom == DeviceValueUOM::DEGREES) ? 2
: (dv.uom == DeviceValueUOM::DEGREES_R) ? 1
: 0;
char val[10];
char val[10] = {'\0'};
if (dv.type == DeviceValueType::INT) {
json[name] = serialized(Helpers::render_value(val, *(int8_t *)(dv.value_p), dv.numeric_operator, fahrenheit));
} else if (dv.type == DeviceValueType::UINT) {
@@ -1610,9 +1652,10 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
Helpers::translated_word(FL_(minutes)));
json[name] = time_s;
} else {
json[name] = time_value;
json[name] = serialized(Helpers::render_value(val, time_value, 0));
}
}
// commenting out - we don't want Commands in MQTT or Console
// else if (dv.type == DeviceValueType::CMD && output_target != EMSdevice::OUTPUT_TARGET::MQTT) {
// json[name] = "";
@@ -1621,18 +1664,16 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
// check for value outside min/max range and adapt the limits to avoid HA complains
// Should this also check for api output?
if ((output_target == OUTPUT_TARGET::MQTT) && (dv.min != 0 || dv.max != 0)) {
if (json[name].is<float>() || json[name].is<int>()) {
int v = json[name];
if (fahrenheit) {
v = (v - (32 * (fahrenheit - 1))) / 1.8; // reset to °C
}
if (v < dv.min) {
dv.min = v;
dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED);
} else if (v > dv.max) {
dv.max = v;
dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED);
}
int v = Helpers::atoint(val);
if (fahrenheit) {
v = (v - (32 * (fahrenheit - 1))) / 1.8; // reset to °C
}
if (v < dv.min) {
dv.min = v;
dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED);
} else if (v > dv.max) {
dv.max = v;
dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED);
}
}
}
@@ -1651,27 +1692,34 @@ void EMSdevice::mqtt_ha_entity_config_create() {
// create climate if roomtemp is visible
// create the discovery topic if if hasn't already been created, not a command (like reset) and is active and visible
for (auto & dv : devicevalues_) {
if ((dv.short_name == FL_(climate)[0]) && !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE) && dv.has_state(DeviceValueState::DV_ACTIVE)) {
if (!strcmp(dv.short_name, FL_(haclimate)[0]) && !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE) && dv.has_state(DeviceValueState::DV_ACTIVE)) {
if (*(int8_t *)(dv.value_p) == 1 && (!dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED) || dv.has_state(DeviceValueState::DV_HA_CLIMATE_NO_RT))) {
dv.remove_state(DeviceValueState::DV_HA_CLIMATE_NO_RT);
dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED);
Mqtt::publish_ha_climate_config(dv.tag, true, false, dv.min, dv.max);
if (Mqtt::publish_ha_climate_config(dv.tag, true, false, dv.min, dv.max)) { // roomTemp
dv.remove_state(DeviceValueState::DV_HA_CLIMATE_NO_RT);
dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED);
}
} else if (*(int8_t *)(dv.value_p) == 0
&& (!dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED) || !dv.has_state(DeviceValueState::DV_HA_CLIMATE_NO_RT))) {
dv.add_state(DeviceValueState::DV_HA_CLIMATE_NO_RT);
dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED);
Mqtt::publish_ha_climate_config(dv.tag, false, false, dv.min, dv.max);
if (Mqtt::publish_ha_climate_config(dv.tag, false, false, dv.min, dv.max)) { // no roomTemp
dv.add_state(DeviceValueState::DV_HA_CLIMATE_NO_RT);
dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED);
}
}
}
if (!dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED) && (dv.type != DeviceValueType::CMD) && dv.has_state(DeviceValueState::DV_ACTIVE)
&& !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE)) {
// create_device_config is only done once for the EMS device. It can added to any entity, so we take the first
Mqtt::publish_ha_sensor_config(dv, name(), brand_to_string(), false, create_device_config);
dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED);
create_device_config = false; // only create the main config once
}
if (ESP.getFreeHeap() < (65 * 1024)) {
break;
if (Mqtt::publish_ha_sensor_config(dv, name(), brand_to_char(), false, create_device_config)) {
dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED);
create_device_config = false; // only create the main config once
}
#ifndef EMSESP_STANDALONE
// always create minimum one config
if (ESP.getMaxAllocHeap() < (6 * 1024) || (!emsesp::EMSESP::system_.PSram() && ESP.getFreeHeap() < (65 * 1024))) {
break;
}
#endif
}
}
@@ -1697,7 +1745,7 @@ bool EMSdevice::has_telegram_id(uint16_t id) const {
}
// return the name of the telegram type
std::string EMSdevice::telegram_type_name(std::shared_ptr<const Telegram> telegram) const {
const char * EMSdevice::telegram_type_name(std::shared_ptr<const Telegram> telegram) {
// see if it's one of the common ones, like Version
if (telegram->type_id == EMS_TYPE_VERSION) {
return "Version";
@@ -1711,7 +1759,7 @@ std::string EMSdevice::telegram_type_name(std::shared_ptr<const Telegram> telegr
}
}
return std::string{};
return "";
}
// take a telegram_type_id and call the matching handler
@@ -1722,7 +1770,9 @@ bool EMSdevice::handle_telegram(std::shared_ptr<const Telegram> telegram) {
// if the data block is empty and we have not received data before, assume that this telegram
// is not recognized by the bus master. So remove it from the automatic fetch list
if (telegram->message_length == 0 && telegram->offset == 0 && !tf.received_) {
#if defined(EMSESP_DEBUG)
EMSESP::logger().debug("This telegram (%s) is not recognized by the EMS bus", tf.telegram_type_name_);
#endif
tf.fetch_ = false;
return false;
}

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -47,15 +47,17 @@ class EMSdevice {
// static functions, used outside the class like in console.cpp, command.cpp, emsesp.cpp, mqtt.cpp
static const char * 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, const bool translate = true);
static std::string tag_to_mqtt(uint8_t tag);
static uint8_t decode_brand(uint8_t value);
static const char * tag_to_string(uint8_t tag, const bool translate = true);
static const char * uom_to_string(uint8_t uom);
static const char * tag_to_mqtt(uint8_t tag);
static uint8_t decode_brand(uint8_t value);
const char * device_type_name(); // returns short non-translated device type name
const char * device_type_2_device_name_translated(); // returns translated device type name
bool has_tag(const uint8_t tag) const;
bool has_tags(const uint8_t tag) const;
bool has_cmd(const char * cmd, const int8_t id) const;
inline uint8_t device_id() const {
@@ -153,7 +155,7 @@ class EMSdevice {
}
}
inline void has_enumupdate(std::shared_ptr<const Telegram> telegram, uint8_t & value, const uint8_t index, uint8_t s = 0) {
inline void has_enumupdate(std::shared_ptr<const Telegram> telegram, uint8_t & value, const uint8_t index, int8_t s = 0) {
if (telegram->read_enumvalue(value, index, s)) {
has_update_ = true;
publish_value((void *)&value);
@@ -176,7 +178,7 @@ class EMSdevice {
}
}
const std::string brand_to_string();
const char * brand_to_char();
const std::string to_string();
const std::string to_string_short();
@@ -290,7 +292,7 @@ class EMSdevice {
void mqtt_ha_entity_config_create();
std::string telegram_type_name(std::shared_ptr<const Telegram> telegram) const;
const char * telegram_type_name(std::shared_ptr<const Telegram> telegram);
void fetch_values();
void toggle_fetch(uint16_t telegram_id, bool toggle);
@@ -317,9 +319,10 @@ class EMSdevice {
};
enum DeviceType : uint8_t {
SYSTEM = 0, // this is us (EMS-ESP)
DALLASSENSOR, // for internal dallas sensors
ANALOGSENSOR, // for internal analog sensors
SYSTEM = 0, // this is us (EMS-ESP)
TEMPERATURESENSOR, // for internal temperature sensors
ANALOGSENSOR, // for internal analog sensors
SCHEDULER,
BOILER,
THERMOSTAT,
MIXER,
@@ -333,6 +336,8 @@ class EMSdevice {
PUMP,
GENERIC,
HEATSOURCE,
CUSTOM,
VENTILATION,
UNKNOWN
};
@@ -376,6 +381,7 @@ class EMSdevice {
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;
static constexpr uint8_t EMS_DEVICE_FLAG_HIU = 6;
// Solar Module
static constexpr uint8_t EMS_DEVICE_FLAG_SM10 = 1;
@@ -408,7 +414,17 @@ class EMSdevice {
uint8_t count_entities();
bool has_entities() const;
#if defined(EMSESP_STANDALONE_DUMP)
/*
void reserve_device_values(uint8_t elements) {
devicevalues_.reserve(elements);
}
void reserve_telegram_functions(uint8_t elements) {
telegram_functions_.reserve(elements);
}
*/
#if defined(EMSESP_STANDALONE)
void dump_value_info();
#endif
@@ -422,15 +438,15 @@ class EMSdevice {
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;
struct TelegramFunction {
uint16_t telegram_type_id_; // it's type_id
const char * telegram_type_name_; // e.g. RC20Message
bool fetch_; // if this type_id be queried automatically
bool received_;
process_function_p process_function_;
const uint16_t telegram_type_id_; // it's type_id
const char * telegram_type_name_; // e.g. RC20Message
bool fetch_; // if this type_id be queried automatically
bool received_;
const process_function_p process_function_;
TelegramFunction(uint16_t telegram_type_id, const char * telegram_type_name, bool fetch, bool received, const process_function_p process_function)
: telegram_type_id_(telegram_type_id)
@@ -441,13 +457,9 @@ class EMSdevice {
}
};
#ifdef EMSESP_STANDALONE
void debug_print_dv(const char * shortname);
#endif
std::vector<TelegramFunction> telegram_functions_; // each EMS device has its own set of registered telegram types
std::vector<DeviceValue> devicevalues_; // all the device values
std::vector<DeviceValue> devicevalues_; // all the device values
std::vector<uint16_t> handlers_ignored_;
};

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -107,14 +107,14 @@ DeviceValue::DeviceValue(uint8_t device_type,
// must be an int of 4 bytes, 32bit aligned
const char * DeviceValue::DeviceValueUOM_s[] = {
F_(uom_blank), F_(uom_degrees), F_(uom_degrees), F_(uom_percent), F_(uom_lmin), F_(uom_kwh), F_(uom_wh), FL_(hours)[0], FL_(minutes)[0],
F_(uom_ua), F_(uom_bar), F_(uom_kw), F_(uom_w), F_(uom_kb), FL_(seconds)[0], F_(uom_dbm), F_(uom_fahrenheit), F_(uom_mv),
F_(uom_sqm), F_(uom_m3), F_(uom_l), F_(uom_kmin), F_(uom_k), F_(uom_blank)
F_(uom_blank), // 0
F_(uom_degrees), F_(uom_degrees), F_(uom_percent), F_(uom_lmin), F_(uom_kwh), F_(uom_wh), FL_(hours)[0], FL_(minutes)[0],
F_(uom_ua), F_(uom_bar), F_(uom_kw), F_(uom_w), F_(uom_kb), FL_(seconds)[0], F_(uom_dbm), F_(uom_fahrenheit),
F_(uom_mv), F_(uom_sqm), F_(uom_m3), F_(uom_l), F_(uom_kmin), F_(uom_k), F_(uom_blank)
};
// mapping of TAGs, to match order in DeviceValueTAG enum in emsdevice.h
// must be an int of 4 bytes, 32bit aligned
// mapping of TAGs, to match order in DeviceValueTAG enum in emsdevicevalue.h
const char * const * DeviceValue::DeviceValueTAG_s[] = {
FL_(tag_none), // ""
@@ -160,7 +160,7 @@ const char * const * DeviceValue::DeviceValueTAG_s[] = {
};
// MQTT topics derived from tags
// tags used in MQTT topic names. Macthes sequence from DeviceValueTAG_s
const char * const DeviceValue::DeviceValueTAG_mqtt[] = {
FL_(tag_none)[0], // ""
@@ -207,7 +207,7 @@ const char * const DeviceValue::DeviceValueTAG_mqtt[] = {
};
// count #tags once at compile time
size_t DeviceValue::tag_count = sizeof(DeviceValue::DeviceValueTAG_s) / sizeof(char * const *);
uint8_t DeviceValue::NUM_TAGS = sizeof(DeviceValue::DeviceValueTAG_s) / sizeof(char * const *);
// checks whether the device value has an actual value
// returns true if its valid
@@ -252,6 +252,11 @@ bool DeviceValue::hasValue() const {
return has_value;
}
// See if the device value has a tag and it's not empty
bool DeviceValue::has_tag() const {
return ((tag < DeviceValue::NUM_TAGS) && (tag != TAG_NONE) && strlen(DeviceValueTAG_s[tag][0]));
}
// 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

View File

@@ -1,7 +1,7 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -145,12 +145,12 @@ class DeviceValue {
DV_NUMOP_MUL15 = -15
};
uint8_t device_type; // EMSdevice::DeviceType
uint8_t tag; // DeviceValueTAG::*
void * value_p; // pointer to variable of any type
uint8_t type; // DeviceValueType::*
const char * const ** options; // options as a flash char array
const char * const * options_single; // options are not translated
uint8_t device_type; // EMSdevice::DeviceType
uint8_t tag; // DeviceValueTAG::*
void * value_p; // pointer to variable of any type
uint8_t type; // DeviceValueType::*
const char * const ** options; // options as a flash char array
const char * const * options_single; // options are not translated
int8_t numeric_operator;
uint8_t options_size; // number of options in the char array, calculated
const char * const short_name; // used in MQTT and API
@@ -179,6 +179,7 @@ class DeviceValue {
uint8_t state);
bool hasValue() const;
bool has_tag() const;
bool get_min_max(int16_t & dv_set_min, uint16_t & dv_set_max);
void set_custom_minmax();
@@ -205,7 +206,7 @@ class DeviceValue {
static const char * DeviceValueUOM_s[];
static const char * const * DeviceValueTAG_s[];
static const char * const DeviceValueTAG_mqtt[];
static size_t tag_count; // # tags
static uint8_t NUM_TAGS; // # tags
};
}; // namespace emsesp

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -18,6 +18,10 @@
#include "emsesp.h"
static_assert(uuid::thread_safe, "uuid-common must be thread-safe");
static_assert(uuid::log::thread_safe, "uuid-log must be thread-safe");
static_assert(uuid::console::thread_safe, "uuid-console must be thread-safe");
namespace emsesp {
AsyncWebServer webServer(80);
@@ -27,10 +31,14 @@ FS dummyFS;
ESP8266React EMSESP::esp8266React(&webServer, &dummyFS);
WebSettingsService EMSESP::webSettingsService = WebSettingsService(&webServer, &dummyFS, EMSESP::esp8266React.getSecurityManager());
WebCustomizationService EMSESP::webCustomizationService = WebCustomizationService(&webServer, &dummyFS, EMSESP::esp8266React.getSecurityManager());
WebSchedulerService EMSESP::webSchedulerService = WebSchedulerService(&webServer, &dummyFS, EMSESP::esp8266React.getSecurityManager());
WebEntityService EMSESP::webEntityService = WebEntityService(&webServer, &dummyFS, EMSESP::esp8266React.getSecurityManager());
#else
ESP8266React EMSESP::esp8266React(&webServer, &LittleFS);
WebSettingsService EMSESP::webSettingsService = WebSettingsService(&webServer, &LittleFS, EMSESP::esp8266React.getSecurityManager());
WebCustomizationService EMSESP::webCustomizationService = WebCustomizationService(&webServer, &LittleFS, EMSESP::esp8266React.getSecurityManager());
WebSchedulerService EMSESP::webSchedulerService = WebSchedulerService(&webServer, &LittleFS, EMSESP::esp8266React.getSecurityManager());
WebEntityService EMSESP::webEntityService = WebEntityService(&webServer, &LittleFS, EMSESP::esp8266React.getSecurityManager());
#endif
WebStatusService EMSESP::webStatusService = WebStatusService(&webServer, EMSESP::esp8266React.getSecurityManager());
@@ -49,15 +57,18 @@ uuid::log::Logger EMSESP::logger() {
return logger_;
}
#ifndef EMSESP_STANDALONE
uuid::syslog::SyslogService System::syslog_;
#endif
// The services
RxService EMSESP::rxservice_; // incoming Telegram Rx handler
TxService EMSESP::txservice_; // outgoing Telegram Tx handler
Mqtt EMSESP::mqtt_; // mqtt handler
System EMSESP::system_; // core system services
Console EMSESP::console_; // telnet and serial console
DallasSensor EMSESP::dallassensor_; // Dallas sensors
AnalogSensor EMSESP::analogsensor_; // Analog sensors
Shower EMSESP::shower_; // Shower logic
RxService EMSESP::rxservice_; // incoming Telegram Rx handler
TxService EMSESP::txservice_; // outgoing Telegram Tx handler
Mqtt EMSESP::mqtt_; // mqtt handler
System EMSESP::system_; // core system services
TemperatureSensor EMSESP::temperaturesensor_; // Temperature sensors
AnalogSensor EMSESP::analogsensor_; // Analog sensors
Shower EMSESP::shower_; // Shower logic
// static/common variables
uint16_t EMSESP::watch_id_ = WATCH_ID_NONE; // for when log is TRACE. 0 means no trace set
@@ -65,6 +76,7 @@ uint8_t EMSESP::watch_ = 0; // trace off
uint16_t EMSESP::read_id_ = WATCH_ID_NONE;
bool EMSESP::read_next_ = false;
uint16_t EMSESP::publish_id_ = 0;
uint16_t EMSESP::response_id_ = 0;
bool EMSESP::tap_water_active_ = false; // for when Boiler states we having running warm water. used in Shower()
uint32_t EMSESP::last_fetch_ = 0;
uint8_t EMSESP::publish_all_idx_ = 0;
@@ -309,7 +321,7 @@ void EMSESP::show_ems(uuid::console::Shell & shell) {
// Dump all entities to Serial out
// this is intended to run within the OS with lots of available memory!
#if defined(EMSESP_STANDALONE_DUMP)
#if defined(EMSESP_STANDALONE)
void EMSESP::dump_all_values(uuid::console::Shell & shell) {
Serial.println("---- CSV START ----"); // marker use by py script
// add header for CSV
@@ -322,19 +334,27 @@ void EMSESP::dump_all_values(uuid::console::Shell & shell) {
for (const auto & device : device_library_) {
if (device_class.first == device.device_type) {
uint8_t device_id = 0;
// Mixer class looks at device_id to determine type, so fixing to 0x28 which will give all the settings except flowSetTemp
if ((device.device_type == DeviceType::MIXER) && (device.flags == EMSdevice::EMS_DEVICE_FLAG_MMPLUS)) {
// pick one as hc and the other as having wwc
if (device.product_id == 160) { // MM100
device_id = 0x28; // wwc
// Mixer class looks at device_id to determine type and the tag
// so fixing to 0x28 which will give all the settings except flowSetTemp
if (device.device_type == DeviceType::MIXER) {
if (device.flags == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) {
if (device.product_id == 160) { // MM100
device_id = 0x28; // wwc
} else {
device_id = 0x20; // hc
}
} else {
device_id = 0x20; // hc
device_id = 0x20; // should cover all the other device types
}
}
// add the device and print out all the entities
// if (device.product_id == 69) { // only for testing mixer
emsdevices.push_back(
EMSFactory::add(device.device_type, device_id, device.product_id, "1.0", device.name, device.flags, EMSdevice::Brand::NO_BRAND));
emsdevices.back()->dump_value_info(); // dump all the entity information
emsdevices.back()->dump_value_info();
// } // only for testing mixer
}
}
}
@@ -358,13 +378,12 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) {
// print header, with device type translated
shell.printfln("%s: %s (%d)", emsdevice->device_type_2_device_name_translated(), emsdevice->to_string().c_str(), emsdevice->count_entities());
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XXLARGE_DYN); // use max size
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XXXLARGE); // use max size
JsonObject json = doc.to<JsonObject>();
emsdevice->generate_values(json, DeviceValueTAG::TAG_NONE, true, EMSdevice::OUTPUT_TARGET::CONSOLE);
// print line
uint8_t id = 0;
for (JsonPair p : json) {
const char * key = p.key().c_str();
shell.printf(" %s: ", key);
@@ -383,7 +402,6 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) {
shell.print(COLOR_RESET);
shell.println();
id++;
}
shell.println();
}
@@ -391,15 +409,15 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) {
}
}
// show Dallas temperature sensors and Analog sensors
// show temperature sensors and Analog sensors
void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
if (dallassensor_.have_sensors()) {
if (temperaturesensor_.have_sensors()) {
shell.printfln("Temperature sensors:");
char s[10];
char s2[10];
uint8_t fahrenheit = EMSESP::system_.fahrenheit() ? 2 : 0;
for (const auto & sensor : dallassensor_.sensors()) {
for (const auto & sensor : temperaturesensor_.sensors()) {
if (Helpers::hasValue(sensor.temperature_c)) {
shell.printfln(" %s: %s%s °%c%s (offset %s, ID: %s)",
sensor.name().c_str(),
@@ -427,7 +445,7 @@ void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
sensor.name().c_str(),
COLOR_BRIGHT_GREEN,
Helpers::render_value(s, sensor.value(), 2),
EMSdevice::uom_to_string(sensor.uom()).c_str(),
EMSdevice::uom_to_string(sensor.uom()),
COLOR_RESET,
Helpers::render_value(s2, sensor.factor(), 4),
sensor.offset());
@@ -461,20 +479,22 @@ void EMSESP::publish_all(bool force) {
publish_device_values(EMSdevice::DeviceType::THERMOSTAT);
publish_device_values(EMSdevice::DeviceType::SOLAR);
publish_device_values(EMSdevice::DeviceType::MIXER);
publish_other_values(); // switch and heat pump, ...
publish_sensor_values(true); // includes dallas and analog sensors
publish_other_values(); // switch and heat pump, ...
webSchedulerService.publish();
webEntityService.publish();
publish_sensor_values(true); // includes temperature and analog sensors
system_.send_heartbeat();
}
}
// on command "publish HA" loop and wait between devices for publishing all sensors
// loop and wait between devices for publishing all values
void EMSESP::publish_all_loop() {
if (!Mqtt::connected() || !publish_all_idx_) {
return;
}
// wait for free queue before sending next message, HA-messages are also queued
if (!Mqtt::is_empty()) {
if (Mqtt::publish_queued() > 0) {
return;
}
@@ -493,6 +513,8 @@ void EMSESP::publish_all_loop() {
break;
case 5:
publish_other_values(); // switch and heat pump
webSchedulerService.publish(true);
webEntityService.publish(true);
break;
case 6:
publish_sensor_values(true, true);
@@ -520,16 +542,16 @@ void EMSESP::reset_mqtt_ha() {
emsdevice->ha_config_clear();
}
// force the re-creating of the dallas and analog sensor topics (for HA)
dallassensor_.reload();
// force the re-creating of the temperature and analog sensor topics (for HA)
temperaturesensor_.reload();
analogsensor_.reload();
}
// create json doc for the devices values and add to MQTT publish queue
// this will also create the HA /config topic
// this will also create the HA /config topic for each device value
// generate_values_json is called to build the device value (dv) object array
void EMSESP::publish_device_values(uint8_t device_type) {
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN);
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XXLARGE);
JsonObject json = doc.to<JsonObject>();
bool need_publish = false;
bool nested = (Mqtt::is_nested());
@@ -540,7 +562,7 @@ void EMSESP::publish_device_values(uint8_t device_type) {
bool nest_created = false;
for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_type)) {
if (nested && !nest_created && emsdevice->has_tag(tag)) {
if (nested && !nest_created && emsdevice->has_tags(tag)) {
json_hc = doc.createNestedObject(EMSdevice::tag_to_mqtt(tag));
nest_created = true;
}
@@ -548,16 +570,17 @@ void EMSESP::publish_device_values(uint8_t device_type) {
}
}
if (need_publish && ((!nested && tag >= DeviceValueTAG::TAG_DEVICE_DATA_WW) || (tag == DeviceValueTAG::TAG_BOILER_DATA_WW))) {
Mqtt::publish(Mqtt::tag_to_topic(device_type, tag), json);
Mqtt::queue_publish(Mqtt::tag_to_topic(device_type, tag), json);
json = doc.to<JsonObject>();
need_publish = false;
}
}
if (need_publish) {
if (doc.overflowed()) {
LOG_WARNING("MQTT buffer overflow, please use individual topics");
}
Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_NONE), json);
Mqtt::queue_publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_NONE), json);
}
// we want to create the /config topic after the data payload to prevent HA from throwing up a warning
@@ -575,19 +598,21 @@ void EMSESP::publish_other_values() {
publish_device_values(EMSdevice::DeviceType::SWITCH);
publish_device_values(EMSdevice::DeviceType::HEATPUMP);
publish_device_values(EMSdevice::DeviceType::HEATSOURCE);
publish_device_values(EMSdevice::DeviceType::VENTILATION);
// other devices without values yet
// publish_device_values(EMSdevice::DeviceType::GATEWAY);
// publish_device_values(EMSdevice::DeviceType::CONNECT);
// publish_device_values(EMSdevice::DeviceType::ALERT);
// publish_device_values(EMSdevice::DeviceType::PUMP);
// publish_device_values(EMSdevice::DeviceType::GENERIC);
webEntityService.publish();
}
// publish both the dallas and analog sensor values
// publish both the temperature and analog sensor values
void EMSESP::publish_sensor_values(const bool time, const bool force) {
if (dallas_enabled()) {
if (dallassensor_.updated_values() || time || force) {
dallassensor_.publish_values(force);
if (sensor_enabled()) {
if (temperaturesensor_.updated_values() || time || force) {
temperaturesensor_.publish_values(force);
}
}
@@ -600,28 +625,39 @@ void EMSESP::publish_sensor_values(const bool time, const bool force) {
// MQTT publish a telegram as raw data to the topic 'response'
void EMSESP::publish_response(std::shared_ptr<const Telegram> telegram) {
StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> doc;
static char * buffer = nullptr;
static uint8_t offset;
if (buffer == nullptr) {
offset = telegram->offset; // store offset from first part
buffer = new char[768]; // max 256 hex-codes, 255 spaces, 1 termination
buffer[0] = '\0';
}
strlcat(buffer, Helpers::data_to_hex(telegram->message_data, telegram->message_length).c_str(), 768);
if (response_id_ != 0) {
strlcat(buffer, " ", 768);
return;
}
DynamicJsonDocument doc(EMSESP_JSON_SIZE_LARGE);
char s[10];
doc["src"] = Helpers::hextoa(s, telegram->src);
doc["dest"] = Helpers::hextoa(s, telegram->dest);
doc["type"] = Helpers::hextoa(s, telegram->type_id);
doc["offset"] = Helpers::hextoa(s, offset);
doc["data"] = buffer;
char buffer[100];
doc["src"] = Helpers::hextoa(buffer, telegram->src);
doc["dest"] = Helpers::hextoa(buffer, telegram->dest);
doc["type"] = Helpers::hextoa(buffer, telegram->type_id);
doc["offset"] = Helpers::hextoa(buffer, telegram->offset);
strlcpy(buffer, Helpers::data_to_hex(telegram->message_data, telegram->message_length).c_str(), sizeof(buffer)); // telegram is without crc
doc["data"] = buffer;
if (telegram->message_length <= 4) {
if (telegram->message_length <= 4 && strlen(buffer) <= 11) {
uint32_t value = 0;
for (uint8_t i = 0; i < telegram->message_length; i++) {
value = (value << 8) + telegram->message_data[i];
}
doc["value"] = value;
}
Mqtt::publish("response", doc.as<JsonObject>());
Mqtt::queue_publish("response", doc.as<JsonObject>());
delete[] buffer;
buffer = nullptr;
}
// builds json with the detail of each value, for a specific EMS device type or the dallas sensor
// builds json with the detail of each value, for a specific EMS device type or the temperature sensor
bool EMSESP::get_device_value_info(JsonObject & root, const char * cmd, const int8_t id, const uint8_t devicetype) {
for (const auto & emsdevice : emsdevices) {
if (emsdevice->device_type() == devicetype) {
@@ -631,16 +667,24 @@ bool EMSESP::get_device_value_info(JsonObject & root, const char * cmd, const in
}
}
// specific for the dallassensor
if (devicetype == DeviceType::DALLASSENSOR) {
EMSESP::dallassensor_.get_value_info(root, cmd, id);
return true;
// specific for the temperaturesensor
if (devicetype == DeviceType::TEMPERATURESENSOR) {
return EMSESP::temperaturesensor_.get_value_info(root, cmd, id);
}
// analog sensor
if (devicetype == DeviceType::ANALOGSENSOR) {
EMSESP::analogsensor_.get_value_info(root, cmd, id);
return true;
return EMSESP::analogsensor_.get_value_info(root, cmd, id);
}
// scheduler
if (devicetype == DeviceType::SCHEDULER) {
return EMSESP::webSchedulerService.get_value_info(root, cmd);
}
// own entities
if (devicetype == DeviceType::CUSTOM) {
return EMSESP::webEntityService.get_value_info(root, cmd);
}
char error[100];
@@ -815,12 +859,19 @@ void EMSESP::process_version(std::shared_ptr<const Telegram> telegram) {
// returns false if there are none found
bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
// if watching or reading...
if ((telegram->type_id == read_id_) && (telegram->dest == txservice_.ems_bus_id())) {
LOG_INFO("%s", pretty_telegram(telegram).c_str());
if (Mqtt::send_response()) {
if ((telegram->type_id == read_id_ || telegram->type_id == response_id_) && (telegram->dest == txservice_.ems_bus_id())) {
if (telegram->type_id == response_id_) {
if (!trace_raw_) {
LOG_TRACE("%s", pretty_telegram(telegram).c_str());
}
if (!read_next_) {
response_id_ = 0;
}
publish_response(telegram);
} else {
LOG_NOTICE("%s", pretty_telegram(telegram).c_str());
}
// check if read is finished or gives more parts
if (!read_next_) {
read_id_ = WATCH_ID_NONE;
}
@@ -843,6 +894,9 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
return false;
}
// Check for custom entities reding this telegram
webEntityService.get_value(telegram);
// check for common types, like the Version(0x02)
if (telegram->type_id == EMSdevice::EMS_TYPE_VERSION) {
process_version(telegram);
@@ -872,7 +926,7 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
if (telegram->type_id == publish_id_) {
publish_id_ = 0;
}
emsdevice->has_update(false); // reset flag
emsdevice->has_update(false); // reset flag
if (!Mqtt::publish_single()) {
publish_device_values(emsdevice->device_type()); // publish to MQTT if we explicitly have too
}
@@ -916,7 +970,7 @@ bool EMSESP::device_exists(const uint8_t device_id) {
// for each associated EMS device go and get its system information
void EMSESP::show_devices(uuid::console::Shell & shell) {
if (emsdevices.empty()) {
shell.printfln("No EMS devices detected. Try using 'scan devices' from the ems menu.");
shell.printfln("No EMS devices detected");
shell.println();
return;
}
@@ -925,12 +979,14 @@ void EMSESP::show_devices(uuid::console::Shell & shell) {
shell.println();
// count the number of thermostats
/*
uint8_t num_thermostats = 0;
for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() == DeviceType::THERMOSTAT)) {
num_thermostats++;
}
}
*/
// for all device objects from emsdevice.h
// so we keep a consistent order
@@ -1040,6 +1096,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
name = "RF room temperature sensor";
device_type = DeviceType::THERMOSTAT;
} else if (device_id == EMSdevice::EMS_DEVICE_ID_ROOMTHERMOSTAT || device_id == EMSdevice::EMS_DEVICE_ID_TADO_OLD) {
// see https://github.com/emsesp/EMS-ESP32/issues/174
name = "Generic thermostat";
device_type = DeviceType::THERMOSTAT;
flags = DeviceFlags::EMS_DEVICE_FLAG_RC10 | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE;
@@ -1050,31 +1107,32 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
name = "Terminal";
device_type = DeviceType::CONNECT;
} else if (device_id == EMSdevice::EMS_DEVICE_ID_SERVICEKEY) {
name = "Service key";
name = "Service Key";
device_type = DeviceType::CONNECT;
} else if (device_id == EMSdevice::EMS_DEVICE_ID_CASCADE) {
name = "Cascade";
device_type = DeviceType::CONNECT;
} else if (device_id == EMSdevice::EMS_DEVICE_ID_EASYCOM) {
} else if (device_id == EMSdevice::EMS_DEVICE_ID_EASYCOM
|| (device_id >= EMSdevice::EMS_DEVICE_ID_MODEM && device_id <= EMSdevice::EMS_DEVICE_ID_MODEM + 5)) {
// see https://github.com/emsesp/EMS-ESP/issues/460#issuecomment-709553012
name = "Modem";
device_type = DeviceType::CONNECT;
} else if (device_id == EMSdevice::EMS_DEVICE_ID_CONVERTER) {
name = "Converter"; // generic
name = "Converter"; // generic
} else if (device_id == EMSdevice::EMS_DEVICE_ID_CLOCK) {
name = "Clock"; // generic
device_type = DeviceType::CONTROLLER;
} else if (device_id == EMSdevice::EMS_DEVICE_ID_CONTROLLER) {
name = "Generic controller";
name = "Generic Controller";
device_type = DeviceType::CONTROLLER;
} else if (device_id == EMSdevice::EMS_DEVICE_ID_BOILER) {
name = "Generic boiler";
name = "Generic Boiler";
device_type = DeviceType::BOILER;
flags = DeviceFlags::EMS_DEVICE_FLAG_HEATPUMP;
LOG_WARNING("Unknown EMS boiler. Using generic profile. Please report on GitHub.");
} else if (device_id >= 0x68 && device_id <= 0x6F) {
// test for https://github.com/emsesp/EMS-ESP32/issues/882
name = "Cascaded controller";
name = "Cascaded Controller";
device_type = DeviceType::CONTROLLER;
} else {
LOG_WARNING("Unrecognized EMS device (device ID 0x%02X, no product ID). Please report on GitHub.", device_id);
@@ -1086,6 +1144,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
emsdevices.push_back(EMSFactory::add(device_type, device_id, product_id, version, name, flags, brand));
// assign a unique ID. Note that this is not actual unique after a restart as it's dependent on the order that devices are found
// can't be 0 otherwise web won't work
emsdevices.back()->unique_id(++unique_id_count_);
// sort devices based on type
@@ -1181,7 +1240,7 @@ bool EMSESP::command_info(uint8_t device_type, JsonObject & output, const int8_t
bool nest_created = false;
for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_type)) {
if (!nest_created && emsdevice->has_tag(tag)) {
if (!nest_created && emsdevice->has_tags(tag)) {
output_hc = output.createNestedObject(EMSdevice::tag_to_mqtt(tag));
nest_created = true;
}
@@ -1193,8 +1252,8 @@ bool EMSESP::command_info(uint8_t device_type, JsonObject & output, const int8_t
}
// send a read request, passing it into to the Tx Service, with optional offset and length
void EMSESP::send_read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t length) {
txservice_.read_request(type_id, dest, offset, length);
void EMSESP::send_read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t length, const bool front) {
txservice_.read_request(type_id, dest, offset, length, front);
}
// sends write request
@@ -1257,9 +1316,10 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
} else if (first_value == TxService::TX_WRITE_FAIL) {
LOG_ERROR("Last Tx write rejected by host");
txservice_.send_poll(); // close the bus
txservice_.reset_retry_count();
tx_successful = true;
}
} else if (tx_state == Telegram::Operation::TX_READ && length == 1) {
EMSbus::tx_state(Telegram::Operation::TX_READ); // reset Tx wait state
return;
} else if (tx_state == Telegram::Operation::TX_READ) {
// got a telegram with data in it. See if the src/dest matches that from the last one we sent and continue to process it
uint8_t src = data[0];
@@ -1267,13 +1327,16 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
if (txservice_.is_last_tx(src, dest)) {
LOG_DEBUG("Last Tx read successful");
txservice_.increment_telegram_read_count();
txservice_.send_poll(); // close the bus
txservice_.reset_retry_count();
tx_successful = true;
// if telegram is longer read next part with offset +25 for ems+ or +27 for ems1.0
if ((length >= 31) && (txservice_.read_next_tx(data[3], length) == read_id_)) {
// not for response to raw send commands without read_id set
if ((response_id_ == 0 || read_id_ > 0) && (length >= 31) && (txservice_.read_next_tx(data[3], length) == read_id_)) {
read_next_ = true;
txservice_.send();
} else {
txservice_.send_poll(); // close the bus
}
}
}
@@ -1327,93 +1390,10 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
#endif
Roomctrl::check((data[1] ^ 0x80 ^ rxservice_.ems_mask()), data); // check if there is a message for the roomcontroller
rxservice_.add(data, length); // add to RxQueue
rxservice_.add(data, length); // add to RxQueue
}
}
// start all the core services
// the services must be loaded in the correct order
void EMSESP::start() {
console_.start_serial();
// start the file system
#ifndef EMSESP_STANDALONE
if (!LittleFS.begin(true)) {
Serial.println("LittleFS Mount Failed. EMS-ESP stopped.");
return;
}
#endif
// do a quick scan of the filesystem to see if we have a /config folder
// so we know if this is a new install or not
#ifndef EMSESP_STANDALONE
File root = LittleFS.open("/config");
bool factory_settings = !root;
if (!root) {
#ifdef EMSESP_DEBUG
Serial.println("No config found, assuming factory settings");
#endif
}
root.close();
#else
bool factory_settings = false;
#endif
esp8266React.begin(); // loads core system services settings (network, mqtt, ap, ntp etc)
webLogService.begin(); // start web log service. now we can start capturing logs to the web log
#ifdef EMSESP_DEBUG
LOG_NOTICE("System is running in Debug mode");
#endif
LOG_INFO("Last system reset reason Core0: %s, Core1: %s", system_.reset_reason(0).c_str(), system_.reset_reason(1).c_str());
// see if we're restoring a settings file
if (system_.check_restore()) {
LOG_WARNING("System needs a restart to apply new settings. Please wait.");
system_.system_restart();
};
webSettingsService.begin(); // load EMS-ESP Application settings...
// do any system upgrades
if (system_.check_upgrade(factory_settings)) {
LOG_WARNING("System needs a restart to apply new settings. Please wait.");
system_.system_restart();
};
system_.reload_settings(); // ... and store some of the settings locally
webCustomizationService.begin(); // load the customizations
// start telnet service if it's enabled
if (system_.telnet_enabled()) {
console_.start_telnet();
}
// start all the EMS-ESP services
mqtt_.start(); // mqtt init
system_.start(); // starts commands, led, adc, button, network, syslog & uart
LOG_INFO(("Starting EMS-ESP version %s (hostname: %s)"), EMSESP_APP_VERSION, system_.hostname().c_str()); // welcome message
shower_.start(); // initialize shower timer and shower alert
dallassensor_.start(); // Dallas external sensors
analogsensor_.start(); // Analog external sensors
webLogService.start(); // apply settings to weblog service
// Load our library of known devices into stack mem. Names are stored in Flash memory
device_library_ = {
#include "device_library.h"
};
LOG_INFO("Loaded EMS device library (%d records)", device_library_.size());
#if defined(EMSESP_STANDALONE)
Mqtt::on_connect(); // simulate an MQTT connection
#endif
webServer.begin(); // start the web server
}
// fetch devices one by one
void EMSESP::scheduled_fetch_values() {
static uint8_t no = 0;
@@ -1432,34 +1412,156 @@ void EMSESP::scheduled_fetch_values() {
return;
}
}
webEntityService.fetch();
no = 0;
}
}
}
// EMSESP main class
EMSESP::EMSESP()
#ifndef EMSESP_STANDALONE
: telnet_([this](Stream & stream, const IPAddress & addr, uint16_t port) -> std::shared_ptr<uuid::console::Shell> {
return std::make_shared<emsesp::EMSESPConsole>(*this, stream, addr, port);
})
#endif
{
}
// start all the core services
// the services must be loaded in the correct order
void EMSESP::start() {
serial_console_.begin(SERIAL_CONSOLE_BAUD_RATE);
shell_ = std::make_shared<EMSESPConsole>(*this, serial_console_, true);
shell_->maximum_log_messages(100);
shell_->start();
#if defined(EMSESP_DEBUG)
shell_->log_level(uuid::log::Level::DEBUG);
#else
shell_->log_level(uuid::log::Level::TRACE);
#endif
#if defined(EMSESP_STANDALONE)
shell_->add_flags(CommandFlags::ADMIN); // always start in su/admin mode when running tests
#endif
// start the file system
#ifndef EMSESP_STANDALONE
if (!LittleFS.begin(true)) {
Serial.println("LittleFS Mount Failed. EMS-ESP stopped.");
return;
}
#endif
// do a quick scan of the filesystem to see if we have a /config folder
// so we know if this is a new install or not
#ifndef EMSESP_STANDALONE
File root = LittleFS.open("/config");
bool factory_settings = !root;
if (!root) {
#if defined(EMSESP_DEBUG)
Serial.println("No config found, assuming factory settings");
#endif
}
root.close();
#else
bool factory_settings = false;
#endif
esp8266React.begin(); // loads core system services settings (network, mqtt, ap, ntp etc)
webLogService.begin(); // start web log service. now we can start capturing logs to the web log
LOG_INFO("Starting EMS-ESP version %s", EMSESP_APP_VERSION); // welcome message
LOG_DEBUG("System is running in Debug mode");
LOG_INFO("Last system reset reason Core0: %s, Core1: %s", system_.reset_reason(0).c_str(), system_.reset_reason(1).c_str());
// see if we're restoring a settings file
if (system_.check_restore()) {
LOG_WARNING("System needs a restart to apply new settings. Please wait.");
system_.system_restart();
};
webSettingsService.begin(); // load EMS-ESP Application settings...
// do any system upgrades
if (system_.check_upgrade(factory_settings)) {
LOG_WARNING("System needs a restart to apply new settings. Please wait.");
system_.system_restart();
};
system_.reload_settings(); // ... and store some of the settings locally
webCustomizationService.begin(); // load the customizations
webSchedulerService.begin(); // load the scheduler events
webEntityService.begin(); // load the custom telegram reads
// start telnet service if it's enabled
// 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
if (system_.telnet_enabled()) {
#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
}
mqtt_.start(); // mqtt init
system_.start(); // starts commands, led, adc, button, network (sets hostname), syslog & uart
shower_.start(); // initialize shower timer and shower alert
temperaturesensor_.start(); // Temperature external sensors
analogsensor_.start(); // Analog external sensors
webLogService.start(); // apply settings to weblog service
// Load our library of known devices into stack mem. Names are stored in Flash memory
device_library_ = {
#include "device_library.h"
};
LOG_INFO("Loaded EMS device library (%d records)", device_library_.size());
#if defined(EMSESP_STANDALONE)
Mqtt::on_connect(); // simulate an MQTT connection
#endif
webServer.begin(); // start the web server
}
// main loop calling all services
void EMSESP::loop() {
esp8266React.loop(); // web services
system_.loop(); // does LED and checks system health, and syslog service
// if we're doing an OTA upload, skip MQTT and EMS
// if we're doing an OTA upload, skip everything except from console refresh
if (!system_.upload_status()) {
webLogService.loop(); // log in Web UI
rxservice_.loop(); // process any incoming Rx telegrams
shower_.loop(); // check for shower on/off
dallassensor_.loop(); // read dallas sensor temperatures
analogsensor_.loop(); // read analog sensor values
publish_all_loop(); // with HA messages in parts to avoid flooding the mqtt queue
mqtt_.loop(); // sends out anything in the MQTT queue
// service loops
webLogService.loop(); // log in Web UI
rxservice_.loop(); // process any incoming Rx telegrams
shower_.loop(); // check for shower on/off
temperaturesensor_.loop(); // read sensor temperatures
analogsensor_.loop(); // read analog sensor values
publish_all_loop(); // with HA messages in parts to avoid flooding the mqtt queue
mqtt_.loop(); // sends out anything in the MQTT queue
webSchedulerService.loop(); // handle any scheduled jobs
// force a query on the EMS devices to fetch latest data at a set interval (1 min)
scheduled_fetch_values();
}
console_.loop(); // telnet/serial console
uuid::loop();
// https://github.com/emsesp/EMS-ESP32/issues/78#issuecomment-877599145
// delay(1); // helps telnet catch up. don't think its needed in ESP32 >3.1.0?
#ifndef EMSESP_STANDALONE
if (system_.telnet_enabled()) {
telnet_.loop();
}
#else
if (!shell_->running()) {
::exit(0);
}
#endif
Shell::loop_all();
}
} // namespace emsesp

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -43,8 +43,10 @@
#include "web/WebDataService.h"
#include "web/WebSettingsService.h"
#include "web/WebCustomizationService.h"
#include "web/WebSchedulerService.h"
#include "web/WebAPIService.h"
#include "web/WebLogService.h"
#include "web/WebEntityService.h"
#include "emsdevicevalue.h"
#include "emsdevice.h"
@@ -52,9 +54,10 @@
#include "telegram.h"
#include "mqtt.h"
#include "system.h"
#include "dallassensor.h"
#include "temperaturesensor.h"
#include "analogsensor.h"
#include "console.h"
#include "console_stream.h"
#include "shower.h"
#include "roomcontrol.h"
#include "command.h"
@@ -62,25 +65,16 @@
#define WATCH_ID_NONE 0 // no watch id set
#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
#define EMSESP_JSON_SIZE_MEDIUM_DYN 1024 // for large json docs, using DynamicJsonDocument
#define EMSESP_JSON_SIZE_LARGE_DYN 2048 // for very large json docs, using DynamicJsonDocument
// uses StaticJsonDocument
#define EMSESP_JSON_SIZE_SMALL 256
#define EMSESP_JSON_SIZE_MEDIUM 768
#define EMSESP_JSON_SIZE_LARGE 1024 // used in forming HA config payloads, also in *AsyncJsonResponse
#ifndef EMSESP_STANDALONE
#define EMSESP_JSON_SIZE_XLARGE_DYN 4096 // for very very large json docs, using DynamicJsonDocument
#else
#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_XXXLARGE_DYN 20480 // web output (maybe for 4 hc)
// used in larger buffers like DynamicJsonDocument
#define EMSESP_JSON_SIZE_XLARGE 2048
#define EMSESP_JSON_SIZE_XXLARGE 4096
#define EMSESP_JSON_SIZE_XXXLARGE 16384
#define EMSESP_JSON_SIZE_XXXXLARGE 20480 // web output
// 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
@@ -94,13 +88,18 @@ using DeviceValueState = emsesp::DeviceValue::DeviceValueState;
using DeviceValueTAG = emsesp::DeviceValue::DeviceValueTAG;
using DeviceValueNumOp = emsesp::DeviceValue::DeviceValueNumOp;
class Shower; // forward declaration for compiler
// forward declarations for compiler
class EMSESPShell;
class Shower;
class EMSESP {
public:
static void start();
static void loop();
EMSESP();
~EMSESP() = default;
virtual void start();
virtual void loop();
static uuid::log::Logger logger();
static void publish_device_values(uint8_t device_type);
static void publish_other_values();
@@ -118,7 +117,7 @@ class EMSESP {
static bool process_telegram(std::shared_ptr<const Telegram> telegram);
static std::string pretty_telegram(std::shared_ptr<const Telegram> telegram);
static void send_read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset = 0, const uint8_t length = 0);
static void send_read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset = 0, const uint8_t length = 0, const bool front = false);
static void send_write_request(const uint16_t type_id,
const uint8_t dest,
const uint8_t offset,
@@ -149,8 +148,8 @@ class EMSESP {
static void incoming_telegram(uint8_t * data, const uint8_t length);
static bool dallas_enabled() {
return (dallassensor_.dallas_enabled());
static bool sensor_enabled() {
return (temperaturesensor_.sensor_enabled());
}
static bool analog_enabled() {
@@ -164,7 +163,7 @@ class EMSESP {
}
static void watch(uint8_t watch) {
watch_ = watch; // 0=off, 1=on, 2=raw
watch_ = watch; // 0=off, 1=on, 2=raw
if (watch == WATCH_OFF) {
watch_id_ = 0; // reset watch id if watch is disabled
}
@@ -177,6 +176,10 @@ class EMSESP {
read_id_ = id;
}
static void set_response_id(uint16_t id) {
response_id_ = id;
}
static bool wait_validate() {
return (wait_validate_ != 0);
}
@@ -215,14 +218,13 @@ class EMSESP {
static std::vector<std::unique_ptr<EMSdevice>> emsdevices;
// services
static Mqtt mqtt_;
static System system_;
static DallasSensor dallassensor_;
static AnalogSensor analogsensor_;
static Console console_;
static Shower shower_;
static RxService rxservice_;
static TxService txservice_;
static Mqtt mqtt_;
static System system_;
static TemperatureSensor temperaturesensor_;
static AnalogSensor analogsensor_;
static Shower shower_;
static RxService rxservice_;
static TxService txservice_;
// web controllers
static ESP8266React esp8266React;
@@ -232,14 +234,10 @@ class EMSESP {
static WebAPIService webAPIService;
static WebLogService webLogService;
static WebCustomizationService webCustomizationService;
static uuid::log::Logger logger();
static WebSchedulerService webSchedulerService;
static WebEntityService webEntityService;
private:
EMSESP() = delete;
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);
@@ -265,6 +263,7 @@ class EMSESP {
static uint16_t read_id_;
static bool read_next_;
static uint16_t publish_id_;
static uint16_t response_id_;
static bool tap_water_active_;
static uint8_t publish_all_idx_;
static uint8_t unique_id_count_;
@@ -272,6 +271,19 @@ class EMSESP {
static uint16_t wait_validate_;
static bool wait_km_;
static uint32_t last_fetch_;
// UUID stuff
static constexpr auto & serial_console_ = Serial;
static constexpr unsigned long SERIAL_CONSOLE_BAUD_RATE = 115200;
std::shared_ptr<EMSESPShell> shell_;
#ifndef EMSESP_STANDALONE
uuid::telnet::TelnetService telnet_;
#endif
protected:
// EMSESP();
static uuid::log::Logger logger_;
};
} // namespace emsesp

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -20,7 +20,7 @@
#include "system.h"
#include "mqtt.h"
#include "dallassensor.h"
#include "temperaturesensor.h"
#include "version.h"
#include "default_settings.h"
@@ -28,14 +28,14 @@
#include <uuid/log.h>
// forward declarators
// forward declarator
// used to bind EMS-ESP functions to external frameworks
namespace emsesp {
class EMSESP {
public:
static Mqtt mqtt_;
static System system_;
static DallasSensor dallassensor_;
static Mqtt mqtt_;
static System system_;
static TemperatureSensor temperaturesensor_;
static uuid::log::Logger logger();
static ESP8266React esp8266React;

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -277,7 +277,7 @@ char * Helpers::render_value(char * result, const double value, const int8_t for
// format: 0=no division, other divide by the value given and render with a decimal point
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};
char s[13] = {0};
// just print it if no conversion required (format = 0)
if (!format) {
strlcpy(result, itoa(new_value, s, 10), sizeof(s)); // format is 0
@@ -385,7 +385,7 @@ std::string Helpers::data_to_hex(const uint8_t * data, const uint8_t length) {
*p++ = buffer[1];
*p++ = ' '; // space
}
*--p = '\0'; // null terminate just in case, loosing the trailing space
*--p = '\0'; // null terminate just in case, loosing the trailing space
return std::string(str);
}
@@ -442,7 +442,6 @@ int Helpers::atoint(const char * value) {
// rounds a number to 2 decimal places
// example: round2(3.14159) -> 3.14
// 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
@@ -647,7 +646,7 @@ bool Helpers::value2enum(const char * value, uint8_t & value_ui, const char * co
std::string s_on = Helpers::translated_word(FL_(on));
std::string s_off = Helpers::translated_word(FL_(off));
// stops when a nullptr is found, which is the end delimeter of a MAKE_PSTR_LIST()
// stops when a nullptr is found, which is the end delimeter of a MAKE_TRANSLATION()
// could use count_items() to avoid buffer over-run but this works
for (value_ui = 0; strs[value_ui]; value_ui++) {
std::string enum_str = toLower((strs[value_ui]));
@@ -682,39 +681,49 @@ std::string Helpers::toUpper(std::string const & s) {
// capitalizes one UTF-8 character in char array
// works with Latin1 (1 byte), Polish amd some other (2 bytes) characters
// TODO add special characters that occur in other supported languages
#if defined(EMSESP_STANDALONE)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wtype-limits"
#endif
void Helpers::CharToUpperUTF8(char * c) {
auto p = (c + 1); // pointer to 2nd char of 2-byte unicode char
char p_v = *p; // value of 2nd char in 2-byte unicode char
switch (*c) {
case 0xC3:
case (char)0xC3:
// grave, acute, circumflex, diaeresis, etc.
if ((*(c + 1) >= 0xA0) && (*(c + 1) <= 0xBE)) {
*(c + 1) -= 0x20;
if ((p_v >= (char)0xA0) && (p_v <= (char)0xBE)) {
*p -= 0x20;
}
break;
case 0xC4:
switch (*(c + 1)) {
case 0x85: //ą (0xC4,0x85) -> Ą (0xC4,0x84)
case 0x87: //ć (0xC4,0x87) -> Ć (0xC4,0x86)
case 0x99: //ę (0xC4,0x99) -> Ę (0xC4,0x98)
*(c + 1) -= 1;
case (char)0xC4:
switch (p_v) {
case (char)0x85: //ą (0xC4,0x85) -> Ą (0xC4,0x84)
case (char)0x87: //ć (0xC4,0x87) -> Ć (0xC4,0x86)
case (char)0x99: //ę (0xC4,0x99) -> Ę (0xC4,0x98)
*p -= 1;
break;
}
break;
case 0xC5:
switch (*(c + 1)) {
case 0x82: //ł (0xC5,0x82) -> Ł (0xC5,0x81)
case 0x84: //ń (0xC5,0x84) -> Ń (0xC5,0x83)
case 0x9B: //ś (0xC5,0x9B) -> Ś (0xC5,0x9A)
case 0xBA: //ź (0xC5,0xBA) -> Ź (0xC5,0xB9)
case 0xBC: //ż (0xC5,0xBC) -> Ż (0xC5,0xBB)
*(c + 1) -= 1;
case (char)0xC5:
switch (p_v) {
case (char)0x82: //ł (0xC5,0x82) -> Ł (0xC5,0x81)
case (char)0x84: //ń (0xC5,0x84) -> Ń (0xC5,0x83)
case (char)0x9B: //ś (0xC5,0x9B) -> Ś (0xC5,0x9A)
case (char)0xBA: //ź (0xC5,0xBA) -> Ź (0xC5,0xB9)
case (char)0xBC: //ż (0xC5,0xBC) -> Ż (0xC5,0xBB)
*p -= 1;
break;
}
break;
default:
*c = toupper(*c); //works on Latin1 letters
*c = toupper(*c); // works on Latin1 letters
break;
}
}
#if defined(EMSESP_STANDALONE)
#pragma GCC diagnostic pop
#endif
// replace char in char string
void Helpers::replace_char(char * str, char find, char replace) {
@@ -774,4 +783,55 @@ const char * Helpers::translated_word(const char * const * strings, const bool f
return strings[index];
}
uint16_t Helpers::string2minutes(const std::string & str) {
uint8_t i = 0;
uint16_t res = 0;
uint16_t tmp = 0;
uint8_t state = 0;
while (str[i] != '\0') {
// If we got a digit
if (str[i] >= '0' && str[i] <= '9') {
tmp = tmp * 10 + str[i] - '0';
}
// Or if we got a colon
else if (str[i] == ':') {
// If we were reading the hours
if (state == 0) {
res = 60 * tmp;
}
// Or if we were reading the minutes
else if (state == 1) {
if (tmp > 60) {
return 0;
}
Serial.print("*");
Serial.print(tmp);
Serial.println("*");
res += tmp;
}
// Or we got an extra colon
else {
return 0;
}
state++;
tmp = 0;
}
// Or we got something wrong
else {
return 0;
}
i++;
}
if (state == 1 && tmp < 60) {
return res + tmp;
} else {
return 0; // Or if we were not, something is wrong in the given string
}
}
} // namespace emsesp

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -49,6 +49,7 @@ class Helpers {
static int atoint(const char * value);
static bool check_abs(const int32_t i);
static uint32_t abs(const int32_t i);
static uint16_t string2minutes(const std::string & str);
static float transformNumFloat(float value, const int8_t numeric_operator, const uint8_t fahrenheit = 0);

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -27,309 +27,330 @@
*/
// 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(su)
MAKE_PSTR_WORD(name)
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(publish)
MAKE_PSTR_WORD(timeout)
MAKE_PSTR_WORD(board_profile)
MAKE_PSTR_WORD(setvalue)
MAKE_WORD(exit)
MAKE_WORD(help)
MAKE_WORD(log)
MAKE_WORD(set)
MAKE_WORD(show)
MAKE_WORD(su)
MAKE_WORD(scan)
MAKE_WORD(password)
MAKE_WORD(read)
MAKE_WORD(values)
MAKE_WORD(system)
MAKE_WORD(fetch)
MAKE_WORD(restart)
MAKE_WORD(format)
MAKE_WORD(raw)
MAKE_WORD(watch)
MAKE_WORD(syslog)
MAKE_WORD(send)
MAKE_WORD(telegram)
MAKE_WORD(bus_id)
MAKE_WORD(tx_mode)
MAKE_WORD(ems)
MAKE_WORD(devices)
MAKE_WORD(shower)
MAKE_WORD(mqtt)
MAKE_WORD(emsesp)
MAKE_WORD(connected)
MAKE_WORD(disconnected)
MAKE_WORD(passwd)
MAKE_WORD(hostname)
MAKE_WORD(wifi)
MAKE_WORD(reconnect)
MAKE_WORD(ssid)
MAKE_WORD(heartbeat)
MAKE_WORD(users)
MAKE_WORD(publish)
MAKE_WORD(board_profile)
MAKE_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(entities)
MAKE_WORD(call)
MAKE_WORD(cmd)
MAKE_WORD(id)
MAKE_WORD(hc)
MAKE_WORD(wwc)
MAKE_WORD(device)
MAKE_WORD(data)
MAKE_WORD(command)
MAKE_WORD(commands)
MAKE_WORD(info)
MAKE_WORD(settings)
MAKE_WORD(value)
MAKE_WORD(entities)
// device types - lowercase, used in MQTT
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(dallassensor)
MAKE_PSTR_WORD(alert)
MAKE_PSTR_WORD(pump)
MAKE_PSTR_WORD(heatsource)
MAKE_WORD(boiler)
MAKE_WORD(thermostat)
MAKE_WORD(switch)
MAKE_WORD(solar)
MAKE_WORD(mixer)
MAKE_WORD(gateway)
MAKE_WORD(controller)
MAKE_WORD(connect)
MAKE_WORD(heatpump)
MAKE_WORD(generic)
MAKE_WORD(analogsensor)
MAKE_WORD(temperaturesensor)
MAKE_WORD(alert)
MAKE_WORD(pump)
MAKE_WORD(heatsource)
MAKE_WORD(scheduler)
MAKE_WORD(custom)
MAKE_WORD(ventilation)
MAKE_PSTR(number, "number")
MAKE_PSTR(enum, "enum")
MAKE_PSTR(text, "text")
// brands
MAKE_WORD_CUSTOM(bosch, "Bosch")
MAKE_WORD_CUSTOM(junkers, "Junkers")
MAKE_WORD_CUSTOM(buderus, "Buderus")
MAKE_WORD_CUSTOM(nefit, "Nefit")
MAKE_WORD_CUSTOM(sieger, "Sieger")
MAKE_WORD_CUSTOM(worcester, "Worcester")
MAKE_WORD_CUSTOM(ivt, "IVT")
MAKE_WORD_CUSTOM(no_brand, "")
// types
MAKE_WORD_CUSTOM(number, "number")
MAKE_WORD_CUSTOM(enum, "enum")
MAKE_WORD_CUSTOM(text, "text")
// Console
MAKE_PSTR(EMSESP, "EMS-ESP")
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_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>")
MAKE_WORD_CUSTOM(EMSESP, "EMS-ESP")
MAKE_WORD_CUSTOM(host_fmt, "Host: %s")
MAKE_WORD_CUSTOM(port_fmt, "Port: %d")
MAKE_WORD_CUSTOM(hostname_fmt, "Hostname: %s")
MAKE_WORD_CUSTOM(board_profile_fmt, "Board Profile: %s")
MAKE_WORD_CUSTOM(mark_interval_fmt, "Mark interval: %lus")
MAKE_WORD_CUSTOM(wifi_ssid_fmt, "WiFi SSID: %s")
MAKE_WORD_CUSTOM(wifi_password_fmt, "WiFi Password: %S")
MAKE_WORD_CUSTOM(tx_mode_fmt, "Tx mode: %d")
MAKE_WORD_CUSTOM(bus_id_fmt, "Bus ID: %02X")
MAKE_WORD_CUSTOM(log_level_fmt, "Log level: %s")
MAKE_WORD_CUSTOM(cmd_optional, "[cmd]")
MAKE_WORD_CUSTOM(ha_optional, "[ha]")
MAKE_WORD_CUSTOM(deep_optional, "[deep]")
MAKE_WORD_CUSTOM(watchid_optional, "[ID]")
MAKE_WORD_CUSTOM(watch_format_optional, "[off | on | raw | unknown]")
MAKE_WORD_CUSTOM(invalid_watch, "Invalid watch type")
MAKE_WORD_CUSTOM(data_mandatory, "\"XX XX ...\"")
MAKE_WORD_CUSTOM(asterisks, "********")
MAKE_WORD_CUSTOM(n_mandatory, "<n>")
MAKE_WORD_CUSTOM(sensorid_optional, "[sensor ID]")
MAKE_WORD_CUSTOM(id_optional, "[id|hc]")
MAKE_WORD_CUSTOM(data_optional, "[data]")
MAKE_WORD_CUSTOM(offset_optional, "[offset]")
MAKE_WORD_CUSTOM(length_optional, "[length]")
MAKE_WORD_CUSTOM(typeid_mandatory, "<type ID>")
MAKE_WORD_CUSTOM(deviceid_mandatory, "<device ID>")
MAKE_WORD_CUSTOM(device_type_optional, "[device]")
MAKE_WORD_CUSTOM(invalid_log_level, "Invalid log level")
MAKE_WORD_CUSTOM(log_level_optional, "[level]")
MAKE_WORD_CUSTOM(name_mandatory, "<name>")
MAKE_WORD_CUSTOM(name_optional, "[name]")
MAKE_WORD_CUSTOM(new_password_prompt1, "Enter new password: ")
MAKE_WORD_CUSTOM(new_password_prompt2, "Retype new password: ")
MAKE_WORD_CUSTOM(password_prompt, "Password: ")
MAKE_WORD_CUSTOM(unset, "<unset>")
// more common names that don't need translations
MAKE_PSTR_LIST(1x3min, "1x3min")
MAKE_PSTR_LIST(2x3min, "2x3min")
MAKE_PSTR_LIST(3x3min, "3x3min")
MAKE_PSTR_LIST(4x3min, "4x3min")
MAKE_PSTR_LIST(5x3min, "5x3min")
MAKE_PSTR_LIST(6x3min, "6x3min")
MAKE_PSTR_LIST(auto, "auto")
MAKE_PSTR_LIST(rc3x, "RC3x")
MAKE_PSTR_LIST(rc20, "RC20")
MAKE_PSTR_LIST(fb10, "FB10")
MAKE_PSTR_LIST(fb100, "FB100")
MAKE_PSTR_LIST(dash, "-")
MAKE_PSTR_LIST(BLANK, "")
MAKE_PSTR_LIST(pwm, "pwm")
MAKE_PSTR_LIST(pwm_invers, "pwm inverse")
MAKE_PSTR_LIST(mpc, "mpc")
MAKE_PSTR_LIST(tempauto, "temp auto")
MAKE_PSTR_LIST(bypass, "bypass")
MAKE_PSTR_LIST(mixer, "mixer")
MAKE_PSTR_LIST(monovalent, "monovalent")
MAKE_PSTR_LIST(bivalent, "bivalent")
MAKE_PSTR_LIST(n_o, "n_o")
MAKE_PSTR_LIST(n_c, "n_c")
MAKE_PSTR_LIST(prog1, "prog 1")
MAKE_PSTR_LIST(prog2, "prog 2")
MAKE_PSTR_LIST(proga, "prog a")
MAKE_PSTR_LIST(progb, "prog b")
MAKE_PSTR_LIST(progc, "prog c")
MAKE_PSTR_LIST(progd, "prog d")
MAKE_PSTR_LIST(proge, "prog e")
MAKE_PSTR_LIST(progf, "prog f")
MAKE_PSTR_LIST(rc35, "RC35")
MAKE_PSTR_LIST(0kW, "0 kW")
MAKE_PSTR_LIST(2kW, "2 kW")
MAKE_PSTR_LIST(3kW, "3 kW")
MAKE_PSTR_LIST(4kW, "4 kW")
MAKE_PSTR_LIST(6kW, "6 kW")
MAKE_PSTR_LIST(9kW, "9 kW")
MAKE_NOTRANSLATION(1x3min, "1x3min")
MAKE_NOTRANSLATION(2x3min, "2x3min")
MAKE_NOTRANSLATION(3x3min, "3x3min")
MAKE_NOTRANSLATION(4x3min, "4x3min")
MAKE_NOTRANSLATION(5x3min, "5x3min")
MAKE_NOTRANSLATION(6x3min, "6x3min")
MAKE_NOTRANSLATION(auto, "auto")
MAKE_NOTRANSLATION(rc3x, "RC3x")
MAKE_NOTRANSLATION(rc20, "RC20")
MAKE_NOTRANSLATION(fb10, "FB10")
MAKE_NOTRANSLATION(fb100, "FB100")
MAKE_NOTRANSLATION(dash, "-")
MAKE_NOTRANSLATION(BLANK, "")
MAKE_NOTRANSLATION(pwm, "pwm")
MAKE_NOTRANSLATION(pwm_invers, "pwm inverse")
MAKE_NOTRANSLATION(mpc, "mpc")
MAKE_NOTRANSLATION(tempauto, "temp auto")
MAKE_NOTRANSLATION(bypass, "bypass")
MAKE_NOTRANSLATION(mixer, "mixer")
MAKE_NOTRANSLATION(monovalent, "monovalent")
MAKE_NOTRANSLATION(bivalent, "bivalent")
MAKE_NOTRANSLATION(n_o, "n_o")
MAKE_NOTRANSLATION(n_c, "n_c")
MAKE_NOTRANSLATION(prog1, "prog 1")
MAKE_NOTRANSLATION(prog2, "prog 2")
MAKE_NOTRANSLATION(proga, "prog a")
MAKE_NOTRANSLATION(progb, "prog b")
MAKE_NOTRANSLATION(progc, "prog c")
MAKE_NOTRANSLATION(progd, "prog d")
MAKE_NOTRANSLATION(proge, "prog e")
MAKE_NOTRANSLATION(progf, "prog f")
MAKE_NOTRANSLATION(rc35, "RC35")
MAKE_NOTRANSLATION(0kW, "0 kW")
MAKE_NOTRANSLATION(2kW, "2 kW")
MAKE_NOTRANSLATION(3kW, "3 kW")
MAKE_NOTRANSLATION(4kW, "4 kW")
MAKE_NOTRANSLATION(6kW, "6 kW")
MAKE_NOTRANSLATION(9kW, "9 kW")
MAKE_NOTRANSLATION(L1, "L1")
MAKE_NOTRANSLATION(L2, "L2")
MAKE_NOTRANSLATION(L3, "L3")
MAKE_NOTRANSLATION(L4, "L4")
// templates - this are not translated and will be saved under options_single
MAKE_PSTR_LIST(tpl_datetime, "Format: < NTP | dd.mm.yyyy-hh:mm:ss-day(0-6)-dst(0/1) >")
MAKE_PSTR_LIST(tpl_switchtime, "Format: <nn> [ not_set | day hh:mm on|off ]")
MAKE_PSTR_LIST(tpl_switchtime1, "Format: <nn> [ not_set | day hh:mm Tn ]")
MAKE_PSTR_LIST(tpl_holidays, "Format: < dd.mm.yyyy-dd.mm.yyyy >")
MAKE_PSTR_LIST(tpl_date, "Format: < dd.mm.yyyy >")
MAKE_PSTR_LIST(tpl_input, "Format: <inv>[<evu1><evu2><evu3><comp><aux><cool><heat><dhw><pv>]")
MAKE_PSTR_LIST(tpl_input4, "Format: <inv>[<comp><aux><cool><heat><dhw><pv>]")
MAKE_NOTRANSLATION(tpl_datetime, "< NTP | dd.mm.yyyy-hh:mm:ss-day(0-6)-dst(0/1) >")
MAKE_NOTRANSLATION(tpl_switchtime, "<nn> [ not_set | day hh:mm on|off ]")
MAKE_NOTRANSLATION(tpl_switchtime1, "<nn> [ not_set | day hh:mm Tn ]")
MAKE_NOTRANSLATION(tpl_holidays, "< dd.mm.yyyy-dd.mm.yyyy >")
MAKE_NOTRANSLATION(tpl_date, "< dd.mm.yyyy >")
MAKE_NOTRANSLATION(tpl_input, "<inv>[<evu1><evu2><evu3><comp><aux><cool><heat><dhw><pv>]")
MAKE_NOTRANSLATION(tpl_input4, "<inv>[<comp><aux><cool><heat><dhw><pv>]")
#if defined(EMSESP_TEST)
MAKE_NOTRANSLATION(test_cmd, "run a test")
#endif
// 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_NOTRANSLATION(tag_none, "")
MAKE_NOTRANSLATION(tag_heartbeat, "")
MAKE_NOTRANSLATION(tag_device_data, "")
// Unit Of Measurement mapping - maps to DeviceValueUOM_s in emsdevice.cpp
// Translating hours/minute/seconds are done in emsdevice.cpp (uom_to_string())
MAKE_PSTR(uom_blank, " ")
MAKE_PSTR(uom_percent, "%")
MAKE_PSTR(uom_degrees, "°C")
MAKE_PSTR(uom_kwh, "kWh")
MAKE_PSTR(uom_wh, "Wh")
MAKE_PSTR(uom_bar, "bar")
MAKE_PSTR(uom_ua, "µA")
MAKE_PSTR(uom_lmin, "l/min")
MAKE_PSTR(uom_kw, "kW")
MAKE_PSTR(uom_w, "W")
MAKE_PSTR(uom_kb, "KB")
MAKE_PSTR(uom_dbm, "dBm")
MAKE_PSTR(uom_fahrenheit, "°F")
MAKE_PSTR(uom_mv, "mV")
MAKE_PSTR(uom_sqm, "")
MAKE_PSTR(uom_m3, "")
MAKE_PSTR(uom_l, "l")
MAKE_PSTR(uom_kmin, "K*min")
MAKE_PSTR(uom_k, "K")
MAKE_WORD_CUSTOM(uom_blank, " ")
MAKE_WORD_CUSTOM(uom_percent, "%")
MAKE_WORD_CUSTOM(uom_degrees, "°C")
MAKE_WORD_CUSTOM(uom_kwh, "kWh")
MAKE_WORD_CUSTOM(uom_wh, "Wh")
MAKE_WORD_CUSTOM(uom_bar, "bar")
MAKE_WORD_CUSTOM(uom_ua, "µA")
MAKE_WORD_CUSTOM(uom_lmin, "l/min")
MAKE_WORD_CUSTOM(uom_kw, "kW")
MAKE_WORD_CUSTOM(uom_w, "W")
MAKE_WORD_CUSTOM(uom_kb, "KB")
MAKE_WORD_CUSTOM(uom_dbm, "dBm")
MAKE_WORD_CUSTOM(uom_fahrenheit, "°F")
MAKE_WORD_CUSTOM(uom_mv, "mV")
MAKE_WORD_CUSTOM(uom_sqm, "")
MAKE_WORD_CUSTOM(uom_m3, "")
MAKE_WORD_CUSTOM(uom_l, "l")
MAKE_WORD_CUSTOM(uom_kmin, "K*min")
MAKE_WORD_CUSTOM(uom_k, "K")
// MQTT topics and prefixes
MAKE_PSTR(heating_active, "heating_active")
MAKE_PSTR(tapwater_active, "tapwater_active")
MAKE_PSTR(response, "response")
MAKE_PSTR(tag_boiler_data_ww_mqtt, "ww")
MAKE_PSTR(tag_device_data_ww_mqtt, "")
// Home Assistant - this is special and has no translations
MAKE_PSTR_LIST(climate, "HA climate config creation")
MAKE_WORD_CUSTOM(heating_active, "heating_active")
MAKE_WORD_CUSTOM(tapwater_active, "tapwater_active")
MAKE_WORD_CUSTOM(response, "response")
MAKE_WORD_CUSTOM(tag_boiler_data_ww_mqtt, "ww")
MAKE_WORD_CUSTOM(tag_device_data_ww_mqtt, "")
// syslog
MAKE_PSTR_LIST(list_syslog_level, "off", "emerg", "alert", "crit", "error", "warn", "notice", "info", "debug", "trace", "all")
MAKE_ENUM_FIXED(list_syslog_level, "off", "emerg", "alert", "crit", "error", "warn", "notice", "info", "debug", "trace", "all")
// sensors
MAKE_PSTR_LIST(counter, "counter")
MAKE_PSTR_LIST(digital_out, "digital_out")
MAKE_PSTR_LIST(list_sensortype, "none", "digital in", "counter", "adc", "timer", "rate", "digital out", "pwm 0", "pwm 1", "pwm 2")
MAKE_ENUM_FIXED(counter, "counter")
MAKE_ENUM_FIXED(digital_out, "digital_out")
MAKE_ENUM_FIXED(list_sensortype, "none", "digital in", "counter", "adc", "timer", "rate", "digital out", "pwm 0", "pwm 1", "pwm 2")
// watch
MAKE_PSTR_LIST(list_watch, "off", "on", "raw", "unknown")
MAKE_ENUM_FIXED(list_watch, "off", "on", "raw", "unknown")
/*
* The rest below are Enums and generated from translations lists
*/
MAKE_PSTR_ENUM(enum_cylprio, FL_(cyl1), FL_(cyl2))
MAKE_PSTR_ENUM(enum_progMode, FL_(prog1), FL_(prog2))
MAKE_PSTR_ENUM(enum_progMode4, FL_(proga), FL_(progb), FL_(progc), FL_(progd), FL_(proge), FL_(progf))
MAKE_PSTR_ENUM(enum_climate, FL_(seltemp), FL_(roomtemp))
MAKE_PSTR_ENUM(enum_charge, FL_(chargepump), FL_(3wayvalve))
MAKE_PSTR_ENUM(enum_freq, FL_(off), FL_(1x3min), FL_(2x3min), FL_(3x3min), FL_(4x3min), FL_(5x3min), FL_(6x3min), FL_(continuous))
MAKE_PSTR_ENUM(enum_off_time_date_manual, FL_(off), FL_(time), FL_(date), FL_(manual))
MAKE_PSTR_ENUM(enum_comfort, FL_(hot), FL_(eco), FL_(intelligent))
MAKE_PSTR_ENUM(enum_comfort1, FL_(high_comfort), FL_(eco))
MAKE_PSTR_ENUM(enum_comfort2, FL_(eco), FL_(high_comfort))
MAKE_PSTR_ENUM(enum_flow, FL_(off), FL_(flow), FL_(bufferedflow), FL_(buffer), FL_(layeredbuffer))
MAKE_PSTR_ENUM(enum_reset, FL_(dash), FL_(maintenance), FL_(error))
MAKE_PSTR_ENUM(enum_maxHeat, FL_(0kW), FL_(2kW), FL_(3kW), FL_(4kW), FL_(6kW), FL_(9kW))
MAKE_PSTR_ENUM(enum_pumpMode, FL_(proportional), FL_(deltaP1), FL_(deltaP2), FL_(deltaP3), FL_(deltaP4))
MAKE_ENUM(enum_cylprio, FL_(cyl1), FL_(cyl2))
MAKE_ENUM(enum_progMode, FL_(prog1), FL_(prog2))
MAKE_ENUM(enum_progMode4, FL_(proga), FL_(progb), FL_(progc), FL_(progd), FL_(proge), FL_(progf))
MAKE_ENUM(enum_climate, FL_(seltemp), FL_(roomtemp))
MAKE_ENUM(enum_charge, FL_(chargepump), FL_(3wayvalve))
MAKE_ENUM(enum_freq, FL_(off), FL_(1x3min), FL_(2x3min), FL_(3x3min), FL_(4x3min), FL_(5x3min), FL_(6x3min), FL_(continuous))
MAKE_ENUM(enum_off_time_date_manual, FL_(off), FL_(time), FL_(date), FL_(manual))
MAKE_ENUM(enum_comfort, FL_(hot), FL_(eco), FL_(intelligent))
MAKE_ENUM(enum_comfort1, FL_(high_comfort), FL_(eco))
MAKE_ENUM(enum_comfort2, FL_(eco), FL_(high_comfort))
MAKE_ENUM(enum_flow, FL_(off), FL_(flow), FL_(bufferedflow), FL_(buffer), FL_(layeredbuffer))
MAKE_ENUM(enum_reset, FL_(dash), FL_(maintenance), FL_(error))
MAKE_ENUM(enum_maxHeat, FL_(0kW), FL_(2kW), FL_(3kW), FL_(4kW), FL_(6kW), FL_(9kW))
MAKE_ENUM(enum_pumpMode, FL_(proportional), FL_(deltaP1), FL_(deltaP2), FL_(deltaP3), FL_(deltaP4))
// thermostat lists
MAKE_PSTR_ENUM(enum_ibaMainDisplay, FL_(internal_temperature), FL_(internal_setpoint), FL_(external_temperature), FL_(burner_temperature), FL_(ww_temperature), FL_(functioning_mode), FL_(time), FL_(date), FL_(smoke_temperature))
MAKE_PSTR_ENUM(enum_ibaLanguage, FL_(german), FL_(dutch), FL_(french), FL_(italian))
MAKE_PSTR_ENUM(enum_ibaLanguage_RC30, FL_(german), FL_(dutch))
MAKE_PSTR_ENUM(enum_floordrystatus, FL_(off), FL_(start), FL_(heat), FL_(hold), FL_(cool), FL_(end))
MAKE_PSTR_ENUM(enum_ibaBuildingType, FL_(light), FL_(medium), FL_(heavy))
MAKE_PSTR_ENUM(enum_PID, FL_(fast), FL_(medium), FL_(slow))
MAKE_PSTR_ENUM(enum_wwMode, FL_(off), FL_(normal), FL_(comfort), FL_(auto), FL_(own_prog), FL_(eco))
MAKE_PSTR_ENUM(enum_wwCircMode, FL_(off), FL_(on), FL_(auto), FL_(own_prog))
MAKE_PSTR_ENUM(enum_wwMode2, FL_(off), FL_(on), FL_(auto))
MAKE_PSTR_ENUM(enum_wwMode3, FL_(on), FL_(off), FL_(auto))
MAKE_PSTR_ENUM(enum_heatingtype, FL_(off), FL_(radiator), FL_(convector), FL_(floor))
MAKE_PSTR_ENUM(enum_summermode, FL_(summer), FL_(auto), FL_(winter))
MAKE_PSTR_ENUM(enum_hpoperatingmode, FL_(off), FL_(auto), FL_(heating), FL_(cooling))
MAKE_PSTR_ENUM(enum_summer, FL_(winter), FL_(summer))
MAKE_PSTR_ENUM(enum_operatingstate, FL_(heating), FL_(off), FL_(cooling))
MAKE_ENUM(enum_ibaMainDisplay, FL_(internal_temperature), FL_(internal_setpoint), FL_(external_temperature), FL_(burner_temperature), FL_(ww_temperature), FL_(functioning_mode), FL_(time), FL_(date), FL_(smoke_temperature))
MAKE_ENUM(enum_ibaLanguage, FL_(german), FL_(dutch), FL_(french), FL_(italian))
MAKE_ENUM(enum_ibaLanguage_RC30, FL_(german), FL_(dutch))
MAKE_ENUM(enum_floordrystatus, FL_(off), FL_(start), FL_(heat), FL_(hold), FL_(cool), FL_(end))
MAKE_ENUM(enum_ibaBuildingType, FL_(light), FL_(medium), FL_(heavy))
MAKE_ENUM(enum_PID, FL_(fast), FL_(medium), FL_(slow))
MAKE_ENUM(enum_wwMode, FL_(off), FL_(normal), FL_(comfort), FL_(auto), FL_(own_prog), FL_(eco))
MAKE_ENUM(enum_wwCircMode, FL_(off), FL_(on), FL_(auto), FL_(own_prog))
MAKE_ENUM(enum_wwMode2, FL_(off), FL_(on), FL_(auto))
MAKE_ENUM(enum_wwMode3, FL_(on), FL_(off), FL_(auto))
MAKE_ENUM(enum_heatingtype, FL_(off), FL_(radiator), FL_(convector), FL_(floor))
MAKE_ENUM(enum_summermode, FL_(summer), FL_(auto), FL_(winter))
MAKE_ENUM(enum_hpoperatingmode, FL_(off), FL_(auto), FL_(heating), FL_(cooling))
MAKE_ENUM(enum_summer, FL_(winter), FL_(summer))
MAKE_ENUM(enum_operatingstate, FL_(heating), FL_(off), FL_(cooling))
MAKE_ENUM(enum_hpmode, FL_(heating), FL_(cooling), FL_(heatandcool))
MAKE_PSTR_ENUM(enum_mode, FL_(manual), FL_(auto)) // RC100, RC300, RC310
MAKE_PSTR_ENUM(enum_mode2, FL_(off), FL_(manual), FL_(auto)) // RC20, RC30
MAKE_PSTR_ENUM(enum_mode3, FL_(night), FL_(day), FL_(auto)) // RC35, RC30_N, RC25, RC20_N
MAKE_PSTR_ENUM(enum_mode4, FL_(nofrost), FL_(eco), FL_(heat), FL_(auto)) // JUNKERS
MAKE_PSTR_ENUM(enum_mode5, FL_(auto), FL_(off)) // CRF
MAKE_PSTR_ENUM(enum_mode6, FL_(nofrost), FL_(night), FL_(day)) // RC10
MAKE_PSTR_ENUM(enum_mode_ha, FL_(off), FL_(heat), FL_(auto)) // HA climate
MAKE_ENUM(enum_mode, FL_(manual), FL_(auto)) // RC100, RC300, RC310
MAKE_ENUM(enum_mode2, FL_(off), FL_(manual), FL_(auto)) // RC20, RC30
MAKE_ENUM(enum_mode3, FL_(night), FL_(day), FL_(auto)) // RC35, RC30_N, RC25, RC20_N
MAKE_ENUM(enum_mode4, FL_(nofrost), FL_(eco), FL_(heat), FL_(auto)) // JUNKERS
MAKE_ENUM(enum_mode5, FL_(auto), FL_(off)) // CRF
MAKE_ENUM(enum_mode6, FL_(nofrost), FL_(night), FL_(day)) // RC10
MAKE_ENUM(enum_mode_ha, FL_(off), FL_(heat), FL_(auto)) // HA climate
MAKE_PSTR_ENUM(enum_modetype, FL_(eco), FL_(comfort))
MAKE_PSTR_ENUM(enum_modetype3, FL_(night), FL_(day))
MAKE_PSTR_ENUM(enum_modetype4, FL_(nofrost), FL_(eco), FL_(heat))
MAKE_PSTR_ENUM(enum_modetype5, FL_(off), FL_(on))
MAKE_ENUM(enum_modetype, FL_(eco), FL_(comfort))
MAKE_ENUM(enum_modetype3, FL_(night), FL_(day))
MAKE_ENUM(enum_modetype4, FL_(nofrost), FL_(eco), FL_(heat))
MAKE_ENUM(enum_modetype5, FL_(off), FL_(on))
MAKE_PSTR_ENUM(enum_reducemode, FL_(nofrost), FL_(reduce), FL_(room), FL_(outdoor))
MAKE_PSTR_ENUM(enum_reducemode1, FL_(outdoor), FL_(room), FL_(reduce)) // RC310 values: 1-3
MAKE_PSTR_ENUM(enum_nofrostmode, FL_(off), FL_(room), FL_(outdoor))
MAKE_PSTR_ENUM(enum_nofrostmode1, FL_(room), FL_(outdoor), FL_(room_outdoor))
MAKE_ENUM(enum_reducemode, FL_(nofrost), FL_(reduce), FL_(room), FL_(outdoor))
MAKE_ENUM(enum_reducemode1, FL_(outdoor), FL_(room), FL_(reduce)) // RC310 values: 1-3
MAKE_ENUM(enum_nofrostmode, FL_(off), FL_(outdoor), FL_(room))
MAKE_ENUM(enum_nofrostmode1, FL_(room), FL_(outdoor), FL_(room_outdoor))
MAKE_PSTR_ENUM(enum_controlmode, FL_(off), FL_(optimized), FL_(simple), FL_(mpc), FL_(room), FL_(power), FL_(constant))
MAKE_PSTR_ENUM(enum_controlmode1, FL_(weather_compensated), FL_(outside_basepoint), FL_(na), FL_(room), FL_(power), FL_(constant)) // RC310 1-4
MAKE_PSTR_ENUM(enum_controlmode2, FL_(outdoor), FL_(room))
MAKE_PSTR_ENUM(enum_control, FL_(off), FL_(rc20), FL_(rc3x))
MAKE_PSTR_ENUM(enum_j_control, FL_(off), FL_(fb10), FL_(fb100))
MAKE_PSTR_ENUM(enum_roomsensor, FL_(extern), FL_(intern), FL_(auto))
MAKE_ENUM(enum_controlmode, FL_(off), FL_(optimized), FL_(simple), FL_(mpc), FL_(room), FL_(power), FL_(constant))
MAKE_ENUM(enum_controlmode1, FL_(weather_compensated), FL_(outside_basepoint), FL_(na), FL_(room), FL_(power), FL_(constant)) // RC310 1-4
MAKE_ENUM(enum_controlmode2, FL_(outdoor), FL_(room))
MAKE_ENUM(enum_control, FL_(off), FL_(rc20), FL_(rc3x))
MAKE_ENUM(enum_j_control, FL_(off), FL_(fb10), FL_(fb100))
MAKE_ENUM(enum_roomsensor, FL_(extern), FL_(intern), FL_(auto))
MAKE_PSTR_ENUM(enum_switchmode, FL_(off), FL_(eco), FL_(comfort), FL_(heat))
MAKE_ENUM(enum_switchmode, FL_(off), FL_(eco), FL_(comfort), FL_(heat))
MAKE_PSTR_ENUM(enum_dayOfWeek, FL_(day_mo), FL_(day_tu), FL_(day_we), FL_(day_th), FL_(day_fr), FL_(day_sa), FL_(day_su), FL_(all))
MAKE_PSTR_ENUM(enum_progMode2, FL_(own_1), FL_(family), FL_(morning), FL_(evening), FL_(am), FL_(pm), FL_(midday), FL_(singles), FL_(seniors), FL_(new), FL_(own_2))
MAKE_PSTR_ENUM(enum_progMode3, FL_(family), FL_(morning), FL_(evening), FL_(am), FL_(pm), FL_(midday), FL_(singles), FL_(seniors))
MAKE_PSTR_ENUM(enum_hybridStrategy, FL_(co2_optimized), FL_(cost_optimized), FL_(outside_temp_switched), FL_(co2_cost_mix))
MAKE_PSTR_ENUM(enum_hybridStrategy1, FL_(cost_optimized), FL_(co2_optimized), FL_(outside_temp_alt), FL_(outside_temp_par), FL_(hp_prefered), FL_(boiler_only))
MAKE_PSTR_ENUM(enum_lowNoiseMode, FL_(off), FL_(reduced_output), FL_(switchoff), FL_(perm))
MAKE_ENUM(enum_dayOfWeek, FL_(day_mo), FL_(day_tu), FL_(day_we), FL_(day_th), FL_(day_fr), FL_(day_sa), FL_(day_su), FL_(all))
MAKE_ENUM(enum_progMode2, FL_(own_1), FL_(family), FL_(morning), FL_(evening), FL_(am), FL_(pm), FL_(midday), FL_(singles), FL_(seniors), FL_(new), FL_(own_2))
MAKE_ENUM(enum_progMode3, FL_(family), FL_(morning), FL_(evening), FL_(am), FL_(pm), FL_(midday), FL_(singles), FL_(seniors))
MAKE_ENUM(enum_hybridStrategy, FL_(co2_optimized), FL_(cost_optimized), FL_(outside_temp_switched), FL_(co2_cost_mix))
MAKE_ENUM(enum_hybridStrategy1, FL_(cost_optimized), FL_(co2_optimized), FL_(outside_temp_alt), FL_(outside_temp_par), FL_(hp_prefered), FL_(boiler_only))
MAKE_ENUM(enum_lowNoiseMode, FL_(off), FL_(reduced_output), FL_(switchoff), FL_(perm))
// heat pump
MAKE_PSTR_ENUM(enum_hpactivity, FL_(none), FL_(heating), FL_(cooling), FL_(hot_water), FL_(pool), FL_(unknown), FL_(defrost))
MAKE_PSTR_ENUM(enum_silentMode, FL_(off), FL_(auto), FL_(on))
MAKE_ENUM(enum_hpactivity, FL_(none), FL_(heating), FL_(cooling), FL_(hot_water), FL_(pool), FL_(unknown), FL_(defrost))
MAKE_ENUM(enum_silentMode, FL_(off), FL_(auto), FL_(on))
// solar
MAKE_PSTR_ENUM(enum_solarmode, FL_(constant), FL_(pwm), FL_(analog))
MAKE_PSTR_ENUM(enum_collectortype, FL_(flat), FL_(vacuum))
MAKE_PSTR_ENUM(enum_wwStatus2, FL_(BLANK), FL_(BLANK), FL_(BLANK), FL_(no_heat), FL_(BLANK), FL_(BLANK), FL_(heatrequest), FL_(BLANK), FL_(disinfecting), FL_(hold))
MAKE_ENUM(enum_solarmode, FL_(constant), FL_(pwm), FL_(analog))
MAKE_ENUM(enum_collectortype, FL_(flat), FL_(vacuum))
MAKE_ENUM(enum_wwStatus2, FL_(BLANK), FL_(BLANK), FL_(BLANK), FL_(no_heat), FL_(BLANK), FL_(BLANK), FL_(heatrequest), FL_(BLANK), FL_(disinfecting), FL_(hold))
// mixer
MAKE_PSTR_ENUM(enum_shunt, FL_(stopped), FL_(opening), FL_(closing), FL_(open), FL_(close))
MAKE_PSTR_ENUM(enum_wwProgMode, FL_(std_prog), FL_(own_prog))
MAKE_ENUM(enum_shunt, FL_(stopped), FL_(opening), FL_(closing), FL_(open), FL_(close))
MAKE_ENUM(enum_wwProgMode, FL_(std_prog), FL_(own_prog))
// AM200 lists
MAKE_PSTR_ENUM(enum_vr2Config, FL_(off), FL_(bypass))
MAKE_PSTR_ENUM(enum_aPumpSignal, FL_(off), FL_(pwm), FL_(pwm_invers))
MAKE_PSTR_ENUM(enum_bufBypass, FL_(no), FL_(mixer), FL_(valve))
MAKE_PSTR_ENUM(enum_blockMode, FL_(off), FL_(auto), FL_(blocking))
MAKE_PSTR_ENUM(enum_bufConfig, FL_(off), FL_(monovalent), FL_(bivalent))
MAKE_PSTR_ENUM(enum_blockTerm, FL_(n_o), FL_(n_c))
MAKE_ENUM(enum_vr2Config, FL_(off), FL_(bypass))
MAKE_ENUM(enum_aPumpSignal, FL_(off), FL_(pwm), FL_(pwm_invers))
MAKE_ENUM(enum_bufBypass, FL_(no), FL_(mixer), FL_(valve))
MAKE_ENUM(enum_blockMode, FL_(off), FL_(auto), FL_(blocking))
MAKE_ENUM(enum_bufConfig, FL_(off), FL_(monovalent), FL_(bivalent))
MAKE_ENUM(enum_blockTerm, FL_(n_o), FL_(n_c))
// Ventilation
MAKE_ENUM(enum_ventMode, FL_(auto), FL_(off), FL_(L1), FL_(L2), FL_(L3), FL_(L4), FL_(demand), FL_(sleep), FL_(intense), FL_(bypass), FL_(partymode), FL_(fireplace))
#pragma GCC diagnostic pop

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -18,10 +18,12 @@
#include "emsesp.h"
static emsesp::EMSESP application;
void setup() {
emsesp::EMSESP::start();
application.start();
}
void loop() {
emsesp::EMSESP::loop();
application.loop();
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -19,7 +19,7 @@
#ifndef EMSESP_MQTT_H_
#define EMSESP_MQTT_H_
#include <AsyncMqttClient.h>
#include <espMqttClient.h>
#include "helpers.h"
#include "system.h"
@@ -29,31 +29,15 @@
using uuid::console::Shell;
// size of queue
static constexpr uint16_t MAX_MQTT_MESSAGES = 300;
namespace emsesp {
using mqtt_sub_function_p = std::function<bool(const char * message)>;
using cmdfunction_p = std::function<bool(const char * data, const int8_t id)>;
struct MqttMessage {
const uint8_t operation;
const std::string topic;
const std::string payload;
const bool retain;
MqttMessage(const uint8_t operation, const std::string & topic, const std::string & payload, bool retain)
: operation(operation)
, topic(topic)
, payload(payload)
, retain(retain) {
}
~MqttMessage() = default;
};
class Mqtt {
public:
enum discoveryType : uint8_t { HOMEASSISTANT, DOMOTICZ };
enum entityFormat : uint8_t { SINGLE_LONG, SINGLE_SHORT, MULTI_SHORT };
void loop();
void start();
@@ -74,25 +58,24 @@ class Mqtt {
static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 128; // fixed, not a user setting anymore
static void on_connect();
static void on_disconnect(espMqttClientTypes::DisconnectReason reason);
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);
static void publish(const char * topic, const char * payload);
static void publish(const std::string & topic, const JsonObject & payload);
static void publish(const char * topic, const JsonObject & payload);
static void publish(const char * topic, const std::string & payload);
static void publish_retain(const std::string & topic, const JsonObject & payload, bool retain);
static void publish_retain(const char * topic, const std::string & payload, bool retain);
static void publish_retain(const char * topic, const JsonObject & payload, bool retain);
static void publish_ha(const char * topic, const JsonObject & payload);
static void publish_ha(const char * topic);
static bool queue_publish(const std::string & topic, const std::string & payload);
static bool queue_publish(const char * topic, const char * payload);
static bool queue_publish(const std::string & topic, const JsonObject & payload);
static bool queue_publish(const char * topic, const JsonObject & payload);
static bool queue_publish(const char * topic, const std::string & payload);
static bool queue_publish_retain(const std::string & topic, const JsonObject & payload, const bool retain);
static bool queue_publish_retain(const char * topic, const std::string & payload, const bool retain);
static bool queue_publish_retain(const char * topic, const JsonObject & payload, const bool retain);
static bool queue_ha(const char * topic, const JsonObject & payload);
static bool queue_remove_topic(const char * topic);
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,
static bool publish_ha_sensor_config(DeviceValue & dv, const char * model, const char * brand, const bool remove, const bool create_device_config = false);
static bool publish_ha_sensor_config(uint8_t type,
uint8_t tag,
const char * const fullname,
const char * const en_name,
@@ -108,27 +91,23 @@ class Mqtt {
const int8_t num_op,
const JsonObject & dev_json);
static void publish_system_ha_sensor_config(uint8_t type, const char * name, const char * entity, const uint8_t uom);
static void publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp, const bool remove = false, const int16_t min = 5, const uint16_t max = 30);
static bool publish_system_ha_sensor_config(uint8_t type, const char * name, const char * entity, const uint8_t uom);
static bool publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp, const bool remove = false, const int16_t min = 5, const uint16_t max = 30);
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();
#if defined(EMSESP_DEBUG)
#if defined(EMSESP_TEST)
void incoming(const char * topic, const char * payload = ""); // for testing only
#endif
static bool connected() {
#if defined(EMSESP_STANDALONE)
return true;
#else
return mqttClient_->connected();
#endif
}
static AsyncMqttClient * client() {
static MqttClient * client() {
return mqttClient_;
}
@@ -169,7 +148,7 @@ class Mqtt {
}
static uint32_t publish_queued() {
return mqtt_messages_.size();
return queuecount_;
}
static uint8_t connect_count() {
@@ -186,6 +165,10 @@ class Mqtt {
return entity_format_;
}
static uint8_t discovery_type() {
return discovery_type_;
}
static void nested_format(uint8_t nested_format) {
nested_format_ = nested_format;
}
@@ -218,12 +201,8 @@ class Mqtt {
ha_climate_reset_ = reset;
}
static bool send_response() {
return send_response_;
}
static void send_response(bool send_response) {
send_response_ = send_response;
static std::string get_response() {
return lastresponse_;
}
void set_qos(uint8_t mqtt_qos) const {
@@ -234,47 +213,24 @@ class Mqtt {
mqtt_retain_ = mqtt_retain;
}
static bool is_empty() {
return mqtt_messages_.empty();
}
static std::string tag_to_topic(uint8_t device_type, uint8_t tag);
static void
add_avty_to_doc(const char * state_t, const JsonObject & doc, const char * cond1 = nullptr, const char * cond2 = nullptr, const char * negcond = nullptr);
struct QueuedMqttMessage {
const uint32_t id_;
const std::shared_ptr<const MqttMessage> content_;
uint8_t retry_count_ = 0;
uint16_t packet_id_ = 0;
~QueuedMqttMessage() = default;
QueuedMqttMessage(uint32_t id, std::shared_ptr<MqttMessage> && content)
: id_(id)
, content_(std::move(content)) {
}
};
static std::deque<QueuedMqttMessage> mqtt_messages_;
private:
static uuid::log::Logger logger_;
static AsyncMqttClient * mqttClient_;
static uint32_t mqtt_message_id_;
static MqttClient * mqttClient_;
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
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);
static bool queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, const bool retain);
static bool queue_publish_message(const std::string & topic, const std::string & payload, const bool retain);
static void queue_subscribe_message(const std::string & topic);
static void queue_unsubscribe_message(const std::string & topic);
void on_publish(uint16_t packetId) const;
void on_message(const char * topic, const char * payload, size_t len) const;
void process_queue();
void on_message(const char * topic, const uint8_t * payload, size_t len) const;
// function handlers for MQTT subscriptions
struct MQTTSubFunction {
@@ -291,7 +247,7 @@ class Mqtt {
static std::vector<MQTTSubFunction> mqtt_subfunctions_; // list of mqtt subscribe callbacks for all devices
uint32_t last_mqtt_poll_ = 0;
// uint32_t last_mqtt_poll_ = 0;
uint32_t last_publish_boiler_ = 0;
uint32_t last_publish_thermostat_ = 0;
uint32_t last_publish_solar_ = 0;
@@ -299,16 +255,18 @@ class Mqtt {
uint32_t last_publish_other_ = 0;
uint32_t last_publish_sensor_ = 0;
uint32_t last_publish_heartbeat_ = 0;
uint32_t last_publish_queue_ = 0;
// uint32_t last_publish_queue_ = 0;
static bool connecting_;
static bool initialized_;
static uint32_t mqtt_publish_fails_;
static uint16_t queuecount_;
static uint8_t connectcount_;
static bool ha_climate_reset_;
static std::string lasttopic_;
static std::string lastpayload_;
static std::string lastresponse_;
// settings, copied over
static std::string mqtt_base_;
@@ -328,6 +286,7 @@ class Mqtt {
static uint8_t nested_format_;
static uint8_t entity_format_;
static std::string discovery_prefix_;
static uint8_t discovery_type_;
static bool publish_single_;
static bool publish_single2cmd_;
static bool send_response_;

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -15,7 +15,6 @@
* 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_ROOMCONTROL_H
#define EMSESP_ROOMCONTROL_H

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -82,7 +82,7 @@ void Shower::loop() {
char s[50];
snprintf(s, 50, "%d minutes and %d seconds", (uint8_t)(duration_ / 60000), (uint8_t)((duration_ / 1000) % 60));
doc["duration"] = s;
Mqtt::publish("shower_data", doc.as<JsonObject>());
Mqtt::queue_publish("shower_data", doc.as<JsonObject>());
LOG_DEBUG("[Shower] finished with duration %d", duration_);
}
}
@@ -144,18 +144,16 @@ void Shower::set_shower_state(bool state, bool force) {
// always publish as a string
char s[12];
Mqtt::publish("shower_active", Helpers::render_boolean(s, shower_state_)); // https://github.com/emsesp/EMS-ESP/issues/369
Mqtt::queue_publish("shower_active", Helpers::render_boolean(s, shower_state_)); // https://github.com/emsesp/EMS-ESP/issues/369
// send out HA MQTT Discovery config topic
if ((Mqtt::ha_enabled()) && (!ha_configdone_ || force)) {
ha_configdone_ = true;
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
StaticJsonDocument<EMSESP_JSON_SIZE_LARGE> doc;
doc["name"] = "Shower Active";
char str[70];
if (Mqtt::entity_format() == 2) {
if (Mqtt::entity_format() == Mqtt::entityFormat::MULTI_SHORT) {
snprintf(str, sizeof(str), "%s_shower_active", Mqtt::basename().c_str());
} else {
snprintf(str, sizeof(str), "shower_active"); // v3.4 compatible
@@ -164,7 +162,7 @@ void Shower::set_shower_state(bool state, bool force) {
doc["object_id"] = str;
char stat_t[50];
snprintf(stat_t, sizeof(stat_t), "%s/shower_active", Mqtt::base().c_str()); // use base path
snprintf(stat_t, sizeof(stat_t), "%s/shower_active", Mqtt::basename().c_str());
doc["stat_t"] = stat_t;
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
@@ -181,14 +179,15 @@ void Shower::set_shower_state(bool state, bool force) {
JsonObject dev = doc.createNestedObject("dev");
JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp");
ids.add(Mqtt::basename());
// add "availability" section
Mqtt::add_avty_to_doc(stat_t, doc.as<JsonObject>());
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "binary_sensor/%s/shower_active/config", Mqtt::basename().c_str());
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
ha_configdone_ = Mqtt::queue_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
}
}

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -46,9 +46,9 @@ class Shower {
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
uint32_t timer_pause_; // ms
uint32_t duration_; // ms
uint32_t timer_start_; // ms
uint32_t timer_pause_; // ms
uint32_t duration_; // ms
// cold shot
uint32_t alert_timer_start_; // ms

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -19,9 +19,13 @@
#include "system.h"
#include "emsesp.h" // for send_raw_telegram() command
#ifndef EMSESP_STANDALONE
#include "esp_ota_ops.h"
#endif
#include <semver200.h>
#if defined(EMSESP_DEBUG)
#if defined(EMSESP_TEST)
#include "test/test.h"
#endif
@@ -50,24 +54,38 @@ Adafruit_NeoPixel pixels(1, 7, NEO_GRB + NEO_KHZ800);
namespace emsesp {
// Languages supported. Note: the order is important and must match locale_translations.h
const char * const languages[] = {EMSESP_LOCALE_EN, EMSESP_LOCALE_DE, EMSESP_LOCALE_NL, EMSESP_LOCALE_SV, EMSESP_LOCALE_PL, EMSESP_LOCALE_NO, EMSESP_LOCALE_FR};
#if defined(EMSESP_TEST) || defined(EMSESP_EN_ONLY)
// in Debug mode use one language (en) to save flash memory needed for the tests
const char * const languages[] = {EMSESP_LOCALE_EN};
#elif defined(EMSESP_DE_ONLY)
const char * const languages[] = {EMSESP_LOCALE_DE};
#else
const char * const languages[] = {EMSESP_LOCALE_EN,
EMSESP_LOCALE_DE,
EMSESP_LOCALE_NL,
EMSESP_LOCALE_SV,
EMSESP_LOCALE_PL,
EMSESP_LOCALE_NO,
EMSESP_LOCALE_FR,
EMSESP_LOCALE_TR,
EMSESP_LOCALE_IT};
#endif
size_t num_languages = sizeof(languages) / sizeof(const char *);
static constexpr uint8_t NUM_LANGUAGES = sizeof(languages) / sizeof(const char *);
uuid::log::Logger System::logger_{F_(system), uuid::log::Facility::KERN};
#ifndef EMSESP_STANDALONE
uuid::syslog::SyslogService System::syslog_;
#endif
// init statics
PButton System::myPButton_;
bool System::restart_requested_ = false;
PButton System::myPButton_;
bool System::restart_requested_ = false;
bool System::test_set_all_active_ = false;
uint32_t System::max_alloc_mem_;
uint32_t System::heap_mem_;
// find the index of the language
// 0 = EN, 1 = DE, etc...
uint8_t System::language_index() {
for (uint8_t i = 0; i < num_languages; i++) {
for (uint8_t i = 0; i < NUM_LANGUAGES; i++) {
if (languages[i] == locale()) {
return i;
}
@@ -80,6 +98,18 @@ bool System::command_send(const char * value, const int8_t id) {
return EMSESP::txservice_.send_raw(value); // ignore id
}
bool System::command_response(const char * value, const int8_t id, JsonObject & output) {
DynamicJsonDocument doc(EMSESP_JSON_SIZE_LARGE);
if (DeserializationError::Ok == deserializeJson(doc, Mqtt::get_response())) {
for (JsonPair p : doc.as<JsonObject>()) {
output[p.key()] = p.value();
}
} else {
output["response"] = Mqtt::get_response();
}
return true;
}
// fetch device values
bool System::command_fetch(const char * value, const int8_t id) {
std::string value_s;
@@ -130,19 +160,21 @@ bool System::command_publish(const char * value, const int8_t id) {
} else if (value_s == "other") {
EMSESP::publish_other_values(); // switch and heat pump
return true;
} else if ((value_s == (F_(dallassensor))) || (value_s == (F_(analogsensor)))) {
} else if ((value_s == (F_(temperaturesensor))) || (value_s == (F_(analogsensor)))) {
EMSESP::publish_sensor_values(true);
return true;
}
}
EMSESP::publish_all();
LOG_INFO("Publishing all data to MQTT");
EMSESP::publish_all();
return true;
}
// syslog level
// commenting this out - don't see the point on having an API service to change the syslog level
/*
bool System::command_syslog_level(const char * value, const int8_t id) {
uint8_t s = 0xff;
if (Helpers::value2enum(value, s, FL_(list_syslog_level))) {
@@ -163,6 +195,7 @@ bool System::command_syslog_level(const char * value, const int8_t id) {
}
return false;
}
*/
// watch
bool System::command_watch(const char * value, const int8_t id) {
@@ -174,9 +207,9 @@ bool System::command_watch(const char * value, const int8_t id) {
}
if (Mqtt::publish_single() && w != EMSESP::watch()) {
if (Mqtt::publish_single2cmd()) {
Mqtt::publish("system/watch", EMSESP::system_.enum_format() == ENUM_FORMAT_INDEX ? Helpers::itoa(w) : (FL_(list_watch)[w]));
Mqtt::queue_publish("system/watch", EMSESP::system_.enum_format() == ENUM_FORMAT_INDEX ? Helpers::itoa(w) : (FL_(list_watch)[w]));
} else {
Mqtt::publish("system_data/watch", EMSESP::system_.enum_format() == ENUM_FORMAT_INDEX ? Helpers::itoa(w) : (FL_(list_watch)[w]));
Mqtt::queue_publish("system_data/watch", EMSESP::system_.enum_format() == ENUM_FORMAT_INDEX ? Helpers::itoa(w) : (FL_(list_watch)[w]));
}
}
EMSESP::watch(w);
@@ -184,9 +217,9 @@ bool System::command_watch(const char * value, const int8_t id) {
} else if (i) {
if (Mqtt::publish_single() && i != EMSESP::watch_id()) {
if (Mqtt::publish_single2cmd()) {
Mqtt::publish("system/watch", Helpers::hextoa(i));
Mqtt::queue_publish("system/watch", Helpers::hextoa(i));
} else {
Mqtt::publish("system_data/watch", Helpers::hextoa(i));
Mqtt::queue_publish("system_data/watch", Helpers::hextoa(i));
}
}
EMSESP::watch_id(i);
@@ -212,7 +245,6 @@ void System::system_restart() {
void System::wifi_reconnect() {
LOG_INFO("WiFi reconnecting...");
Shell::loop_all();
EMSESP::console_.loop();
delay(1000); // wait a second
EMSESP::webSettingsService.save(); // local settings
EMSESP::esp8266React.getNetworkSettingsService()->callUpdateHandlers("local"); // in case we've changed ssid or password
@@ -222,8 +254,6 @@ void System::wifi_reconnect() {
void System::format(uuid::console::Shell & shell) {
auto msg = ("Formatting file system. This will reset all settings to their defaults");
shell.logger().warning(msg);
// shell.flush();
EMSuart::stop();
#ifndef EMSESP_STANDALONE
@@ -234,9 +264,6 @@ void System::format(uuid::console::Shell & shell) {
}
void System::syslog_init() {
#ifndef EMSESP_STANDALONE
bool was_enabled = syslog_enabled_;
#endif
EMSESP::webSettingsService.read([&](WebSettings & settings) {
syslog_enabled_ = settings.syslog_enabled;
syslog_level_ = settings.syslog_level;
@@ -247,44 +274,42 @@ void System::syslog_init() {
#ifndef EMSESP_STANDALONE
if (syslog_enabled_) {
// start & configure syslog
if (!was_enabled) {
syslog_.start();
EMSESP::logger().info("Starting Syslog");
}
EMSESP::logger().info("Starting Syslog service");
syslog_.start();
syslog_.log_level((uuid::log::Level)syslog_level_);
syslog_.mark_interval(syslog_mark_interval_);
syslog_.destination(syslog_host_.c_str(), syslog_port_);
syslog_.hostname(hostname().c_str());
// register the command
Command::add(EMSdevice::DeviceType::SYSTEM, F_(syslog), System::command_syslog_level, FL_(changeloglevel_cmd), CommandFlag::ADMIN_ONLY);
// removed in 3.6.0
// Command::add(EMSdevice::DeviceType::SYSTEM, F_(syslog), System::command_syslog_level, FL_(changeloglevel_cmd), CommandFlag::ADMIN_ONLY);
} else if (was_enabled) {
} else if (syslog_.started()) {
// in case service is still running, this flushes the queue
// https://github.com/emsesp/EMS-ESP/issues/496
EMSESP::logger().info("Stopping Syslog");
syslog_.log_level((uuid::log::Level)-1);
syslog_.log_level((uuid::log::Level)-1); // stop server
syslog_.mark_interval(0);
syslog_.destination("");
}
if (Mqtt::publish_single()) {
if (Mqtt::publish_single2cmd()) {
Mqtt::publish("system/syslog", syslog_enabled_ ? (FL_(list_syslog_level)[syslog_level_ + 1]) : "off");
Mqtt::queue_publish("system/syslog", syslog_enabled_ ? (FL_(list_syslog_level)[syslog_level_ + 1]) : "off");
if (EMSESP::watch_id() == 0 || EMSESP::watch() == 0) {
Mqtt::publish("system/watch",
EMSESP::system_.enum_format() == ENUM_FORMAT_INDEX ? Helpers::itoa(EMSESP::watch()) : (FL_(list_watch)[EMSESP::watch()]));
Mqtt::queue_publish("system/watch",
EMSESP::system_.enum_format() == ENUM_FORMAT_INDEX ? Helpers::itoa(EMSESP::watch()) : (FL_(list_watch)[EMSESP::watch()]));
} else {
Mqtt::publish("system/watch", Helpers::hextoa(EMSESP::watch_id()));
Mqtt::queue_publish("system/watch", Helpers::hextoa(EMSESP::watch_id()));
}
} else {
Mqtt::publish("system_data/syslog", syslog_enabled_ ? (FL_(list_syslog_level)[syslog_level_ + 1]) : "off");
Mqtt::queue_publish("system_data/syslog", syslog_enabled_ ? (FL_(list_syslog_level)[syslog_level_ + 1]) : "off");
if (EMSESP::watch_id() == 0 || EMSESP::watch() == 0) {
Mqtt::publish("system_data/watch",
EMSESP::system_.enum_format() == ENUM_FORMAT_INDEX ? Helpers::itoa(EMSESP::watch()) : (FL_(list_watch)[EMSESP::watch()]));
Mqtt::queue_publish("system_data/watch",
EMSESP::system_.enum_format() == ENUM_FORMAT_INDEX ? Helpers::itoa(EMSESP::watch()) : (FL_(list_watch)[EMSESP::watch()]));
} else {
Mqtt::publish("system_data/watch", Helpers::hextoa(EMSESP::watch_id()));
Mqtt::queue_publish("system_data/watch", Helpers::hextoa(EMSESP::watch_id()));
}
}
}
@@ -352,9 +377,7 @@ void System::wifi_tweak() {
bool s1 = WiFi.getSleep();
WiFi.setSleep(false); // turn off sleep - WIFI_PS_NONE
bool s2 = WiFi.getSleep();
#if defined(EMSESP_DEBUG)
LOG_DEBUG("[DEBUG] Adjusting WiFi - Tx power %d->%d, Sleep %d->%d", p1, p2, s1, s2);
#endif
LOG_DEBUG("Adjusting WiFi - Tx power %d->%d, Sleep %d->%d", p1, p2, s1, s2);
#endif
}
@@ -391,10 +414,13 @@ void System::start() {
setCpuFrequencyMhz(160);
#endif
}
// get current memory values
fstotal_ = LittleFS.totalBytes() / 1024; // read only once, it takes 500 ms to read
psram_ = ESP.getPsramSize() / 1024;
appused_ = ESP.getSketchSize() / 1024;
appfree_ = ESP.getFreeSketchSpace() / 1024 - appused_;
appfree_ = esp_ota_get_running_partition()->size / 1024 - appused_;
refreshHeapMem(); // refresh free heap and max alloc heap
#endif
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) {
@@ -405,16 +431,15 @@ void System::start() {
led_init(false); // init LED
button_init(false); // the special button
network_init(false); // network
syslog_init(); // start Syslog
EMSESP::uart_init(); // start UART
syslog_init(); // start syslog
}
// button single click
void System::button_OnClick(PButton & b) {
LOG_DEBUG("Button pressed - single click");
#ifdef EMSESP_DEBUG
#if defined(EMSESP_TEST)
#ifndef EMSESP_STANDALONE
Test::listDir(LittleFS, FS_CONFIG_DIRECTORY, 3);
#endif
@@ -437,7 +462,6 @@ void System::button_OnVLongPress(PButton & b) {
LOG_DEBUG("Button pressed - very long press");
#ifndef EMSESP_STANDALONE
LOG_WARNING("Performing factory reset...");
EMSESP::console_.loop();
EMSESP::esp8266React.factoryReset();
#endif
}
@@ -519,29 +543,15 @@ void System::loop() {
led_monitor(); // check status and report back using the LED
system_check(); // check system health
#ifndef EMSESP_STANDALONE
#if defined(EMSESP_DEBUG)
/*
static uint32_t last_memcheck_ = 0;
if (currentMillis - last_memcheck_ > 10000) { // 10 seconds
last_memcheck_ = currentMillis;
show_mem("core");
}
*/
#endif
#endif
#endif
}
// send MQTT info topic appended with the version information as JSON, as a retained flag
void System::send_info_mqtt(const char * event_str, bool send_ntp) {
StaticJsonDocument<EMSESP_JSON_SIZE_MEDIUM> doc;
doc["event"] = event_str;
doc["version"] = EMSESP_APP_VERSION;
// use dynamic json because it is called from NTP-callback from lwip task with small stack
DynamicJsonDocument doc = DynamicJsonDocument(EMSESP_JSON_SIZE_MEDIUM);
doc["event"] = event_str;
doc["version"] = EMSESP_APP_VERSION;
// if NTP is enabled send the boot_time in local time in ISO 8601 format (eg: 2022-11-15 20:46:38)
// https://github.com/emsesp/EMS-ESP32/issues/751
@@ -554,15 +564,18 @@ void System::send_info_mqtt(const char * event_str, bool send_ntp) {
#ifndef EMSESP_STANDALONE
if (EMSESP::system_.ethernet_connected()) {
doc["network"] = "ethernet";
doc["hostname"] = ETH.getHostname();
doc["network"] = "ethernet";
doc["hostname"] = ETH.getHostname();
/*
doc["MAC"] = ETH.macAddress();
doc["IPv4 address"] = uuid::printable_to_string(ETH.localIP()) + "/" + uuid::printable_to_string(ETH.subnetMask());
doc["IPv4 gateway"] = uuid::printable_to_string(ETH.gatewayIP());
doc["IPv4 nameserver"] = uuid::printable_to_string(ETH.dnsIP());
if (ETH.localIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000") {
doc["IPv6 address"] = uuid::printable_to_string(ETH.localIPv6());
}
}
*/
} else if (WiFi.status() == WL_CONNECTED) {
doc["network"] = "wifi";
doc["hostname"] = WiFi.getHostname();
@@ -578,7 +591,7 @@ void System::send_info_mqtt(const char * event_str, bool send_ntp) {
}
}
#endif
Mqtt::publish_retain(F_(info), doc.as<JsonObject>(), true); // topic called "info" and it's Retained
Mqtt::queue_publish_retain(F_(info), doc.as<JsonObject>(), true); // topic called "info" and it's Retained
}
// create the json for heartbeat
@@ -617,13 +630,14 @@ bool System::heartbeat_json(JsonObject & output) {
output["apicalls"] = WebAPIService::api_count(); // + WebAPIService::api_fails();
output["apifails"] = WebAPIService::api_fails();
if (EMSESP::dallas_enabled() || EMSESP::analog_enabled()) {
output["sensorreads"] = EMSESP::dallassensor_.reads() + EMSESP::analogsensor_.reads();
output["sensorfails"] = EMSESP::dallassensor_.fails() + EMSESP::analogsensor_.fails();
if (EMSESP::sensor_enabled() || EMSESP::analog_enabled()) {
output["sensorreads"] = EMSESP::temperaturesensor_.reads() + EMSESP::analogsensor_.reads();
output["sensorfails"] = EMSESP::temperaturesensor_.fails() + EMSESP::analogsensor_.fails();
}
#ifndef EMSESP_STANDALONE
output["freemem"] = ESP.getFreeHeap() / 1024; // kilobytes
output["freemem"] = getHeapMem();
output["max_alloc"] = getMaxAllocMem();
#endif
#ifndef EMSESP_STANDALONE
@@ -644,11 +658,13 @@ void System::send_heartbeat() {
return;
}
refreshHeapMem(); // refresh free heap and max alloc heap
StaticJsonDocument<EMSESP_JSON_SIZE_MEDIUM> doc;
JsonObject json = doc.to<JsonObject>();
if (heartbeat_json(json)) {
Mqtt::publish(F_(heartbeat), json); // send to MQTT with retain off. This will add to MQTT queue.
Mqtt::queue_publish(F_(heartbeat), json); // send to MQTT with retain off. This will add to MQTT queue.
}
}
@@ -659,11 +675,14 @@ void System::network_init(bool refresh) {
}
last_system_check_ = 0; // force the LED to go from fast flash to pulse
send_heartbeat();
if (phy_type_ == PHY_type::PHY_TYPE_NONE) {
bool disableEth;
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & settings) { disableEth = settings.ssid.length() > 0; });
// no ethernet present or disabled
if (phy_type_ == PHY_type::PHY_TYPE_NONE || disableEth) {
return;
}
} // no ethernet present
// configure Ethernet
int mdc = 23; // Pin# of the I²C clock signal for the Ethernet PHY - hardcoded
@@ -671,14 +690,14 @@ void System::network_init(bool refresh) {
uint8_t phy_addr = eth_phy_addr_; // I²C-address of Ethernet PHY (0 or 1 for LAN8720, 31 for TLK110)
int8_t power = eth_power_; // Pin# of the enable signal for the external crystal oscillator (-1 to disable for internal APLL source)
eth_phy_type_t type = (phy_type_ == PHY_type::PHY_TYPE_LAN8720) ? ETH_PHY_LAN8720 : ETH_PHY_TLK110; // Type of the Ethernet PHY (LAN8720 or TLK110)
// clock mode
// ETH_CLOCK_GPIO0_IN = 0 RMII clock input to GPIO0
// ETH_CLOCK_GPIO0_OUT = 1 RMII clock output from GPIO0
// ETH_CLOCK_GPIO16_OUT = 2 RMII clock output from GPIO16
// ETH_CLOCK_GPIO17_OUT = 3 RMII clock output from GPIO17, for 50hz inverted clock
// clock mode:
// ETH_CLOCK_GPIO0_IN = 0 RMII clock input to GPIO0
// ETH_CLOCK_GPIO0_OUT = 1 RMII clock output from GPIO0
// ETH_CLOCK_GPIO16_OUT = 2 RMII clock output from GPIO16
// ETH_CLOCK_GPIO17_OUT = 3 RMII clock output from GPIO17, for 50hz inverted clock
auto clock_mode = (eth_clock_mode_t)eth_clock_mode_;
ETH.begin(phy_addr, power, mdc, mdio, type, clock_mode);
eth_present_ = ETH.begin(phy_addr, power, mdc, mdio, type, clock_mode);
}
// check health of system, done every 5 seconds
@@ -715,7 +734,6 @@ void System::system_check() {
digitalWrite(led_gpio_, hide_led_ ? !LED_ON : LED_ON);
#endif
}
send_heartbeat();
} else {
// turn off LED so we're ready to the flashes
if (led_gpio_) {
@@ -732,23 +750,22 @@ void System::system_check() {
}
// commands - takes static function pointers
// can be called via Console using 'call system <cmd>'
void System::commands_init() {
Command::add(EMSdevice::DeviceType::SYSTEM, F_(send), System::command_send, FL_(send_cmd), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), System::command_fetch, FL_(fetch_cmd), CommandFlag::ADMIN_ONLY);
// restart and watch (and test) are also exposed as Console commands
Command::add(EMSdevice::DeviceType::SYSTEM, F_(restart), System::command_restart, FL_(restart_cmd), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(watch), System::command_watch, FL_(watch_cmd));
if (Mqtt::enabled()) {
Command::add(EMSdevice::DeviceType::SYSTEM, F_(publish), System::command_publish, FL_(publish_cmd));
}
#if defined(EMSESP_TEST)
Command::add(EMSdevice::DeviceType::SYSTEM, ("test"), System::command_test, FL_(test_cmd));
#endif
// these commands will return data in JSON format
Command::add(EMSdevice::DeviceType::SYSTEM, F_(info), System::command_info, FL_(system_info_cmd));
Command::add(EMSdevice::DeviceType::SYSTEM, F_(commands), System::command_commands, FL_(commands_cmd));
#if defined(EMSESP_DEBUG)
Command::add(EMSdevice::DeviceType::SYSTEM, ("test"), System::command_test, FL_(test_cmd));
#endif
Command::add(EMSdevice::DeviceType::SYSTEM, F("response"), System::command_response, FL_(commands_response));
// MQTT subscribe "ems-esp/system/#"
Mqtt::subscribe(EMSdevice::DeviceType::SYSTEM, "system/#", nullptr); // use empty function callback
@@ -830,7 +847,7 @@ void System::led_monitor() {
}
if (led_on_) {
digitalWrite(led_gpio_, LED_ON); // LED off
digitalWrite(led_gpio_, LED_ON); // LED off
}
#endif
} else {
@@ -880,19 +897,27 @@ void System::show_users(uuid::console::Shell & shell) {
}
void System::show_system(uuid::console::Shell & shell) {
refreshHeapMem(); // refresh free heap and max alloc heap
shell.println("System:");
shell.printfln(" Board profile: %s", board_profile().c_str());
shell.printfln(" Uptime: %s", uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3).c_str());
#ifndef EMSESP_STANDALONE
shell.printfln(" SDK version: %s", ESP.getSdkVersion());
shell.printfln(" CPU frequency: %lu MHz", ESP.getCpuFreqMHz());
shell.printfln(" Free heap: %lu KB", (uint32_t)ESP.getFreeHeap() / 1024);
shell.printfln(" Free heap/Max alloc: %lu KB / %lu KB", getHeapMem(), getMaxAllocMem());
shell.printfln(" App used/free: %lu KB / %lu KB", appUsed(), appFree());
uint32_t FSused = LittleFS.usedBytes() / 1024;
shell.printfln(" FS used/free: %lu KB / %lu KB", FSused, FStotal() - FSused);
shell.println();
shell.println("Network:");
// show ethernet mac address if we have an eth controller present
if (eth_present_) {
shell.printfln(" Ethernet MAC address: %s", ETH.macAddress().c_str());
}
switch (WiFi.status()) {
case WL_IDLE_STATUS:
shell.printfln(" Status: Idle");
@@ -907,7 +932,7 @@ void System::show_system(uuid::console::Shell & shell) {
break;
case WL_CONNECTED:
shell.printfln(" Status: connected");
shell.printfln(" Status: WiFi connected");
shell.printfln(" SSID: %s", WiFi.SSID().c_str());
shell.printfln(" BSSID: %s", WiFi.BSSIDstr().c_str());
shell.printfln(" RSSI: %d dBm (%d %%)", WiFi.RSSI(), wifi_quality(WiFi.RSSI()));
@@ -935,7 +960,8 @@ void System::show_system(uuid::console::Shell & shell) {
case WL_NO_SHIELD:
default:
shell.printfln(" WiFi Network: Unknown");
shell.printfln(" WiFi MAC address: %s", WiFi.macAddress().c_str());
shell.printfln(" WiFi Network: not connected");
break;
}
@@ -943,7 +969,7 @@ void System::show_system(uuid::console::Shell & shell) {
if (ethernet_connected_) {
shell.println();
shell.printfln(" Status: Ethernet connected");
shell.printfln(" MAC address: %s", ETH.macAddress().c_str());
shell.printfln(" Ethernet MAC address: %s", ETH.macAddress().c_str());
shell.printfln(" Hostname: %s", ETH.getHostname());
shell.printfln(" IPv4 address: %s/%s", uuid::printable_to_string(ETH.localIP()).c_str(), uuid::printable_to_string(ETH.subnetMask()).c_str());
shell.printfln(" IPv4 gateway: %s", uuid::printable_to_string(ETH.gatewayIP()).c_str());
@@ -996,9 +1022,16 @@ bool System::check_restore() {
reboot_required |= saveSettings(NTP_SETTINGS_FILE, "NTP", input);
reboot_required |= saveSettings(SECURITY_SETTINGS_FILE, "Security", input);
reboot_required |= saveSettings(EMSESP_SETTINGS_FILE, "Settings", input);
reboot_required |= saveSettings(OTA_SETTINGS_FILE, "OTA", input);
} else if (settings_type == "customizations") {
// it's a customization file, just replace it and there's no need to reboot
saveSettings(EMSESP_CUSTOMIZATION_FILE, "Customizations", input);
} else if (settings_type == "schedule") {
// it's a schedule file, just replace it and there's no need to reboot
saveSettings(EMSESP_SCHEDULER_FILE, "Schedule", input);
} else if (settings_type == "entities") {
// it's a entity file, just replace it and there's no need to reboot
saveSettings(EMSESP_ENTITY_FILE, "Entities", input);
} else {
LOG_ERROR("Unrecognized file uploaded");
}
@@ -1029,15 +1062,14 @@ bool System::check_upgrade(bool factory_settings) {
// see if we're missing a version, will be < 3.5.0b13 from Dec 23 2022
missing_version = (settingsVersion.empty() || (settingsVersion.length() < 5));
if (missing_version) {
#ifdef EMSESP_DEBUG
LOG_DEBUG("No version information found (%s)", settingsVersion.c_str());
#endif
settingsVersion = "3.4.4"; // this was the last stable version
settingsVersion = "3.5.0"; // this was the last stable version
}
}
version::Semver200_version settings_version(settingsVersion);
#if defined(EMSESP_DEBUG)
if (!missing_version) {
LOG_INFO("Current version from settings is %d.%d.%d-%s",
settings_version.major(),
@@ -1045,6 +1077,7 @@ bool System::check_upgrade(bool factory_settings) {
settings_version.patch(),
settings_version.prerelease().c_str());
}
#endif
// always save the new version to the settings
EMSESP::webSettingsService.update(
@@ -1067,9 +1100,7 @@ bool System::check_upgrade(bool factory_settings) {
// if we're coming from 3.4.4 or 3.5.0b14 then we need to apply new settings
if (missing_version) {
#ifdef EMSESP_DEBUG
LOG_DEBUG("Setting MQTT ID Entity to v3.4 format");
#endif
LOG_DEBUG("Setting MQTT Entity ID format to v3.4 format");
EMSESP::esp8266React.getMqttSettingsService()->update(
[&](MqttSettings & mqttSettings) {
mqttSettings.entity_format = 0; // use old Entity ID format from v3.4
@@ -1119,7 +1150,7 @@ bool System::saveSettings(const char * filename, const char * section, JsonObjec
if (section_json) {
File section_file = LittleFS.open(filename, "w");
if (section_file) {
LOG_INFO("Applying new %s settings", section);
LOG_INFO("Applying new %s", section);
serializeJson(section_json, section_file);
section_file.close();
return true; // reboot required
@@ -1141,9 +1172,9 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
node["uptime (seconds)"] = uuid::get_uptime_sec();
#ifndef EMSESP_STANDALONE
node["free mem"] = ESP.getFreeHeap() / 1024; // kilobytes
node["max alloc"] = ESP.getMaxAllocHeap() / 1024; // kilobytes
node["free app"] = EMSESP::system_.appFree(); // kilobytes
node["free mem"] = getHeapMem();
node["max alloc"] = getMaxAllocMem();
node["free app"] = EMSESP::system_.appFree(); // kilobytes
#endif
node["reset reason"] = EMSESP::system_.reset_reason(0) + " / " + EMSESP::system_.reset_reason(1);
@@ -1151,9 +1182,9 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
// Network Status
node = output.createNestedObject("Network Info");
if (EMSESP::system_.ethernet_connected()) {
node["network"] = "Ethernet";
node["hostname"] = ETH.getHostname();
node["MAC"] = ETH.macAddress();
node["network"] = "Ethernet";
node["hostname"] = ETH.getHostname();
// node["MAC"] = ETH.macAddress();
node["IPv4 address"] = uuid::printable_to_string(ETH.localIP()) + "/" + uuid::printable_to_string(ETH.subnetMask());
node["IPv4 gateway"] = uuid::printable_to_string(ETH.gatewayIP());
node["IPv4 nameserver"] = uuid::printable_to_string(ETH.dnsIP());
@@ -1163,9 +1194,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
} else if (WiFi.status() == WL_CONNECTED) {
node["network"] = "WiFi";
node["hostname"] = WiFi.getHostname();
// node["SSID"] = WiFi.SSID();
// node["BSSID"] = WiFi.BSSIDstr();
node["RSSI"] = WiFi.RSSI();
node["RSSI"] = WiFi.RSSI();
// node["MAC"] = WiFi.macAddress();
node["IPv4 address"] = uuid::printable_to_string(WiFi.localIP()) + "/" + uuid::printable_to_string(WiFi.subnetMask());
node["IPv4 gateway"] = uuid::printable_to_string(WiFi.gatewayIP());
@@ -1231,6 +1260,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
node["entity format"] = settings.entity_format;
node["base"] = settings.base;
node["discovery prefix"] = settings.discovery_prefix;
node["discovery type"] = settings.discovery_type;
node["nested format"] = settings.nested_format;
node["ha enabled"] = settings.ha_enabled;
node["mqtt qos"] = settings.mqtt_qos;
@@ -1261,10 +1291,10 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
// Sensor Status
node = output.createNestedObject("Sensor Info");
if (EMSESP::dallas_enabled()) {
node["temperature sensors"] = EMSESP::dallassensor_.no_sensors();
node["temperature sensor reads"] = EMSESP::dallassensor_.reads();
node["temperature sensor fails"] = EMSESP::dallassensor_.fails();
if (EMSESP::sensor_enabled()) {
node["temperature sensors"] = EMSESP::temperaturesensor_.no_sensors();
node["temperature sensor reads"] = EMSESP::temperaturesensor_.reads();
node["temperature sensor fails"] = EMSESP::temperaturesensor_.fails();
}
if (EMSESP::analog_enabled()) {
node["analog sensors"] = EMSESP::analogsensor_.no_sensors();
@@ -1383,11 +1413,10 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
return true;
}
#if defined(EMSESP_DEBUG)
#if defined(EMSESP_TEST)
// run a test, e.g. http://ems-esp/api?device=system&cmd=test&data=boiler
bool System::command_test(const char * value, const int8_t id) {
Test::run_test(value, id);
return true;
return Test::run_test(value, id);
}
#endif
@@ -1403,25 +1432,27 @@ bool System::command_test(const char * value, const int8_t id) {
// 3 = RMII clock output from GPIO17, for 50hz inverted clock
bool System::load_board_profile(std::vector<int8_t> & data, const std::string & board_profile) {
if (board_profile == "S32") {
data = {2, 18, 23, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // BBQKees Gateway S32
data = {2, 18, 23, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // BBQKees Gateway S32
} else if (board_profile == "E32") {
data = {2, 4, 5, 17, 33, PHY_type::PHY_TYPE_LAN8720, 16, 1, 0}; // BBQKees Gateway E32
} else if (board_profile == "MH-ET") {
data = {2, 18, 23, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // MH-ET Live D1 Mini
data = {2, 18, 23, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // MH-ET Live D1 Mini
} else if (board_profile == "NODEMCU") {
data = {2, 18, 23, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // NodeMCU 32S
data = {2, 18, 23, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // NodeMCU 32S
} else if (board_profile == "LOLIN") {
data = {2, 18, 17, 16, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // Lolin D32
data = {2, 18, 17, 16, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // Lolin D32
} else if (board_profile == "OLIMEX") {
data = {0, 0, 36, 4, 34, PHY_type::PHY_TYPE_LAN8720, -1, 0, 0}; // Olimex ESP32-EVB (uses U1TXD/U1RXD/BUTTON, no LED or Dallas)
data = {0, 0, 36, 4, 34, PHY_type::PHY_TYPE_LAN8720, -1, 0, 0}; // Olimex ESP32-EVB (uses U1TXD/U1RXD/BUTTON, no LED or Temperature sensor)
} else if (board_profile == "OLIMEXPOE") {
data = {0, 0, 36, 4, 34, PHY_type::PHY_TYPE_LAN8720, 12, 0, 3}; // Olimex ESP32-POE
} else if (board_profile == "C3MINI") {
data = {7, 1, 4, 5, 9, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // Lolin C3 Mini
data = {7, 1, 4, 5, 9, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // Lolin C3 Mini
} else if (board_profile == "S2MINI") {
data = {15, 7, 11, 12, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // Lolin S2 Mini
data = {15, 7, 11, 12, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // Lolin S2 Mini
} else if (board_profile == "S3MINI") {
data = {17, 18, 8, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // Liligo S3
data = {17, 18, 8, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // Liligo S3
} else if (board_profile == "S32S3") {
data = {2, 18, 5, 17, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0}; // BBQKees Gateway S3
} else if (board_profile == "CUSTOM") {
// send back current values
data = {(int8_t)EMSESP::system_.led_gpio_,
@@ -1488,7 +1519,7 @@ std::string System::reset_reason(uint8_t cpu) const {
break;
}
#endif
return ("Unkonwn");
return ("Unknown");
}
#pragma GCC diagnostic pop

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -55,14 +55,14 @@ class System {
static bool command_publish(const char * value, const int8_t id);
static bool command_fetch(const char * value, const int8_t id);
static bool command_restart(const char * value, const int8_t id);
#if defined(EMSESP_DEBUG)
static bool command_test(const char * value, const int8_t id);
#endif
static bool command_syslog_level(const char * value, const int8_t id);
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_commands(const char * value, const int8_t id, JsonObject & output);
static bool command_response(const char * value, const int8_t id, JsonObject & output);
#if defined(EMSESP_TEST)
static bool command_test(const char * value, const int8_t id);
#endif
std::string reset_reason(uint8_t cpu) const;
@@ -230,9 +230,38 @@ class System {
return appused_;
}
// memory in kb
static uint32_t getMaxAllocMem() {
return max_alloc_mem_;
}
static uint32_t getHeapMem() {
return heap_mem_;
}
static void refreshHeapMem() {
#ifndef EMSESP_STANDALONE
max_alloc_mem_ = ESP.getMaxAllocHeap() / 1024;
heap_mem_ = ESP.getFreeHeap() / 1024;
#endif
}
static bool test_set_all_active() {
return test_set_all_active_;
}
static void test_set_all_active(bool n) {
#if defined(EMSESP_TEST)
if (n) {
logger_.debug("Using dummy entity values");
}
#endif
test_set_all_active_ = n;
}
private:
static uuid::log::Logger logger_;
static bool restart_requested_;
static bool test_set_all_active_; // force all entities in a device to have a value
static uint32_t max_alloc_mem_;
static uint32_t heap_mem_;
// button
static PButton myPButton_; // PButton instance
@@ -271,6 +300,8 @@ class System {
bool ntp_connected_ = false;
uint32_t ntp_last_check_ = 0;
bool eth_present_ = false;
// EMS-ESP settings
// copies from WebSettings class in WebSettingsService.h and loaded with reload_settings()
std::string hostname_;

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -148,11 +148,11 @@ void RxService::add(uint8_t * data, uint8_t length) {
// validate the CRC. if it fails then increment the number of corrupt/incomplete telegrams and only report to console/syslog
uint8_t crc = calculate_crc(data, length - 1);
if (data[length - 1] != crc) {
if ((data[0] & 0x7F) != ems_bus_id()) { // do not count echos as errors
if ((data[0] & 0x7F) != ems_bus_id()) { // do not count echos as errors
telegram_error_count_++;
LOG_WARNING("Incomplete Rx: %s", Helpers::data_to_hex(data, length - 1).c_str()); // exclude CRC
} else {
LOG_TRACE("Incomplete Rx: %s", Helpers::data_to_hex(data, length - 1).c_str()); // exclude CRC
LOG_TRACE("Incomplete Rx: %s", Helpers::data_to_hex(data, length - 1).c_str()); // exclude CRC
}
return;
}
@@ -211,9 +211,7 @@ void RxService::add(uint8_t * data, uint8_t length) {
LOG_TRACE("Rx: %s", Helpers::data_to_hex(data, length).c_str());
}
#ifdef EMSESP_DEBUG
LOG_DEBUG("[DEBUG] New Rx telegram, message length %d", message_length);
#endif
LOG_DEBUG("New Rx telegram, message length %d", message_length);
// if we don't have a type_id exit,
// do not exit on empty message, it is checked for toggle fetch
@@ -349,7 +347,7 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
telegram_raw[5] = (telegram->type_id >> 8) - 1; // type, 1st byte, high-byte, subtract 0x100
telegram_raw[6] = telegram->type_id & 0xFF; // type, 2nd byte, low-byte
message_p = 7;
copy_data = false; // there are no more data values after the type_id when reading on EMS+
copy_data = false; // there are no more data values after the type_id when reading on EMS+
}
} else {
// EMS 1.0
@@ -398,11 +396,11 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
if (status == EMS_TX_STATUS_ERR) {
LOG_ERROR("Failed to transmit Tx via UART.");
if (telegram->operation == Telegram::Operation::TX_READ) {
increment_telegram_read_fail_count(); // another Tx fail
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
tx_state(Telegram::Operation::NONE); // nothing send, tx not in wait state
return;
}
@@ -443,9 +441,7 @@ void TxService::add(const uint8_t operation,
const bool front) {
auto telegram = std::make_shared<Telegram>(operation, ems_bus_id(), dest, type_id, offset, message_data, message_length);
#ifdef EMSESP_DEBUG
LOG_DEBUG("[DEBUG] New Tx [#%d] telegram, length %d", tx_telegram_id_, message_length);
#endif
LOG_DEBUG("New Tx [#%d] telegram, length %d", tx_telegram_id_, message_length);
// if the queue is full, make room by removing the last one
if (tx_telegrams_.size() >= MAX_TX_TELEGRAMS) {
@@ -460,7 +456,7 @@ void TxService::add(const uint8_t operation,
if (front) {
tx_telegrams_.emplace_front(tx_telegram_id_++, std::move(telegram), false, validateid); // add to front of queue
} else {
tx_telegrams_.emplace_back(tx_telegram_id_++, std::move(telegram), false, validateid); // add to back of queue
tx_telegrams_.emplace_back(tx_telegram_id_++, std::move(telegram), false, validateid); // add to back of queue
}
if (validateid != 0) {
EMSESP::wait_validate(validateid);
@@ -507,9 +503,7 @@ void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t lengt
// if we don't have a type_id or empty data block, exit
if ((type_id == 0) || (message_length == 0)) {
#ifdef EMSESP_DEBUG
LOG_DEBUG("[DEBUG] Tx telegram type %d failed, length %d", type_id, message_length);
#endif
LOG_DEBUG("Tx telegram type %d failed, length %d", type_id, message_length);
return;
}
@@ -518,11 +512,16 @@ void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t lengt
operation = Telegram::Operation::NONE; // do not check reply/ack for other ids
} else if (dest & 0x80) {
operation = Telegram::Operation::TX_READ;
EMSESP::set_response_id(type_id);
// trigger read of all parts of telegram if requested length is more than 32
// compatibility to earlier versions
if (message_data[0] >= 32) {
EMSESP::set_read_id(type_id);
}
} else {
operation = Telegram::Operation::TX_WRITE;
validate_id = type_id;
}
EMSESP::set_read_id(type_id);
}
auto telegram = std::make_shared<Telegram>(operation, src, dest, type_id, offset, message_data, message_length); // operation is TX_WRITE or TX_READ
@@ -537,9 +536,7 @@ void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t lengt
tx_telegrams_.pop_front();
}
#ifdef EMSESP_DEBUG
LOG_DEBUG("[DEBUG] New Tx [#%d] telegram, length %d", tx_telegram_id_, message_length);
#endif
LOG_DEBUG("New Tx [#%d] telegram, length %d", tx_telegram_id_, message_length);
if (front) {
// tx_telegrams_.push_front(qtxt); // add to front of queue
@@ -554,16 +551,14 @@ void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t lengt
}
// send a Tx telegram to request data from an EMS device
void TxService::read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t length) {
void TxService::read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t length, const bool front) {
LOG_DEBUG("Tx read request to device 0x%02X for type ID 0x%02X", dest, type_id);
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) {
if (length > 0 && length < message_data) {
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, front);
}
// Send a raw telegram to the bus, telegram is a text string of hex values
@@ -574,32 +569,16 @@ bool TxService::send_raw(const char * telegram_data) {
// since the telegram data is a const, make a copy. add 1 to grab the \0 EOS
char telegram[EMS_MAX_TELEGRAM_LENGTH * 3];
for (uint8_t i = 0; i < strlen(telegram_data); i++) {
telegram[i] = telegram_data[i];
}
telegram[strlen(telegram_data)] = '\0'; // make sure its terminated
strlcpy(telegram, telegram_data, sizeof(telegram));
uint8_t count = 0;
char * p;
char value[10] = {0};
uint8_t data[EMS_MAX_TELEGRAM_LENGTH];
// get first value, which should be the src
if ((p = strtok(telegram, " ,"))) { // delimiter
strlcpy(value, p, sizeof(value));
data[0] = (uint8_t)strtol(value, 0, 16);
} else {
return false;
}
// and iterate until end
while (p != 0) {
if ((p = strtok(nullptr, " ,"))) {
strlcpy(value, p, sizeof(value));
auto val = (uint8_t)strtol(value, 0, 16);
data[++count] = val;
}
// get values
char * p = strtok(telegram, " ,"); // delimiter
while (p != nullptr) {
data[count++] = (uint8_t)strtol(p, 0, 16);
p = strtok(nullptr, " ,");
}
// check valid length
@@ -607,7 +586,7 @@ bool TxService::send_raw(const char * telegram_data) {
return false;
}
add(Telegram::Operation::TX_RAW, data, count + 1, 0, true); // add to top/front of Tx queue
add(Telegram::Operation::TX_RAW, data, count, 0, true); // add to top/front of Tx queue
return true;
}
@@ -616,14 +595,14 @@ bool 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
EMSESP::wait_validate(0); // do not wait for validation
reset_retry_count(); // give up
EMSESP::wait_validate(0); // do not wait for validation
if (operation == Telegram::Operation::TX_READ) {
if (telegram_last_->offset > 0) { // ignore errors for higher offsets
LOG_DEBUG("Last Tx Read operation failed after %d retries. Ignoring request: %s", MAXIMUM_TX_RETRIES, telegram_last_->to_string().c_str());
return;
}
increment_telegram_read_fail_count(); // another Tx fail
increment_telegram_read_fail_count(); // another Tx fail
} else {
increment_telegram_write_fail_count(); // another Tx fail
}
@@ -639,13 +618,11 @@ void TxService::retry_tx(const uint8_t operation, const uint8_t * data, const ui
return;
}
#ifdef EMSESP_DEBUG
LOG_DEBUG("[DEBUG] Last Tx %s operation failed. Retry #%d. sent message: %s, received: %s",
LOG_DEBUG("Last Tx %s operation failed. Retry #%d. sent message: %s, received: %s",
(operation == Telegram::Operation::TX_WRITE) ? ("Write") : ("Read"),
retry_count_,
telegram_last_->to_string().c_str(),
Helpers::data_to_hex(data, length - 1).c_str());
#endif
// add to the top of the queue
if (tx_telegrams_.size() >= MAX_TX_TELEGRAMS) {

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -37,30 +37,20 @@
#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
static constexpr uint8_t EMS_VALUE_BOOL_OFF = 0x00; // boolean false
static constexpr uint8_t EMS_VALUE_BOOL_ON = 0x01; // boolean true. True can be 0x01 or 0xFF sometimes
static constexpr uint8_t EMS_VALUE_BOOL = 0xFF; // used to mark that something is a boolean
static constexpr uint8_t EMS_VALUE_BOOL_OFF = 0x00; // boolean false
static constexpr uint8_t EMS_VALUE_BOOL_ON = 0x01; // boolean true. True can be 0x01 or 0xFF sometimes
static constexpr uint8_t EMS_VALUE_BOOL_NOTSET = 0xFE; // random number for booleans, that's not 0, 1 or FF
static constexpr uint8_t EMS_VALUE_UINT_NOTSET = 0xFF; // for 8-bit unsigned ints/bytes
static constexpr int8_t EMS_VALUE_INT_NOTSET = 0x7F; // for signed 8-bit ints/bytes
static constexpr uint16_t EMS_VALUE_USHORT_NOTSET = 0x7D00; // 32000: for 2-byte unsigned shorts
static constexpr int16_t EMS_VALUE_SHORT_NOTSET = 0x7D00; // 32000: for 2-byte signed shorts
static constexpr uint16_t EMS_VALUE_USHORT_NOTSET = 0x7D00; // 32000: for 2-byte unsigned shorts
static constexpr int16_t EMS_VALUE_SHORT_NOTSET = 0x7D00; // 32000: for 2-byte signed shorts
static constexpr uint32_t EMS_VALUE_ULONG_NOTSET = 0x00FFFFFF; // for 3-byte longs
static constexpr uint32_t EMS_VALUE_ULLONG_NOTSET = 0xFFFFFFFF; // for 4-byte longs
static constexpr uint8_t EMS_MAX_TELEGRAM_LENGTH = 32; // max length of a complete EMS telegram
static constexpr uint8_t EMS_MAX_TELEGRAM_MESSAGE_LENGTH = 27; // max length of message block, assuming EMS1.0
static constexpr uint8_t EMS_MAX_TELEGRAM_LENGTH = 32; // max length of a complete EMS telegram
static constexpr uint8_t EMS_MAX_TELEGRAM_MESSAGE_LENGTH = 27; // max length of message block, assuming EMS1.0
#if defined(EMSESP_STANDALONE_DUMP)
#define EMS_VALUE_DEFAULT_INT 11
#define EMS_VALUE_DEFAULT_UINT -12
#define EMS_VALUE_DEFAULT_SHORT -1234
#define EMS_VALUE_DEFAULT_USHORT 1234
#define EMS_VALUE_DEFAULT_ULONG 12356
#define EMS_VALUE_DEFAULT_BOOL 1
#define EMS_VALUE_DEFAULT_ENUM 1
#else
#define EMS_VALUE_DEFAULT_INT EMS_VALUE_INT_NOTSET
#define EMS_VALUE_DEFAULT_UINT EMS_VALUE_UINT_NOTSET
#define EMS_VALUE_DEFAULT_SHORT EMS_VALUE_SHORT_NOTSET
@@ -68,7 +58,15 @@ static constexpr uint8_t EMS_MAX_TELEGRAM_MESSAGE_LENGTH = 27; // max length of
#define EMS_VALUE_DEFAULT_ULONG EMS_VALUE_ULONG_NOTSET
#define EMS_VALUE_DEFAULT_BOOL EMS_VALUE_BOOL_NOTSET
#define EMS_VALUE_DEFAULT_ENUM EMS_VALUE_UINT_NOTSET
#endif
// used when System::test_set_all_active() is set
#define EMS_VALUE_DEFAULT_INT_DUMMY 11
#define EMS_VALUE_DEFAULT_UINT_DUMMY -12
#define EMS_VALUE_DEFAULT_SHORT_DUMMY -1234
#define EMS_VALUE_DEFAULT_USHORT_DUMMY 1235
#define EMS_VALUE_DEFAULT_ULONG_DUMMY 12456
#define EMS_VALUE_DEFAULT_BOOL_DUMMY 1
#define EMS_VALUE_DEFAULT_ENUM_DUMMY 1
namespace emsesp {
@@ -139,7 +137,7 @@ class Telegram {
return (val != value);
}
bool read_enumvalue(uint8_t & value, const uint8_t index, uint8_t start = 0) const {
bool read_enumvalue(uint8_t & value, const uint8_t index, int8_t start = 0) const {
if ((index < this->offset) || ((index - this->offset) >= this->message_length)) {
return false;
}
@@ -234,13 +232,13 @@ class EMSbus {
private:
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
static uint8_t tx_mode_; // local copy of the tx mode
static uint8_t tx_state_; // state of the Tx line (NONE or waiting on a TX_READ or TX_WRITE)
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
static uint8_t tx_mode_; // local copy of the tx mode
static uint8_t tx_state_; // state of the Tx line (NONE or waiting on a TX_READ or TX_WRITE)
};
class RxService : public EMSbus {
@@ -319,7 +317,7 @@ class TxService : public EMSbus {
const uint16_t validateid,
const bool front = false);
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 read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset = 0, const uint8_t length = 0, const bool readId = false);
bool send_raw(const char * telegram_data);
void send_poll() const;
void retry_tx(const uint8_t operation, const uint8_t * data, const uint8_t length);
@@ -435,17 +433,17 @@ 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_read_fail_count_ = 0; // # Tx unsuccessful transmits
uint32_t telegram_write_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
uint8_t retry_count_ = 0; // count for # Tx retries
uint32_t delayed_send_ = 0; // manage delay for post send query
uint8_t tx_telegram_id_ = 0; // queue counter
uint8_t tx_telegram_id_ = 0; // queue counter
void send_telegram(const QueuedTxTelegram & tx_telegram);
};

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -18,7 +18,7 @@
// code originally written by nomis - https://github.com/nomis
#include "dallassensor.h"
#include "temperaturesensor.h"
#include "emsesp.h"
#ifdef ESP32
@@ -29,10 +29,10 @@
namespace emsesp {
uuid::log::Logger DallasSensor::logger_{F_(dallassensor), uuid::log::Facility::DAEMON};
uuid::log::Logger TemperatureSensor::logger_{F_(temperaturesensor), uuid::log::Facility::DAEMON};
// start the 1-wire
void DallasSensor::start() {
void TemperatureSensor::start() {
reload();
if (!dallas_gpio_) {
@@ -42,32 +42,32 @@ void DallasSensor::start() {
#ifndef EMSESP_STANDALONE
bus_.begin(dallas_gpio_);
LOG_INFO("Starting Dallas sensor service");
LOG_INFO("Starting Temperature sensor service");
#endif
// Add API calls
Command::add(
EMSdevice::DeviceType::DALLASSENSOR,
EMSdevice::DeviceType::TEMPERATURESENSOR,
F_(info),
[&](const char * value, const int8_t id, JsonObject & output) { return command_info(value, id, output); },
FL_(info_cmd));
Command::add(
EMSdevice::DeviceType::DALLASSENSOR,
EMSdevice::DeviceType::TEMPERATURESENSOR,
F_(values),
[&](const char * value, const int8_t id, JsonObject & output) { return command_info(value, 0, output); },
nullptr,
CommandFlag::HIDDEN); // this command is hidden
Command::add(
EMSdevice::DeviceType::DALLASSENSOR,
EMSdevice::DeviceType::TEMPERATURESENSOR,
F_(commands),
[&](const char * value, const int8_t id, JsonObject & output) { return command_commands(value, id, output); },
FL_(commands_cmd));
Mqtt::subscribe(EMSdevice::DeviceType::DALLASSENSOR, "dallasssensor/#", nullptr); // use empty function callback
Mqtt::subscribe(EMSdevice::DeviceType::TEMPERATURESENSOR, "temperaturesensor/#", nullptr); // use empty function callback
}
// load settings
void DallasSensor::reload() {
void TemperatureSensor::reload() {
// load the service settings
EMSESP::webSettingsService.read([&](WebSettings & settings) {
dallas_gpio_ = settings.dallas_gpio;
@@ -80,7 +80,7 @@ void DallasSensor::reload() {
}
}
void DallasSensor::loop() {
void TemperatureSensor::loop() {
if (!dallas_gpio_) {
return; // dallas gpio is 0 (disabled)
}
@@ -91,7 +91,7 @@ void DallasSensor::loop() {
if (state_ == State::IDLE) {
if (time_now - last_activity_ >= READ_INTERVAL_MS) {
#ifdef EMSESP_DEBUG_SENSOR
LOG_DEBUG("[DEBUG] Read sensor temperature");
LOG_DEBUG("Read sensor temperature");
#endif
if (bus_.reset() || parasite_) {
YIELD;
@@ -119,13 +119,13 @@ void DallasSensor::loop() {
} else if (state_ == State::READING) {
if (temperature_convert_complete() && (time_now - last_activity_ > CONVERSION_MS)) {
#ifdef EMSESP_DEBUG_SENSOR
LOG_DEBUG("Scanning for sensors");
LOG_DEBUG("Scanning for temperature sensors");
#endif
bus_.reset_search();
state_ = State::SCANNING;
} else if (time_now - last_activity_ > READ_TIMEOUT_MS) {
#ifdef EMSESP_DEBUG_SENSOR
LOG_WARNING("Dallas sensor read timeout");
LOG_WARNING("Sensor read timeout");
#endif
state_ = State::IDLE;
sensorfails_++;
@@ -133,7 +133,7 @@ void DallasSensor::loop() {
} else if (state_ == State::SCANNING) {
if (time_now - last_activity_ > SCAN_TIMEOUT_MS) {
#ifdef EMSESP_DEBUG_SENSOR
LOG_ERROR("Dallas sensor scan timeout");
LOG_ERROR("Sensor scan timeout");
#endif
state_ = State::IDLE;
sensorfails_++;
@@ -188,12 +188,12 @@ void DallasSensor::loop() {
default:
sensorfails_++;
LOG_ERROR("Unknown dallas sensor %s", Sensor(addr).id().c_str());
LOG_ERROR("Unknown sensor %s", Sensor(addr).id().c_str());
break;
}
} else {
sensorfails_++;
LOG_ERROR("Invalid dallas sensor %s", Sensor(addr).id().c_str());
LOG_ERROR("Invalid sensor %s", Sensor(addr).id().c_str());
}
} else {
if (!parasite_) {
@@ -211,10 +211,10 @@ void DallasSensor::loop() {
scancnt_ = 0;
} else if (scancnt_ == SCAN_START + 1) { // startup
firstscan_ = sensors_.size();
// LOG_DEBUG("Adding %d dallas sensor(s) from first scan", firstscan_);
// LOG_DEBUG("Adding %d 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
sensors_.clear(); // restart scanning and clear to get correct numbering
}
state_ = State::IDLE;
}
@@ -223,7 +223,7 @@ void DallasSensor::loop() {
#endif
}
bool DallasSensor::temperature_convert_complete() {
bool TemperatureSensor::temperature_convert_complete() {
#ifndef EMSESP_STANDALONE
if (parasite_) {
return true; // don't care, use the minimum time in loop
@@ -234,7 +234,7 @@ bool DallasSensor::temperature_convert_complete() {
#endif
}
int16_t DallasSensor::get_temperature_c(const uint8_t addr[]) {
int16_t TemperatureSensor::get_temperature_c(const uint8_t addr[]) {
#ifndef EMSESP_STANDALONE
if (!bus_.reset()) {
LOG_ERROR("Bus reset failed before reading scratchpad from %s", Sensor(addr).id().c_str());
@@ -297,8 +297,8 @@ int16_t DallasSensor::get_temperature_c(const uint8_t addr[]) {
#endif
}
// update dallas information name and offset
bool DallasSensor::update(const std::string & id, const std::string & name, int16_t offset) {
// update temperature sensor information name and offset
bool TemperatureSensor::update(const std::string & id, const std::string & name, int16_t offset) {
// find the sensor
for (auto & sensor : sensors_) {
if (sensor.id() == id) {
@@ -346,7 +346,7 @@ bool DallasSensor::update(const std::string & id, const std::string & name, int1
}
// check to see if values have been updated
bool DallasSensor::updated_values() {
bool TemperatureSensor::updated_values() {
if (changed_) {
changed_ = false;
return true;
@@ -355,13 +355,13 @@ bool DallasSensor::updated_values() {
}
// list commands
bool DallasSensor::command_commands(const char * value, const int8_t id, JsonObject & output) {
return Command::list(EMSdevice::DeviceType::DALLASSENSOR, output);
bool TemperatureSensor::command_commands(const char * value, const int8_t id, JsonObject & output) {
return Command::list(EMSdevice::DeviceType::TEMPERATURESENSOR, output);
}
// creates JSON doc from values
// returns false if there are no sensors
bool DallasSensor::command_info(const char * value, const int8_t id, JsonObject & output) {
bool TemperatureSensor::command_info(const char * value, const int8_t id, JsonObject & output) {
if (sensors_.empty()) {
return false;
}
@@ -387,7 +387,7 @@ bool DallasSensor::command_info(const char * value, const int8_t id, JsonObject
}
// 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) {
bool TemperatureSensor::get_value_info(JsonObject & output, const char * cmd, const int8_t id) {
if (sensors_.empty()) {
return false;
}
@@ -404,7 +404,7 @@ bool DallasSensor::get_value_info(JsonObject & output, const char * cmd, const i
}
for (const auto & sensor : sensors_) {
if (strcmp(command_s, sensor.name().c_str()) == 0 || strcmp(command_s, sensor.id().c_str()) == 0) {
if (Helpers::toLower(command_s) == Helpers::toLower(sensor.name().c_str()) || Helpers::toLower(command_s) == Helpers::toLower(sensor.id().c_str())) {
output["id"] = sensor.id();
output["name"] = sensor.name();
char val[10];
@@ -438,37 +438,39 @@ bool DallasSensor::get_value_info(JsonObject & output, const char * cmd, const i
}
// publish a single sensor to MQTT
void DallasSensor::publish_sensor(const Sensor & sensor) {
if (Mqtt::publish_single()) {
void TemperatureSensor::publish_sensor(const Sensor & sensor) {
if (Mqtt::enabled() && Mqtt::publish_single()) {
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
if (Mqtt::publish_single2cmd()) {
snprintf(topic, sizeof(topic), "%s/%s", (F_(dallassensor)), sensor.name().c_str());
snprintf(topic, sizeof(topic), "%s/%s", (F_(temperaturesensor)), sensor.name().c_str());
} else {
snprintf(topic, sizeof(topic), "%s%s/%s", (F_(dallassensor)), "_data", sensor.name().c_str());
snprintf(topic, sizeof(topic), "%s%s/%s", (F_(temperaturesensor)), "_data", sensor.name().c_str());
}
char payload[10];
Mqtt::publish(topic, Helpers::render_value(payload, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0));
Mqtt::queue_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) {
void TemperatureSensor::remove_ha_topic(const std::string & id) {
if (!Mqtt::ha_enabled()) {
return;
}
#ifdef EMSESP_DEBUG
LOG_DEBUG("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::basename().c_str(), sensorid.c_str());
Mqtt::publish_ha(topic);
snprintf(topic, sizeof(topic), "sensor/%s/temperaturesensor_%s/config", Mqtt::basename().c_str(), sensorid.c_str());
Mqtt::queue_remove_topic(topic);
}
// send all dallas sensor values as a JSON package to MQTT
void DallasSensor::publish_values(const bool force) {
// send all temperature sensor values as a JSON package to MQTT
void TemperatureSensor::publish_values(const bool force) {
if (!Mqtt::enabled()) {
return;
}
uint8_t num_sensors = sensors_.size();
if (num_sensors == 0) {
@@ -497,7 +499,7 @@ void DallasSensor::publish_values(const bool force) {
}
// create the HA MQTT config
// to e.g. homeassistant/sensor/ems-esp/dallassensor_28-233D-9497-0C03/config
// to e.g. homeassistant/sensor/ems-esp/temperaturesensor_28-233D-9497-0C03/config
if (Mqtt::ha_enabled()) {
if (!has_value && sensor.ha_registered) {
remove_ha_topic(sensor.id());
@@ -509,31 +511,31 @@ void DallasSensor::publish_values(const bool force) {
config["dev_cla"] = "temperature";
char stat_t[50];
snprintf(stat_t, sizeof(stat_t), "%s/dallassensor_data", Mqtt::base().c_str()); // use base path
snprintf(stat_t, sizeof(stat_t), "%s/temperaturesensor_data", Mqtt::basename().c_str());
config["stat_t"] = stat_t;
config["unit_of_meas"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES);
char val_obj[50];
char val_cond[65];
char val_cond[95];
if (Mqtt::is_nested()) {
snprintf(val_obj, sizeof(val_obj), "value_json['%s'].temp", sensor.id().c_str());
snprintf(val_cond, sizeof(val_cond), "value_json['%s'] is defined", sensor.id().c_str());
snprintf(val_cond, sizeof(val_cond), "value_json['%s'] is defined and %s is defined", sensor.id().c_str(), val_obj);
} else {
snprintf(val_obj, sizeof(val_obj), "value_json['%s']", sensor.name().c_str());
snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj);
}
config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + "}}";
config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + " else -55}}";
char uniq_s[70];
if (Mqtt::entity_format() == 2) {
snprintf(uniq_s, sizeof(uniq_s), "%s_dallassensor_%s", Mqtt::basename().c_str(), sensor.id().c_str());
if (Mqtt::entity_format() == Mqtt::entityFormat::MULTI_SHORT) {
snprintf(uniq_s, sizeof(uniq_s), "%s_temperaturesensor_%s", Mqtt::basename().c_str(), sensor.id().c_str());
} else {
snprintf(uniq_s, sizeof(uniq_s), "dallassensor_%s", sensor.id().c_str());
snprintf(uniq_s, sizeof(uniq_s), "temperaturesensor_%s", sensor.id().c_str());
}
config["obj_id"] = uniq_s;
config["uniq_id"] = uniq_s; // same as object_id
config["obj_id"] = uniq_s;
config["uniq_id"] = uniq_s; // same as object_id/obj_id
char name[50];
snprintf(name, sizeof(name), "%s", sensor.name().c_str());
@@ -541,7 +543,7 @@ void DallasSensor::publish_values(const bool force) {
JsonObject dev = config.createNestedObject("dev");
JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp");
ids.add(Mqtt::basename());
// add "availability" section
Mqtt::add_avty_to_doc(stat_t, config.as<JsonObject>(), val_cond);
@@ -551,21 +553,19 @@ void DallasSensor::publish_values(const bool force) {
std::string sensorid = sensor.id();
std::replace(sensorid.begin(), sensorid.end(), '-', '_');
snprintf(topic, sizeof(topic), "sensor/%s/dallassensor_%s/config", Mqtt::basename().c_str(), sensorid.c_str());
snprintf(topic, sizeof(topic), "sensor/%s/temperaturesensor_%s/config", Mqtt::basename().c_str(), sensorid.c_str());
Mqtt::publish_ha(topic, config.as<JsonObject>());
sensor.ha_registered = true;
sensor.ha_registered = Mqtt::queue_ha(topic, config.as<JsonObject>());
}
}
}
Mqtt::publish("dallassensor_data", doc.as<JsonObject>());
Mqtt::queue_publish("temperaturesensor_data", doc.as<JsonObject>());
}
// skip crc from id
DallasSensor::Sensor::Sensor(const uint8_t addr[])
TemperatureSensor::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
@@ -582,14 +582,14 @@ DallasSensor::Sensor::Sensor(const uint8_t addr[])
offset_ = 0; // 0 degrees offset
}
uint64_t DallasSensor::get_id(const uint8_t addr[]) {
uint64_t TemperatureSensor::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 {
std::string TemperatureSensor::Sensor::name() const {
if (name_.empty()) {
return id_;
}
@@ -598,14 +598,12 @@ std::string DallasSensor::Sensor::name() const {
// 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() {
bool TemperatureSensor::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("Loading customization for dallas sensor %s", sensor.id.c_str());
#endif
LOG_DEBUG("Loading customization for temperature sensor %s", sensor.id.c_str());
if (id_ == sensor.id) {
set_name(sensor.name);
set_offset(sensor.offset);
@@ -620,9 +618,9 @@ bool DallasSensor::Sensor::apply_customization() {
}
// hard coded tests
#ifdef EMSESP_DEBUG
void DallasSensor::test() {
// add 2 dallas sensors
#if defined(EMSESP_TEST)
void TemperatureSensor::test() {
// add 2 temperature sensors
uint8_t addr[ADDR_LEN] = {1, 2, 3, 4, 5, 6, 7, 8};
sensors_.emplace_back(addr);
sensors_.back().temperature_c = 123;

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -18,8 +18,8 @@
// code originally written by nomis - https://github.com/nomis
#ifndef EMSESP_DALLASSENSOR_H
#define EMSESP_DALLASSENSOR_H
#ifndef EMSESP_TEMPERATURESENSOR_H
#define EMSESP_TEMPERATURESENSOR_H
#include "helpers.h"
#include "mqtt.h"
@@ -33,7 +33,7 @@
namespace emsesp {
class DallasSensor {
class TemperatureSensor {
public:
class Sensor {
public:
@@ -73,8 +73,8 @@ class DallasSensor {
int16_t offset_;
};
DallasSensor() = default;
~DallasSensor() = default;
TemperatureSensor() = default;
~TemperatureSensor() = default;
void start();
void loop();
@@ -97,7 +97,7 @@ class DallasSensor {
return sensorfails_;
}
bool dallas_enabled() {
bool sensor_enabled() {
return (dallas_gpio_ != 0);
}
@@ -111,7 +111,7 @@ class DallasSensor {
bool update(const std::string & id, const std::string & name, int16_t offset);
#ifdef EMSESP_DEBUG
#if defined(EMSESP_TEST)
void test();
#endif
@@ -128,11 +128,11 @@ class DallasSensor {
static constexpr size_t SCRATCHPAD_CONFIG = 4;
static constexpr size_t SCRATCHPAD_CNT_REM = 6;
// dallas chips
// dallas chip types
static constexpr uint8_t TYPE_DS18B20 = 0x28;
static constexpr uint8_t TYPE_DS18S20 = 0x10;
static constexpr uint8_t TYPE_DS1822 = 0x22;
static constexpr uint8_t TYPE_DS1825 = 0x3B; // also DS1826
static constexpr uint8_t TYPE_DS1825 = 0x3B; // also DS1826
static constexpr uint32_t READ_INTERVAL_MS = 5000; // 5 seconds
static constexpr uint32_t CONVERSION_MS = 1000; // 1 seconds

View File

@@ -1,7 +1,7 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -17,18 +17,33 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#if defined(EMSESP_DEBUG)
#if defined(EMSESP_STANDALONE) || defined(EMSESP_TEST)
#include "test.h"
namespace emsesp {
// no shell
// no shell, called via the API or 'call system test' command
// or http://ems-esp/api?device=system&cmd=test&data=boiler
bool Test::run_test(const char * command, int8_t id) {
if ((command == nullptr) || (strlen(command) == 0)) {
return false;
}
if (strcmp(command, "memory") == 0) {
EMSESP::logger().notice("Testing memory by adding lots of devices and entities...");
System::test_set_all_active(true); // include all entities and give them fake values
// simulate HansRemmerswaal's setup - see https://github.com/emsesp/EMS-ESP32/issues/859
add_device(0x08, 172); // 176 entities - boiler: Enviline/Compress 6000AW/Hybrid 3000-7000iAW/SupraEco/Geo 5xx/WLW196i
// add_device(0x10, 158); // 62 entities - thermostat: RC300/RC310/Moduline 3000/1010H/CW400/Sense II/HPC410
// add_device(0x38, 200); // 4 entities - thermostat: RC100H
return true;
}
if (strcmp(command, "general") == 0) {
EMSESP::logger().info("Testing general. Adding a Boiler and Thermostat");
@@ -53,7 +68,11 @@ bool Test::run_test(const char * command, int8_t id) {
return true;
}
#ifndef EMSESP_DEBUG_LIMITED
//
// the tests take a lot of memory when built for the ESP32
// so only including the full set in standalone, otherwise a limited selection of basic tests
//
#ifdef EMSESP_STANDALONE
if (strcmp(command, "2thermostats") == 0) {
EMSESP::logger().info("Testing with multiple thermostats...");
@@ -223,41 +242,79 @@ bool Test::run_test(const char * command, int8_t id) {
return false;
}
// These next tests are run from the Console
// using the test command
// These next tests are run from the Consol via the test command, so inherit the Shell
void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const std::string & data) {
// switch to su
shell.add_flags(CommandFlags::ADMIN);
shell.add_flags(CommandFlags::ADMIN); // switch to su
// init stuff
Mqtt::ha_enabled(true);
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
EMSESP::watch(EMSESP::Watch::WATCH_RAW); // raw
// EMSESP::watch(EMSESP::Watch::WATCH_RAW); // raw mode
EMSESP::watch(EMSESP::Watch::WATCH_ON); // verbose mode
std::string command(20, '\0');
#ifndef EMSESP_DEBUG_LIMITED
if ((cmd.empty()) || (cmd == "default")) {
command = EMSESP_DEBUG_DEFAULT;
} else {
command = cmd;
}
#if defined(EMSESP_STANDALONE_DUMP)
if (command == "dump") {
shell.printfln("Adding all devices and entities...");
EMSESP::dump_all_values(shell);
}
#endif
bool ok = false;
if (command == "general") {
shell.printfln("Testing adding a general boiler & thermostat...");
run_test("general");
shell.invoke_command("show devices");
shell.invoke_command("show");
// shell.invoke_command("show devices");
// shell.invoke_command("show values");
shell.invoke_command("call system publish");
shell.invoke_command("show mqtt");
// shell.invoke_command("show mqtt");
ok = true;
}
// https://github.com/emsesp/EMS-ESP32/issues/869
if (command == "memory") {
shell.printfln("Testing memory by adding lots of devices and entities...");
run_test("memory");
shell.invoke_command("show values");
ok = true;
}
if (command == "string2minutes") {
shell.printfln("Testing string2minutes()...");
std::string time_s = "12:00";
shell.printfln("Testing %s is %d", time_s.c_str(), Helpers::string2minutes(time_s));
std::string time_s2 = "12:12";
shell.printfln("Testing %s is %d", time_s2.c_str(), Helpers::string2minutes(time_s2));
std::string time_s3 = "00:50";
shell.printfln("Testing %s is %d", time_s3.c_str(), Helpers::string2minutes(time_s3));
std::string time_s4 = "03:49";
shell.printfln("Testing %s is %d", time_s4.c_str(), Helpers::string2minutes(time_s4));
ok = true;
}
// THESE ONLY WORK WITH AN ESP32, not in standalone mode
#ifndef EMSESP_STANDALONE
if (command == "ls") {
listDir(LittleFS, "/", 3);
Serial.println();
ok = true;
}
#endif
//
// the tests take a lot of memory when built for the ESP32
// so only including the full set in standalone, otherwise a limited selection of basic tests
//
#ifdef EMSESP_STANDALONE
// all tests with EMSESP_STANDALONE
if (command == "entity_dump") {
shell.printfln("Adding all devices and entities...");
System::test_set_all_active(true);
EMSESP::dump_all_values(shell);
ok = true;
}
if (command == "modes") {
@@ -266,6 +323,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
shell.invoke_command("call thermostat mode auto");
shell.invoke_command("call thermostat mode Manuell"); // DE
shell.invoke_command("call thermostat mode 1");
ok = true;
}
if (command == "render") {
@@ -334,6 +392,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
temp = 0x63;
doub = Helpers::transformNumFloat(temp, 2); // divide by 2
shell.printfln("Round test div2 from x%02X to %d to %f", temp, temp, doub);
ok = true;
}
if (command == "devices") {
@@ -341,6 +400,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
// A fake response - UBADevices(0x07)
rx_telegram({0x08, 0x00, 0x07, 0x00, 0x0B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
ok = true;
}
// check for boiler and controller on same product_id
@@ -352,6 +412,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
// UBAuptime
uart_telegram({0x08, 0x0B, 0x14, 00, 0x3C, 0x1F, 0xAC, 0x70});
ok = true;
}
if (command == "620") {
@@ -362,6 +423,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
// Version Boiler
uart_telegram({0x08, 0x0B, 0x02, 0x00, 0x5F, 0x06, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
ok = true;
}
// unknown device
@@ -380,6 +442,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
shell.invoke_command("show devices");
shell.invoke_command("call system report");
ok = true;
}
if (command == "unknown2") {
@@ -387,11 +450,13 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
// simulate getting version information back from an unknown device
rx_telegram({0x09, 0x0B, 0x02, 0x00, 0x5A, 0x01, 0x02}); // productID is 90 which doesn't exist
ok = true;
}
if (command == "gateway") {
shell.printfln("Testing Gateway...");
run_test("gateway");
ok = true;
}
if (command == "310") {
@@ -401,6 +466,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
shell.invoke_command("show");
shell.invoke_command("call system publish");
shell.invoke_command("show mqtt");
ok = true;
}
if (command == "2thermostats") {
@@ -408,6 +474,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
run_test("2thermostats");
shell.invoke_command("show");
shell.invoke_command("show devices");
ok = true;
}
if (command == "web") {
@@ -419,8 +486,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
run_test("boiler");
run_test("thermostat");
#if defined(EMSESP_STANDALONE)
DynamicJsonDocument doc(8000); // some absurd high number
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice) {
@@ -455,8 +520,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
Serial.println(" **");
}
}
#endif
return;
ok = true;
}
if (command == "board_profile") {
@@ -466,6 +530,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
shell.invoke_command("set board_profile wemos");
shell.invoke_command("exit");
shell.invoke_command("call system settings");
ok = true;
}
if (command == "boiler") {
@@ -494,6 +559,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
EMSESP::mqtt_.incoming("ems-esp/boiler", "{\"cmd\":\"heatingactivated\",\"data\":1}");
shell.invoke_command("show mqtt");
ok = true;
}
if (command == "shower_alert") {
@@ -503,6 +569,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
// device type, command, data
Command::call(EMSdevice::DeviceType::BOILER, "wwtapactivated", "false");
ok = true;
}
if (command == "fr120") {
@@ -515,6 +582,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
shell.invoke_command("show");
shell.invoke_command("show devices");
ok = true;
}
if (command == "ha") {
@@ -537,6 +605,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
// shell.invoke_command("call thermostat seltemp"); // sensor.thermostat_hc1_selected_room_temperature
// shell.invoke_command("call thermostat entities");
// shell.invoke_command("call boiler entities");
ok = true;
}
if (command == "lastcode") {
@@ -544,7 +613,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
Mqtt::ha_enabled(false);
Mqtt::nested_format(1);
Mqtt::send_response(false);
// Mqtt::send_response(false);
run_test("boiler");
// run_test("thermostat");
@@ -555,6 +624,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
{0x08, 0x0B, 0xC2, 0, 0x08, 0xAC, 00, 0x10, 0x31, 0x48, 0x30, 0x31, 0x15, 0x80, 0x95, 0x0B, 0x0E, 0x10, 0x38, 00, 0x7F, 0xFF, 0xFF, 0xFF});
// shell.invoke_command("show");
ok = true;
}
if (command == "dv") {
@@ -562,34 +633,37 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
Mqtt::ha_enabled(true);
Mqtt::nested_format(1);
Mqtt::send_response(false);
// Mqtt::send_response(false);
run_test("boiler");
run_test("thermostat");
shell.invoke_command("call boiler wwseltemp");
shell.invoke_command("call system publish");
ok = true;
}
if (command == "dallas") {
shell.printfln("Testing adding Dallas sensor");
emsesp::EMSESP::dallassensor_.test();
if (command == "temperature") {
shell.printfln("Testing adding Temperature sensor");
emsesp::EMSESP::temperaturesensor_.test();
ok = true;
}
if (command == "dallas_full") {
shell.printfln("Testing adding and changing Dallas sensor");
if (command == "temperature_full") {
shell.printfln("Testing adding and changing Temperature sensor");
Mqtt::ha_enabled(true);
Mqtt::nested_format(1);
// Mqtt::nested_format(0);
emsesp::EMSESP::dallassensor_.test();
emsesp::EMSESP::temperaturesensor_.test();
shell.invoke_command("show");
shell.invoke_command("call system publish");
// rename
EMSESP::dallassensor_.update("01-0203-0405-0607", "testdallas", 2);
EMSESP::temperaturesensor_.update("01-0203-0405-0607", "testtemperature", 2);
shell.invoke_command("show");
shell.invoke_command("call system publish");
ok = true;
}
if (command == "analog") {
@@ -609,6 +683,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
EMSESP::analogsensor_.update(36, "analogtest", 2, 0.7, 17, 1);
shell.invoke_command("show");
// shell.invoke_command("call system publish");
ok = true;
}
if (command == "healthcheck") {
@@ -621,13 +696,14 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
// n=2 = EMSESP::system_.HEALTHCHECK_NO_NETWORK
shell.printfln("Testing healthcheck with %d", n);
EMSESP::system_.healthcheck(n);
ok = true;
}
if (command == "custom") {
shell.printfln(F("Testing custom entities"));
shell.printfln("Testing custom entities");
Mqtt::ha_enabled(true);
Mqtt::send_response(false);
// Mqtt::send_response(false);
run_test("thermostat");
@@ -650,14 +726,15 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
shell.invoke_command("call thermostat seltemp");
shell.invoke_command("call system publish");
}
ok = true;
}
if (command == "masked") {
shell.printfln("Testing masked entities");
Mqtt::ha_enabled(true);
Mqtt::send_response(false);
// Mqtt::send_response(false);
run_test("boiler");
@@ -675,13 +752,14 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
shell.invoke_command("call boiler wwseltemp");
shell.invoke_command("call system publish");
ok = true;
}
if (command == "dv2") {
shell.printfln("Testing device value lost");
Mqtt::ha_enabled(true);
Mqtt::send_response(false);
// Mqtt::send_response(false);
run_test("boiler");
@@ -695,14 +773,14 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
shell.invoke_command("call boiler wwseltemp");
shell.invoke_command("call system publish");
ok = true;
}
if (command == "api_values") {
#if defined(EMSESP_STANDALONE)
shell.printfln("Testing API getting values");
Mqtt::ha_enabled(false);
Mqtt::nested_format(1);
Mqtt::send_response(false);
// 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
@@ -728,14 +806,14 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
EMSESP::webAPIService.webAPIService_get(&request);
request.url("/api/boiler/flamecurr/bad");
EMSESP::webAPIService.webAPIService_get(&request);
#endif
ok = true;
}
if (command == "mqtt_post") {
shell.printfln("Testing MQTT incoming changes");
Mqtt::ha_enabled(false);
Mqtt::nested_format(1);
Mqtt::send_response(false);
// 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
@@ -743,9 +821,9 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
run_test("thermostat");
EMSESP::mqtt_.incoming("ems-esp/boiler/wwseltemp", "59");
ok = true;
}
#if defined(EMSESP_STANDALONE)
// https://github.com/emsesp/EMS-ESP32/issues/541
if (command == "api_wwmode") {
shell.printfln("Testing API wwmode");
@@ -763,8 +841,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
json = doc.as<JsonVariant>();
request.url("/api/thermostat/wwmode");
EMSESP::webAPIService.webAPIService_post(&request, json);
ok = true;
}
#endif
if (command == "api") {
shell.printfln("Testing API with MQTT and REST, standalone");
@@ -773,13 +851,11 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
// Mqtt::ha_enabled(false);
Mqtt::nested_format(1);
Mqtt::send_response(true);
// Mqtt::send_response(true);
run_test("boiler");
run_test("thermostat");
#if defined(EMSESP_STANDALONE)
AsyncWebServerRequest requestX;
DynamicJsonDocument docX(2000);
JsonVariant jsonX;
@@ -799,7 +875,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
*/
/*
requestX.url("/api/thermostat/mode/auto");
requestX.url("/api/thermostat/mode/auto");
EMSESP::webAPIService.webAPIService_get(&requestX);
return;
*/
@@ -847,10 +923,10 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
*/
/*
requestX.url("/api/dallassensor/xxxx");
requestX.url("/api/temperaturesensor/xxxx");
EMSESP::webAPIService.webAPIService_get(&requestX);
emsesp::EMSESP::logger().notice("****");
requestX.url("/api/dallassensor/info");
requestX.url("/api/temperaturesensor/info");
EMSESP::webAPIService.webAPIService_get(&requestX);
return;
*/
@@ -871,7 +947,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
return;
*/
/*
// char dataX[] = "{\"value\":\"0B 88 19 19 02\"}";
char dataX[] = "{\"name\":\"temp\",\"value\":11}";
@@ -917,8 +992,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
ncmd = Command::parse_command_string(command_s, id_n);
shell.printfln("test cmd parse cmd=%s id=%d", ncmd, id_n);
#endif
// Console tests
shell.invoke_command("call thermostat entities");
shell.invoke_command("call thermostat mode auto");
@@ -928,14 +1001,14 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
EMSESP::mqtt_.incoming("ems-esp/thermostat/hc2/mode", "auto");
EMSESP::mqtt_.incoming("ems-esp/thermostat/wwc3/mode", "auto");
EMSESP::mqtt_.incoming("ems-esp/boiler/wwcircpump", "off");
EMSESP::mqtt_.incoming("ems-esp/thermostat/seltemp"); // empty payload, sends reponse
EMSESP::mqtt_.incoming("ems-esp/thermostat/seltemp"); // empty payload
EMSESP::mqtt_.incoming("ems-esp/thermostat_hc1", "22"); // HA only
EMSESP::mqtt_.incoming("ems-esp/thermostat_hc1", "off"); // HA only
EMSESP::mqtt_.incoming("ems-esp/system/send", "11 12 13");
EMSESP::mqtt_.incoming("ems-esp/boiler/syspress"); // empty payload, sends reponse
EMSESP::mqtt_.incoming("ems-esp/thermostat/mode"); // empty payload, sends reponse
EMSESP::mqtt_.incoming("ems-esp/boiler/syspress"); // empty payload
EMSESP::mqtt_.incoming("ems-esp/thermostat/mode"); // empty payload
EMSESP::mqtt_.incoming("ems-esp/system/publish");
EMSESP::mqtt_.incoming("ems-esp/thermostat/seltemp"); // empty payload, sends reponse
EMSESP::mqtt_.incoming("ems-esp/thermostat/seltemp"); // empty payload
EMSESP::mqtt_.incoming("ems-esp/boiler/wwseltemp", "59");
EMSESP::mqtt_.incoming("ems-esp/boiler/wwseltemp");
@@ -949,9 +1022,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
// check extended MQTT base
Mqtt::base("home/cellar/heating");
EMSESP::mqtt_.incoming("home/cellar/heating/thermostat/mode"); // empty payload, sends reponse
EMSESP::mqtt_.incoming("home/cellar/heating/thermostat/mode"); // empty payload
#if defined(EMSESP_STANDALONE)
// Web API TESTS
AsyncWebServerRequest request;
@@ -1019,15 +1091,15 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
char data6[] = "{\"id\":2,\"devicevalue\":{\"v\":\"44\",\"u\":1,\"n\":\"hc2 selected room temperature\",\"c\":\"hc2/seltemp\"}";
deserializeJson(doc, data6);
json = doc.as<JsonVariant>();
request.url("/rest/writeValue");
EMSESP::webDataService.write_value(&request, json);
request.url("/rest/writeDeviceValue");
EMSESP::webDataService.write_device_value(&request, json);
// write value from web - testing hc9/seltemp - should fail!
char data7[] = "{\"id\":2,\"devicevalue\":{\"v\":\"55\",\"u\":1,\"n\":\"hc2 selected room temperature\",\"c\":\"hc9/seltemp\"}";
deserializeJson(doc, data7);
json = doc.as<JsonVariant>();
request.url("/rest/writeValue");
EMSESP::webDataService.write_value(&request, json);
request.url("/rest/writeDeviceValue");
EMSESP::webDataService.write_device_value(&request, json);
// emsesp::EMSESP::logger().notice("*");
@@ -1037,8 +1109,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
json = doc.as<JsonVariant>();
request.url("/api/thermostat/mode/auto");
EMSESP::webAPIService.webAPIService_post(&request, json);
#endif
ok = true;
}
if (command == "mqtt_nested") {
@@ -1059,6 +1130,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
Mqtt::nested_format(2);
shell.invoke_command("call system publish");
shell.invoke_command("show mqtt");
ok = true;
}
if (command == "thermostat") {
@@ -1075,6 +1147,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
EMSESP::mqtt_.incoming("ems-esp/thermostat_hc3", "{\"cmd\":\"temp\",\"data\":-3}");
shell.invoke_command("show mqtt");
ok = true;
}
if (command == "tc100") {
@@ -1086,6 +1159,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
// 0x0A
uart_telegram({0x98, 0x0B, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
ok = true;
}
if (command == "solar") {
@@ -1099,6 +1173,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
shell.invoke_command("call system publish");
// EMSESP::txservice_.send_raw("B0 00 FF 18 02 62 80 00 B8");
ok = true;
}
if (command == "heatpump") {
@@ -1106,6 +1181,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
run_test("heatpump");
shell.invoke_command("call");
shell.invoke_command("call heatpump info");
ok = true;
}
if (command == "solar200") {
@@ -1128,9 +1204,10 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
uart_telegram("30 00 FF 0A 02 6A 04"); // SM100 pump on 1
uart_telegram("30 00 FF 00 02 64 00 00 00 04 00 00 FF 00 00 1E 0B 09 64 00 00 00 00"); // SM100 modulation
uart_telegram("30 00 FF 0A 02 6A 03"); // SM100 pump off 0
uart_telegram("30 00 FF 0A 02 6A 03"); // SM100 pump off 0
shell.invoke_command("show");
ok = true;
}
if (command == "km") {
@@ -1189,6 +1266,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
EMSESP::show_ems(shell);
EMSESP::show_device_values(shell);
ok = true;
}
if (command == "cr100") {
@@ -1214,12 +1292,14 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
shell.loop_all();
EMSESP::txservice_.send(); // send it to UART
ok = true;
}
if (command == "rx2") {
shell.printfln("Testing Rx2...");
for (uint8_t i = 0; i < 30; i++) {
uart_telegram({0x08, 0x0B, 0x33, 0x00, 0x08, 0xFF, 0x34, 0xFB, 0x00, 0x28, 0x00, 0x00, 0x46, 0x00, 0xFF, 0xFF, 0x00});
ok = true;
}
}
@@ -1278,6 +1358,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
// test 0x2A - DHWStatus3
uart_telegram({0x88, 00, 0x2A, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0xD2, 00, 00, 0x80, 00, 00, 01, 0x9D, 0x80, 0x00, 0x02, 0x79, 00});
ok = true;
}
if (command == "tx") {
@@ -1315,6 +1396,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
for (uint8_t i = 0; i < 10; i++) {
EMSESP::txservice_.send(); // send it to UART
}
ok = true;
}
if (command == "poll") {
@@ -1340,6 +1423,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
uint8_t t2[] = {0x21, 0x22};
EMSESP::send_write_request(0x91, 0x17, 0x00, t2, sizeof(t2), 0);
EMSESP::show_ems(shell);
ok = true;
}
if (command == "cmd") {
@@ -1361,7 +1446,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
shell.invoke_command("call");
shell.invoke_command("call system info");
EMSESP::mqtt_.incoming("ems-esp/system", "{\"cmd\":\"info\"}"); // this should fail
EMSESP::mqtt_.incoming("ems-esp/system", "{\"cmd\":\"info\"}"); // this should fail
EMSESP::mqtt_.incoming("ems-esp/thermostat", "{\"cmd\":\"temp\",\"data\":23.45}"); // this should work just fine
EMSESP::mqtt_.incoming("ems-esp/thermostat", "{\"cmd\":\"TeMP\",\"data\":23.45}"); // test mix cased cmd
@@ -1371,18 +1456,20 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
shell.invoke_command("call thermostat wwmode"); // should do nothing
shell.invoke_command("call thermostat mode auto 2"); // should error, no hc2
shell.invoke_command("call thermostat temp 22.56");
ok = true;
}
if (command == "pin") {
shell.printfln("Testing pin...");
shell.invoke_command("call system pin");
shell.invoke_command("call system pin 1 true");
ok = true;
}
if (command == "mqtt2") {
shell.printfln("Testing MQTT large payloads...");
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XXLARGE_DYN);
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XXXLARGE);
char key[8];
char value[8];
@@ -1398,8 +1485,9 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
shell.printfln("Size of JSON payload = %d", jo.memoryUsage());
shell.printfln("Length of JSON payload = %d", measureJson(jo));
Mqtt::publish("test", jo);
Mqtt::queue_publish("test", jo);
Mqtt::show_mqtt(shell); // show queue
ok = true;
}
if (command == "mqtt") {
@@ -1448,7 +1536,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
strlcpy(system_topic, "ems-esp/system", sizeof(system_topic));
// test publishing
EMSESP::mqtt_.publish(boiler_topic, "test me");
EMSESP::mqtt_.queue_publish(boiler_topic, "test me");
// test receiving
EMSESP::mqtt_.incoming(boiler_topic, ""); // test if ignore empty payloads, should return values
@@ -1471,7 +1559,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
// EMSESP::mqtt_.incoming(system_topic, "{\"cmd\":\"pin\",\"id\":12,\"data\":\"1\"}");
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"wwmode\",\"data\":\"auto\"}");
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"mode\",\"data\":\"typo\",\"id\":2}"); // invalid mode
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"mode\",\"data\":\"typo\",\"id\":2}"); // invalid mode
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"mode\",\"data\":\"auto\",\"id\":2}");
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"mode\",\"data\":\"auto\",\"hc\":2}"); // hc as number
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"seltemp\",\"data\":19.5,\"hc\":1}"); // data as number
@@ -1494,6 +1582,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
Mqtt::resubscribe();
Mqtt::show_mqtt(shell); // show queue
ok = true;
}
if (command == "poll2") {
@@ -1505,6 +1594,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
EMSESP::incoming_telegram(poll, 1);
EMSESP::show_ems(shell);
ok = true;
}
if (command == "rx2") {
@@ -1512,6 +1602,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
uart_telegram({0x1B, 0x5B, 0xFD, 0x2D, 0x9E, 0x3A, 0xB6, 0xE5, 0x02, 0x20, 0x33, 0x30, 0x32, 0x3A, 0x20, 0x5B,
0x73, 0xFF, 0xFF, 0xCB, 0xDF, 0xB7, 0xA7, 0xB5, 0x67, 0x77, 0x77, 0xE4, 0xFF, 0xFD, 0x77, 0xFF});
ok = true;
}
// https://github.com/emsesp/EMS-ESP/issues/380#issuecomment-633663007
@@ -1519,6 +1610,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
shell.printfln("Testing rx3...");
uart_telegram({0x21, 0x0B, 0xFF, 0x00});
ok = true;
}
// testing the UART tx command, without a queue
@@ -1527,6 +1619,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
uint8_t t[] = {0x0B, 0x88, 0x18, 0x00, 0x20, 0xD4}; // including CRC
EMSuart::transmit(t, sizeof(t));
ok = true;
}
// send read request with offset
@@ -1535,6 +1628,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
// send_read_request(0x18, 0x08);
EMSESP::txservice_.read_request(0x18, 0x08, 27); // no offset
ok = true;
}
if (command == "mixer") {
@@ -1550,11 +1644,10 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
shell.invoke_command("call system publish");
shell.invoke_command("show mqtt");
// shell.invoke_command("call mixer wwc1 info");
// shell.invoke_command("call mixer wwc2 info");
// shell.invoke_command("call mixer wwc1 info");
// shell.invoke_command("call mixer wwc2 info");
// test API
#if defined(EMSESP_STANDALONE)
// test API
AsyncWebServerRequest request;
request.url("/api/mixer");
EMSESP::webAPIService.webAPIService_get(&request);
@@ -1562,30 +1655,35 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
EMSESP::webAPIService.webAPIService_get(&request);
request.url("/api/mixer/wwc2/pumpstatus");
EMSESP::webAPIService.webAPIService_get(&request);
#endif
ok = true;
}
if (command == "crash") {
shell.printfln("Forcing a crash...");
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdiv-by-zero"
#pragma GCC diagnostic ignored "-Wunused-variable"
uint8_t a = 2 / 0;
shell.printfln("Testing %s", a);
#pragma GCC diagnostic pop
ok = true;
}
#endif
if (command == "limited") {
shell.printfln("Run a limited memory test...");
run_test("general");
if (!ok) {
shell.printfln("Unknown test command: %s", command.c_str());
EMSESP::logger().notice("Unknown test command: %s", command.c_str());
}
}
// loop console. simulates what EMSESP::loop() does
void Test::refresh() {
uuid::loop();
EMSESP::rxservice_.loop();
EMSESP::mqtt_.loop();
Shell::loop_all();
}
// simulates a telegram in the Rx queue, but without the CRC which is added automatically
void Test::rx_telegram(const std::vector<uint8_t> & rx_data) {
uint8_t len = rx_data.size();
@@ -1598,9 +1696,7 @@ void Test::rx_telegram(const std::vector<uint8_t> & rx_data) {
data[i] = EMSESP::rxservice_.calculate_crc(data, i);
EMSESP::rxservice_.add(data, len + 1);
#if defined(EMSESP_STANDALONE)
EMSESP::loop();
#endif
refresh();
}
// simulates a telegram straight from UART, but without the CRC which is added automatically
@@ -1615,9 +1711,7 @@ void Test::uart_telegram(const std::vector<uint8_t> & rx_data) {
data[i] = EMSESP::rxservice_.calculate_crc(data, i);
EMSESP::incoming_telegram(data, i + 1);
#if defined(EMSESP_STANDALONE)
EMSESP::loop();
#endif
refresh();
}
// takes raw string, assuming it contains the CRC. This is what is output from 'watch raw'
@@ -1656,9 +1750,7 @@ void Test::uart_telegram_withCRC(const char * rx_data) {
EMSESP::incoming_telegram(data, count + 1);
#if defined(EMSESP_STANDALONE)
EMSESP::loop();
#endif
refresh();
}
// takes raw string, adds CRC to end
@@ -1699,17 +1791,14 @@ void Test::uart_telegram(const char * rx_data) {
EMSESP::incoming_telegram(data, count + 2);
#if defined(EMSESP_STANDALONE)
EMSESP::loop();
#endif
refresh();
}
// Sends version telegram. Version is hardcoded to 1.0
void Test::add_device(uint8_t device_id, uint8_t product_id) {
// Send version: 09 0B 02 00 PP V1 V2
uart_telegram({device_id, EMSESP_DEFAULT_EMS_BUS_ID, EMSdevice::EMS_TYPE_VERSION, 0, product_id, 1, 0});
}
#ifdef EMSESP_TEST
#ifndef EMSESP_STANDALONE
void Test::listDir(fs::FS & fs, const char * dirname, uint8_t levels) {
Serial.println();
@@ -1744,24 +1833,7 @@ void Test::listDir(fs::FS & fs, const char * dirname, uint8_t levels) {
}
}
#endif
void Test::debug(uuid::console::Shell & shell, const std::string & cmd) {
// shell.add_flags(CommandFlags::ADMIN); // switch to su
std::string command(20, '\0');
if ((cmd.empty()) || (cmd == "default")) {
command = "ls";
} else {
command = cmd;
}
#ifndef EMSESP_STANDALONE
if (command == "ls") {
listDir(LittleFS, "/", 3);
Serial.println();
}
#endif
}
} // namespace emsesp

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#if defined(EMSESP_DEBUG)
#if defined(EMSESP_STANDALONE) || defined(EMSESP_TEST)
#ifndef EMSESP_TEST_H
#define EMSESP_TEST_H
@@ -28,10 +28,9 @@ namespace emsesp {
// #define EMSESP_DEBUG_DEFAULT "thermostat"
// #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 "general"
// #define EMSESP_DEBUG_DEFAULT "boiler"
// #define EMSESP_DEBUG_DEFAULT "mqtt2"
// #define EMSESP_DEBUG_DEFAULT "mqtt_nested"
@@ -46,13 +45,14 @@ namespace emsesp {
// #define EMSESP_DEBUG_DEFAULT "dv"
// #define EMSESP_DEBUG_DEFAULT "lastcode"
// #define EMSESP_DEBUG_DEFAULT "2thermostats"
// #define EMSESP_DEBUG_DEFAULT "dallas"
// #define EMSESP_DEBUG_DEFAULT "temperature"
// #define EMSESP_DEBUG_DEFAULT "analog"
// #define EMSESP_DEBUG_DEFAULT "api_values"
// #define EMSESP_DEBUG_DEFAULT "mqtt_post"
// #define EMSESP_DEBUG_DEFAULT "api_wwmode"
// #define EMSESP_DEBUG_DEFAULT "custom"
// #define EMSESP_DEBUG_DEFAULT "dump"
// #define EMSESP_DEBUG_DEFAULT "entity_dump"
// #define EMSESP_DEBUG_DEFAULT "memory"
class Test {
public:
@@ -64,10 +64,8 @@ class Test {
static void uart_telegram(const char * rx_data);
static void uart_telegram_withCRC(const char * rx_data);
static void add_device(uint8_t device_id, uint8_t product_id);
static void debug(uuid::console::Shell & shell, const std::string & command);
#ifndef EMSESP_STANDALONE
static void refresh();
static void listDir(fs::FS & fs, const char * dirname, uint8_t levels);
#endif
};
} // namespace emsesp

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -86,10 +86,10 @@ void EMSuart::start(const uint8_t tx_mode, const uint8_t rx_gpio, const uint8_t
uart_set_pin(EMSUART_NUM, tx_gpio, rx_gpio, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
uart_driver_install(EMSUART_NUM, 129, 0, (EMS_MAXBUFFERSIZE + 1) * 2, &uart_queue, 0); // buffer must be > fifo
uart_set_rx_full_threshold(EMSUART_NUM, 1);
uart_set_rx_timeout(EMSUART_NUM, 0); // disable
uart_set_rx_timeout(EMSUART_NUM, 0); // disable
// note setting the static max buffer to 1024 causes OTA to fail
xTaskCreate(uart_event_task, "uart_event_task", 2048, NULL, configMAX_PRIORITIES - 1, NULL);
// note esp32s3 crashes with 2k stacksize, stack overflow here sometimes wipes settingsfiles.
xTaskCreate(uart_event_task, "uart_event_task", 2560, NULL, configMAX_PRIORITIES - 1, NULL);
}
tx_mode_ = tx_mode;
uart_enable_intr_mask(EMSUART_NUM, UART_BRK_DET_INT_ENA | UART_RXFIFO_FULL_INT_ENA);

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -24,7 +24,7 @@
#ifndef EMSESP_EMSUART_H
#define EMSESP_EMSUART_H
#define EMS_MAXBUFFERSIZE 33 // max size of the buffer. EMS packets are max 32 bytes, plus extra for BRK
#define EMS_MAXBUFFERSIZE 33 // max size of the buffer. EMS packets are max 32 bytes, plus extra for BRK
#define EMSUART_NUM UART_NUM_1 // on C3 and S2 there is no UART2, use UART1 for all
#define EMSUART_BAUD 9600 // uart baud rate for the EMS circuit

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.5.1"
#define EMSESP_APP_VERSION "3.6.0"

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -36,6 +36,8 @@ WebAPIService::WebAPIService(AsyncWebServer * server, SecurityManager * security
server->on(GET_CUSTOMIZATIONS_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WebAPIService::getCustomizations, this, _1), AuthenticationPredicates::IS_ADMIN));
server->on(GET_SCHEDULE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WebAPIService::getSchedule, this, _1), AuthenticationPredicates::IS_ADMIN));
server->on(GET_ENTITIES_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WebAPIService::getEntities, this, _1), AuthenticationPredicates::IS_ADMIN));
}
// HTTP GET
@@ -100,8 +102,11 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject & input) {
}
}
// capture current heap memory before allocating the large return buffer
emsesp::EMSESP::system_.refreshHeapMem();
// output json buffer
size_t buffer = EMSESP_JSON_SIZE_XXLARGE_DYN;
size_t buffer = EMSESP_JSON_SIZE_XXXLARGE;
auto * response = new PrettyAsyncJsonResponse(false, buffer);
while (!response->getSize()) {
delete response;
@@ -123,7 +128,6 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject & input) {
emsesp::EMSESP::logger().err(error);
api_fails_++;
} else {
// emsesp::EMSESP::logger().debug("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";
@@ -194,4 +198,28 @@ void WebAPIService::getCustomizations(AsyncWebServerRequest * request) {
request->send(response);
}
void WebAPIService::getSchedule(AsyncWebServerRequest * request) {
auto * response = new AsyncJsonResponse(false, FS_BUFFER_SIZE);
JsonObject root = response->getRoot();
root["type"] = "schedule";
System::extractSettings(EMSESP_SCHEDULER_FILE, "Schedule", root);
response->setLength();
request->send(response);
}
void WebAPIService::getEntities(AsyncWebServerRequest * request) {
auto * response = new AsyncJsonResponse(false, FS_BUFFER_SIZE);
JsonObject root = response->getRoot();
root["type"] = "entities";
System::extractSettings(EMSESP_ENTITY_FILE, "Entities", root);
response->setLength();
request->send(response);
}
} // namespace emsesp

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -22,6 +22,8 @@
#define EMSESP_API_SERVICE_PATH "/api"
#define GET_SETTINGS_PATH "/rest/getSettings"
#define GET_CUSTOMIZATIONS_PATH "/rest/getCustomizations"
#define GET_SCHEDULE_PATH "/rest/getSchedule"
#define GET_ENTITIES_PATH "/rest/getEntities"
namespace emsesp {
@@ -51,6 +53,8 @@ class WebAPIService {
void getSettings(AsyncWebServerRequest * request);
void getCustomizations(AsyncWebServerRequest * request);
void getSchedule(AsyncWebServerRequest * request);
void getEntities(AsyncWebServerRequest * request);
};
} // namespace emsesp

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -33,10 +33,12 @@ WebCustomizationService::WebCustomizationService(AsyncWebServer * server, FS * f
, _fsPersistence(WebCustomization::read, WebCustomization::update, this, fs, EMSESP_CUSTOMIZATION_FILE)
, _masked_entities_handler(CUSTOM_ENTITIES_PATH,
securityManager->wrapCallback(std::bind(&WebCustomizationService::custom_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(DEVICE_ENTITIES_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WebCustomizationService::device_entities, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
server->on(DEVICES_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WebCustomizationService::devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
@@ -49,25 +51,21 @@ WebCustomizationService::WebCustomizationService(AsyncWebServer * server, FS * f
_masked_entities_handler.setMaxContentLength(2048);
_masked_entities_handler.setMaxJsonBufferSize(2048);
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");
// Temperature Sensor customization
JsonArray sensorsJson = root.createNestedArray("ts");
for (const SensorCustomization & sensor : settings.sensorCustomizations) {
JsonObject sensorJson = sensorsJson.createNestedObject();
sensorJson["id"] = sensor.id; // is
sensorJson["id"] = sensor.id; // ID of chip
sensorJson["name"] = sensor.name; // n
sensorJson["offset"] = sensor.offset; // o
}
// Analog Sensor customization
JsonArray analogJson = root.createNestedArray("analogs");
JsonArray analogJson = root.createNestedArray("as");
for (const AnalogCustomization & sensor : settings.analogCustomizations) {
JsonObject sensorJson = analogJson.createNestedObject();
sensorJson["gpio"] = sensor.gpio; // g
@@ -98,7 +96,7 @@ void WebCustomization::read(WebCustomization & settings, JsonObject & root) {
StateUpdateResult WebCustomization::update(JsonObject & root, WebCustomization & settings) {
#ifdef EMSESP_STANDALONE
// invoke some fake data for testing
const char * json = "{\"sensors\":[],\"analogs\":[],\"masked_entities\":[{\"product_id\":123,\"device_id\":8,\"entity_ids\":[\"08heatingactive|my custom "
const char * json = "{\"ts\":[],\"as\":[],\"masked_entities\":[{\"product_id\":123,\"device_id\":8,\"entity_ids\":[\"08heatingactive|my custom "
"name for heating active\",\"08tapwateractive\"]}]}";
StaticJsonDocument<500> doc;
@@ -111,10 +109,10 @@ StateUpdateResult WebCustomization::update(JsonObject & root, WebCustomization &
Serial.println(COLOR_RESET);
#endif
// Dallas Sensor customization
// Temperature Sensor customization
settings.sensorCustomizations.clear();
if (root["sensors"].is<JsonArray>()) {
for (const JsonObject sensorJson : root["sensors"].as<JsonArray>()) {
if (root["ts"].is<JsonArray>()) {
for (const JsonObject sensorJson : root["ts"].as<JsonArray>()) {
// create each of the sensor, overwriting any previous settings
auto sensor = SensorCustomization();
sensor.id = sensorJson["id"].as<std::string>();
@@ -126,8 +124,8 @@ StateUpdateResult WebCustomization::update(JsonObject & root, WebCustomization &
// Analog Sensor customization
settings.analogCustomizations.clear();
if (root["analogs"].is<JsonArray>()) {
for (const JsonObject analogJson : root["analogs"].as<JsonArray>()) {
if (root["as"].is<JsonArray>()) {
for (const JsonObject analogJson : root["as"].as<JsonArray>()) {
// create each of the sensor, overwriting any previous settings
auto sensor = AnalogCustomization();
sensor.gpio = analogJson["gpio"];
@@ -165,20 +163,20 @@ StateUpdateResult WebCustomization::update(JsonObject & root, WebCustomization &
void WebCustomizationService::reset_customization(AsyncWebServerRequest * request) {
#ifndef EMSESP_STANDALONE
if (LittleFS.remove(EMSESP_CUSTOMIZATION_FILE)) {
AsyncWebServerResponse * response = request->beginResponse(200); // OK
AsyncWebServerResponse * response = request->beginResponse(205); // restart needed
request->send(response);
EMSESP::system_.restart_requested(true);
return;
}
// failed
AsyncWebServerResponse * response = request->beginResponse(204); // no content error
AsyncWebServerResponse * response = request->beginResponse(400); // bad request
request->send(response);
#endif
}
// send back a list of devices used in the customization web page
void WebCustomizationService::devices(AsyncWebServerRequest * request) {
auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_LARGE_DYN);
auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_XLARGE);
JsonObject root = response->getRoot();
// list is already sorted by device type
@@ -199,17 +197,21 @@ void WebCustomizationService::devices(AsyncWebServerRequest * request) {
}
// send back list of device entities
void WebCustomizationService::device_entities(AsyncWebServerRequest * request, JsonVariant & json) {
if (json.is<JsonObject>()) {
size_t buffer = EMSESP_JSON_SIZE_XXXLARGE_DYN;
void WebCustomizationService::device_entities(AsyncWebServerRequest * request) {
uint8_t id;
if (request->hasParam(F_(id))) {
id = Helpers::atoint(request->getParam(F_(id))->value().c_str()); // get id from url
size_t buffer = EMSESP_JSON_SIZE_XXXXLARGE;
auto * response = new MsgpackAsyncJsonResponse(true, buffer);
while (!response->getSize()) {
delete response;
buffer -= 1024;
response = new MsgpackAsyncJsonResponse(true, buffer);
}
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice->unique_id() == json["id"]) {
if (emsdevice->unique_id() == id) {
#ifndef EMSESP_STANDALONE
JsonArray output = response->getRoot();
emsdevice->generate_values_web_customization(output);
@@ -319,7 +321,7 @@ void WebCustomizationService::custom_entities(AsyncWebServerRequest * request, J
}
}
AsyncWebServerResponse * response = request->beginResponse(need_reboot ? 201 : 200); // OK
AsyncWebServerResponse * response = request->beginResponse(need_reboot ? 205 : 200); // reboot or just OK
request->send(response);
}
@@ -328,4 +330,4 @@ void WebCustomizationService::begin() {
_fsPersistence.readFromFS();
}
} // namespace emsesp
} // namespace emsesp

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -24,15 +24,15 @@
// GET
#define DEVICES_SERVICE_PATH "/rest/devices"
#define EMSESP_CUSTOMIZATION_SERVICE_PATH "/rest/customization"
#define DEVICE_ENTITIES_PATH "/rest/deviceEntities"
// POST
#define DEVICE_ENTITIES_PATH "/rest/deviceEntities"
#define CUSTOM_ENTITIES_PATH "/rest/customEntities"
#define RESET_CUSTOMIZATION_SERVICE_PATH "/rest/resetCustomizations"
namespace emsesp {
// Customization for dallas sensor
// Customization for temperature sensor
class SensorCustomization {
public:
std::string id;
@@ -91,13 +91,13 @@ class WebCustomizationService : public StatefulService<WebCustomization> {
// GET
void devices(AsyncWebServerRequest * request);
void device_entities(AsyncWebServerRequest * request);
// POST
void custom_entities(AsyncWebServerRequest * request, JsonVariant & json);
void device_entities(AsyncWebServerRequest * request, JsonVariant & json);
void reset_customization(AsyncWebServerRequest * request);
void reset_customization(AsyncWebServerRequest * request); // command
AsyncCallbackJsonWebHandler _masked_entities_handler, _device_entities_handler;
AsyncCallbackJsonWebHandler _masked_entities_handler;
};
} // namespace emsesp

View File

@@ -1,7 +1,7 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
*
* Copyright 2020-2023 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
@@ -23,14 +23,18 @@ namespace emsesp {
using namespace std::placeholders; // for `_1` etc
WebDataService::WebDataService(AsyncWebServer * server, SecurityManager * securityManager)
: _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)) {
: _write_value_handler(WRITE_DEVICE_VALUE_SERVICE_PATH,
securityManager->wrapCallback(std::bind(&WebDataService::write_device_value, this, _1, _2), AuthenticationPredicates::IS_ADMIN))
, _write_temperature_handler(WRITE_TEMPERATURE_SENSOR_SERVICE_PATH,
securityManager->wrapCallback(std::bind(&WebDataService::write_temperature_sensor, this, _1, _2),
AuthenticationPredicates::IS_ADMIN))
, _write_analog_handler(WRITE_ANALOG_SENSOR_SERVICE_PATH,
securityManager->wrapCallback(std::bind(&WebDataService::write_analog_sensor, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) {
// GET's
server->on(DEVICE_DATA_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WebDataService::device_data, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
server->on(CORE_DATA_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WebDataService::core_data, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
@@ -39,21 +43,18 @@ WebDataService::WebDataService(AsyncWebServer * server, SecurityManager * securi
HTTP_GET,
securityManager->wrapRequest(std::bind(&WebDataService::sensor_data, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
// POST's
server->on(SCAN_DEVICES_SERVICE_PATH,
HTTP_POST,
securityManager->wrapRequest(std::bind(&WebDataService::scan_devices, this, _1), AuthenticationPredicates::IS_ADMIN));
_device_data_handler.setMethod(HTTP_POST);
_device_data_handler.setMaxContentLength(256);
server->addHandler(&_device_data_handler);
_write_value_handler.setMethod(HTTP_POST);
_write_value_handler.setMaxContentLength(256);
server->addHandler(&_write_value_handler);
_write_sensor_handler.setMethod(HTTP_POST);
_write_sensor_handler.setMaxContentLength(256);
server->addHandler(&_write_sensor_handler);
_write_temperature_handler.setMethod(HTTP_POST);
_write_temperature_handler.setMaxContentLength(256);
server->addHandler(&_write_temperature_handler);
_write_analog_handler.setMethod(HTTP_POST);
_write_analog_handler.setMaxContentLength(256);
@@ -70,33 +71,40 @@ void WebDataService::scan_devices(AsyncWebServerRequest * request) {
// 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);
auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_XXLARGE);
JsonObject root = response->getRoot();
// list is already sorted by device type
JsonArray devices = root.createNestedArray("devices");
char buffer[3];
for (const auto & emsdevice : EMSESP::emsdevices) {
// ignore controller
if (emsdevice && (emsdevice->device_type() != EMSdevice::DeviceType::CONTROLLER || emsdevice->count_entities() > 0)) {
JsonObject obj = devices.createNestedObject();
obj["id"] = Helpers::smallitoa(buffer, emsdevice->unique_id()); // a unique id as a string
obj["tn"] = emsdevice->device_type_2_device_name_translated(); // translated device type name
obj["t"] = emsdevice->device_type(); // device type number
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)
obj["id"] = emsdevice->unique_id(); // a unique id
obj["tn"] = emsdevice->device_type_2_device_name_translated(); // translated device type name
obj["t"] = emsdevice->device_type(); // device type number
obj["b"] = emsdevice->brand_to_char(); // brand
obj["n"] = emsdevice->name(); // name
obj["d"] = emsdevice->device_id(); // deviceid
obj["p"] = emsdevice->product_id(); // productid
obj["v"] = emsdevice->version(); // version
}
}
// sensors stuff
root["s_n"] = Helpers::translated_word(FL_(sensors_device));
root["active_sensors"] = EMSESP::dallassensor_.no_sensors() + (EMSESP::analogsensor_.analog_enabled() ? EMSESP::analogsensor_.no_sensors() : 0);
root["analog_enabled"] = EMSESP::analogsensor_.analog_enabled();
root["connected"] = EMSESP::bus_status() != 2;
// add any custom entities
if (EMSESP::webEntityService.count_entities()) {
JsonObject obj = devices.createNestedObject();
obj["id"] = 99; // the last unique id
obj["tn"] = Helpers::translated_word(FL_(custom_device)); // translated device type name
obj["t"] = EMSdevice::DeviceType::CUSTOM; // device type number
obj["b"] = Helpers::translated_word(FL_(na)); // brand
obj["n"] = Helpers::translated_word(FL_(custom_device_name)); // name
obj["d"] = 0; // deviceid
obj["p"] = 0; // productid
obj["v"] = 0; // version
}
root["connected"] = EMSESP::bus_status() != 2;
response->setLength();
request->send(response);
@@ -104,15 +112,14 @@ void WebDataService::core_data(AsyncWebServerRequest * request) {
// 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);
auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_XXLARGE);
JsonObject root = response->getRoot();
// dallas sensors
JsonArray sensors = root.createNestedArray("sensors");
if (EMSESP::dallassensor_.have_sensors()) {
for (const auto & sensor : EMSESP::dallassensor_.sensors()) {
// temperature sensors
JsonArray sensors = root.createNestedArray("ts");
if (EMSESP::temperaturesensor_.have_sensors()) {
for (const auto & sensor : EMSESP::temperaturesensor_.sensors()) {
JsonObject obj = sensors.createNestedObject();
obj["id"] = sensor.id(); // id as string
obj["n"] = sensor.name(); // name
@@ -133,49 +140,52 @@ void WebDataService::sensor_data(AsyncWebServerRequest * request) {
}
// analog sensors
JsonArray analogs = root.createNestedArray("analogs");
JsonArray analogs = root.createNestedArray("as");
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();
JsonObject obj = analogs.createNestedObject();
obj["id"] = ++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::transformNumFloat(sensor.value(), 0); // is optional and is a float
} else {
obj["v"] = 0; // must have a value for web sorting to work
}
if (sensor.type() != AnalogSensor::AnalogType::NOTUSED) {
obj["v"] = Helpers::transformNumFloat(sensor.value(), 0); // is optional and is a float
} else {
obj["v"] = 0; // must have a value for web sorting to work
}
}
}
root["analog_enabled"] = EMSESP::analogsensor_.analog_enabled();
response->setLength();
request->send(response);
}
// The unique_id is the unique record ID from the Web table to identify which device to load
// Compresses the JSON using MsgPack https://msgpack.org/index.html
void WebDataService::device_data(AsyncWebServerRequest * request, JsonVariant & json) {
if (json.is<JsonObject>()) {
size_t buffer = EMSESP_JSON_SIZE_XXXLARGE_DYN;
void WebDataService::device_data(AsyncWebServerRequest * request) {
uint8_t id;
if (request->hasParam(F_(id))) {
id = Helpers::atoint(request->getParam(F_(id))->value().c_str()); // get id from url
size_t buffer = EMSESP_JSON_SIZE_XXXXLARGE;
auto * response = new MsgpackAsyncJsonResponse(false, buffer);
// check size
while (!response->getSize()) {
delete response;
buffer -= 1024;
response = new MsgpackAsyncJsonResponse(false, buffer);
}
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice->unique_id() == json["id"]) {
if (emsdevice->unique_id() == 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);
@@ -186,12 +196,6 @@ void WebDataService::device_data(AsyncWebServerRequest * request, JsonVariant &
emsdevice->generate_values_web(output);
#endif
// #ifdef EMSESP_USE_SERIAL
// #ifdef EMSESP_DEBUG
// serializeJson(output, Serial);
// #endif
// #endif
#if defined(EMSESP_DEBUG)
size_t length = response->setLength();
EMSESP::logger().debug("Dashboard buffer used: %d", length);
@@ -202,17 +206,25 @@ void WebDataService::device_data(AsyncWebServerRequest * request, JsonVariant &
return;
}
}
#ifndef EMSESP_STANDALONE
if (id == 99) {
JsonObject output = response->getRoot();
EMSESP::webEntityService.generate_value_web(output);
response->setLength();
request->send(response);
return;
}
#endif
}
// invalid but send ok
AsyncWebServerResponse * response = request->beginResponse(200);
// invalid
AsyncWebServerResponse * response = request->beginResponse(400);
request->send(response);
}
// 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) {
void WebDataService::write_device_value(AsyncWebServerRequest * request, JsonVariant & json) {
if (json.is<JsonObject>()) {
JsonObject dv = json["devicevalue"];
uint8_t unique_id = json["id"];
@@ -233,7 +245,7 @@ void WebDataService::write_value(AsyncWebServerRequest * request, JsonVariant &
// 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 return_code = CommandRet::NOT_FOUND;
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);
@@ -251,24 +263,57 @@ void WebDataService::write_value(AsyncWebServerRequest * request, JsonVariant &
if (return_code != CommandRet::OK) {
EMSESP::logger().err("Write command failed %s (%s)", (const char *)output["message"], Command::return_code_string(return_code).c_str());
} else {
#if defined(EMSESP_DEBUG)
EMSESP::logger().debug("Write command successful");
#endif
}
response->setCode((return_code == CommandRet::OK) ? 200 : 204);
response->setCode((return_code == CommandRet::OK) ? 200 : 400); // bad request
response->setLength();
request->send(response);
return;
}
}
if (unique_id == 99) {
// parse the command as it could have a hc or wwc prefixed, e.g. hc2/seltemp
const char * cmd = dv["c"];
int8_t id = -1;
cmd = Command::parse_command_string(cmd, id);
auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_SMALL);
JsonObject output = response->getRoot();
JsonVariant data = dv["v"]; // the value in any format
uint8_t return_code = CommandRet::NOT_FOUND;
uint8_t device_type = EMSdevice::DeviceType::CUSTOM;
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);
}
if (return_code != CommandRet::OK) {
EMSESP::logger().err("Write command failed %s (%s)", (const char *)output["message"], Command::return_code_string(return_code).c_str());
} else {
#if defined(EMSESP_DEBUG)
EMSESP::logger().debug("Write command successful");
#endif
}
response->setCode((return_code == CommandRet::OK) ? 200 : 400); // bad request
response->setLength();
request->send(response);
return;
}
}
AsyncWebServerResponse * response = request->beginResponse(204); // Write command failed
AsyncWebServerResponse * response = request->beginResponse(400); // bad request
request->send(response);
}
// 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) {
// takes a temperaturesensor name and optional offset from the WebUI and update the customization settings
// via the temperaturesensor service
void WebDataService::write_temperature_sensor(AsyncWebServerRequest * request, JsonVariant & json) {
bool ok = false;
if (json.is<JsonObject>()) {
JsonObject sensor = json;
@@ -282,29 +327,31 @@ void WebDataService::write_sensor(AsyncWebServerRequest * request, JsonVariant &
if (EMSESP::system_.fahrenheit()) {
offset10 = offset / 0.18;
}
ok = EMSESP::dallassensor_.update(id, name, offset10);
ok = EMSESP::temperaturesensor_.update(id, name, offset10);
}
AsyncWebServerResponse * response = request->beginResponse(ok ? 200 : 204);
AsyncWebServerResponse * response = request->beginResponse(ok ? 200 : 400); // bad request
request->send(response);
}
// update the analog record, or create a new one
void WebDataService::write_analog(AsyncWebServerRequest * request, JsonVariant & json) {
void WebDataService::write_analog_sensor(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"];
double factor = analog["factor"];
double offset = analog["offset"];
uint8_t uom = analog["uom"];
int8_t type = analog["type"];
ok = EMSESP::analogsensor_.update(gpio, name, offset, factor, uom, type);
uint8_t gpio = analog["gpio"];
std::string name = analog["name"];
double factor = analog["factor"];
double offset = analog["offset"];
uint8_t uom = analog["uom"];
int8_t type = analog["type"];
bool deleted = analog["deleted"];
ok = EMSESP::analogsensor_.update(gpio, name, offset, factor, uom, type, deleted);
}
AsyncWebServerResponse * response = request->beginResponse(ok ? 200 : 204);
AsyncWebServerResponse * response = request->beginResponse(ok ? 200 : 400); // bad request
request->send(response);
}

View File

@@ -1,7 +1,7 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
*
* Copyright 2020-2023 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
@@ -21,14 +21,14 @@
// 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"
#define WRITE_DEVICE_VALUE_SERVICE_PATH "/rest/writeDeviceValue"
#define WRITE_TEMPERATURE_SENSOR_SERVICE_PATH "/rest/writeTemperatureSensor"
#define WRITE_ANALOG_SENSOR_SERVICE_PATH "/rest/writeAnalogSensor"
#define SCAN_DEVICES_SERVICE_PATH "/rest/scanDevices"
namespace emsesp {
@@ -44,15 +44,15 @@ class WebDataService {
// GET
void core_data(AsyncWebServerRequest * request);
void sensor_data(AsyncWebServerRequest * request);
void device_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);
void write_device_value(AsyncWebServerRequest * request, JsonVariant & json);
void write_temperature_sensor(AsyncWebServerRequest * request, JsonVariant & json);
void write_analog_sensor(AsyncWebServerRequest * request, JsonVariant & json);
void scan_devices(AsyncWebServerRequest * request); // command
AsyncCallbackJsonWebHandler _device_data_handler, _write_value_handler, _write_sensor_handler, _write_analog_handler;
AsyncCallbackJsonWebHandler _write_value_handler, _write_temperature_handler, _write_analog_handler;
};
} // namespace emsesp

View File

@@ -0,0 +1,549 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2023 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
WebEntityService::WebEntityService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(WebEntity::read, WebEntity::update, this, server, EMSESP_ENTITY_SERVICE_PATH, securityManager, AuthenticationPredicates::IS_AUTHENTICATED)
, _fsPersistence(WebEntity::read, WebEntity::update, this, fs, EMSESP_ENTITY_FILE, FS_BUFFER_SIZE) {
}
// load the settings when the service starts
void WebEntityService::begin() {
_fsPersistence.readFromFS();
EMSESP::logger().info("Starting Custom entity service");
}
// this creates the entity file, saving it to the FS
// and also calls when the Entity web page is refreshed
void WebEntity::read(WebEntity & webEntity, JsonObject & root) {
JsonArray entity = root.createNestedArray("entities");
uint8_t counter = 0;
for (const EntityItem & entityItem : webEntity.entityItems) {
JsonObject ei = entity.createNestedObject();
ei["id"] = counter++; // id is only used to render the table and must be unique
ei["device_id"] = entityItem.device_id;
ei["type_id"] = entityItem.type_id;
ei["offset"] = entityItem.offset;
ei["factor"] = entityItem.factor;
ei["name"] = entityItem.name;
ei["uom"] = entityItem.uom;
ei["value_type"] = entityItem.value_type;
ei["writeable"] = entityItem.writeable;
EMSESP::webEntityService.render_value(ei, entityItem, true, true);
}
}
// call on initialization and also when the Entity web page is updated
// this loads the data into the internal class
StateUpdateResult WebEntity::update(JsonObject & root, WebEntity & webEntity) {
for (EntityItem & entityItem : webEntity.entityItems) {
Command::erase_command(EMSdevice::DeviceType::CUSTOM, entityItem.name.c_str());
}
webEntity.entityItems.clear();
if (root["entities"].is<JsonArray>()) {
for (const JsonObject ei : root["entities"].as<JsonArray>()) {
auto entityItem = EntityItem();
entityItem.device_id = ei["device_id"]; // send as numeric, will be converted to string in web
entityItem.type_id = ei["type_id"];
entityItem.offset = ei["offset"];
entityItem.factor = ei["factor"];
entityItem.name = ei["name"].as<std::string>();
entityItem.uom = ei["uom"];
entityItem.value_type = ei["value_type"];
entityItem.writeable = ei["writeable"];
if (entityItem.value_type == DeviceValueType::BOOL) {
entityItem.value = EMS_VALUE_DEFAULT_BOOL;
} else if (entityItem.value_type == DeviceValueType::INT) {
entityItem.value = EMS_VALUE_DEFAULT_INT;
} else if (entityItem.value_type == DeviceValueType::UINT) {
entityItem.value = EMS_VALUE_DEFAULT_UINT;
} else if (entityItem.value_type == DeviceValueType::SHORT) {
entityItem.value = EMS_VALUE_DEFAULT_SHORT;
} else if (entityItem.value_type == DeviceValueType::USHORT) {
entityItem.value = EMS_VALUE_DEFAULT_USHORT;
} else { // if (entityItem.value_type == DeviceValueType::ULONG || entityItem.value_type == DeviceValueType::TIME) {
entityItem.value = EMS_VALUE_DEFAULT_ULONG;
}
if (entityItem.factor == 0) {
entityItem.factor = 1;
}
webEntity.entityItems.push_back(entityItem); // add to list
if (entityItem.writeable) {
Command::add(
EMSdevice::DeviceType::CUSTOM,
webEntity.entityItems.back().name.c_str(),
[webEntity](const char * value, const int8_t id) {
return EMSESP::webEntityService.command_setvalue(value, webEntity.entityItems.back().name);
},
FL_(entity_cmd),
CommandFlag::ADMIN_ONLY);
}
}
}
return StateUpdateResult::CHANGED;
}
// set value by api command
bool WebEntityService::command_setvalue(const char * value, const std::string name) {
EMSESP::webEntityService.read([&](WebEntity & webEntity) { entityItems = &webEntity.entityItems; });
for (EntityItem & entityItem : *entityItems) {
if (Helpers::toLower(entityItem.name) == Helpers::toLower(name)) {
if (entityItem.value_type == DeviceValueType::STRING) {
char telegram[84];
strlcpy(telegram, value, sizeof(telegram));
uint8_t data[EMS_MAX_TELEGRAM_LENGTH];
uint8_t count = 0;
char * p = strtok(telegram, " ,"); // delimiter
while (p != nullptr) {
data[count++] = (uint8_t)strtol(p, 0, 16);
p = strtok(nullptr, " ,");
}
if (count == 0) {
return false;
}
EMSESP::send_write_request(entityItem.type_id, entityItem.device_id, entityItem.offset, data, count, 0);
} else if (entityItem.value_type == DeviceValueType::BOOL) {
bool v;
if (!Helpers::value2bool(value, v)) {
return false;
}
EMSESP::send_write_request(entityItem.type_id, entityItem.device_id, entityItem.offset, v ? 0xFF : 0, 0);
} else {
float f;
if (!Helpers::value2float(value, f)) {
return false;
}
int v = f / entityItem.factor;
if (entityItem.value_type == DeviceValueType::UINT || entityItem.value_type == DeviceValueType::INT) {
EMSESP::send_write_request(entityItem.type_id, entityItem.device_id, entityItem.offset, v, 0);
} else if (entityItem.value_type == DeviceValueType::USHORT || entityItem.value_type == DeviceValueType::SHORT) {
uint8_t v1[2] = {(uint8_t)(v >> 8), (uint8_t)(v & 0xFF)};
EMSESP::send_write_request(entityItem.type_id, entityItem.device_id, entityItem.offset, v1, 2, 0);
} else {
uint8_t v1[3] = {(uint8_t)(v >> 16), (uint8_t)((v & 0xFF00) >> 8), (uint8_t)(v & 0xFF)};
EMSESP::send_write_request(entityItem.type_id, entityItem.device_id, entityItem.offset, v1, 3, 0);
}
}
publish_single(entityItem);
if (EMSESP::mqtt_.get_publish_onchange(0)) {
publish();
}
return true;
}
}
return false;
}
// output of a single value
void WebEntityService::render_value(JsonObject & output, EntityItem entity, const bool useVal, const bool web) {
char payload[12];
std::string name = useVal ? "value" : entity.name;
switch (entity.value_type) {
case DeviceValueType::BOOL:
if ((uint8_t)entity.value != EMS_VALUE_BOOL_NOTSET) {
if (web) {
output[name] = Helpers::render_boolean(payload, (uint8_t)entity.value, true);
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
output[name] = (uint8_t)entity.value ? true : false;
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
output[name] = (uint8_t)entity.value ? 1 : 0;
} else {
output[name] = Helpers::render_boolean(payload, (uint8_t)entity.value);
}
}
break;
case DeviceValueType::INT:
if ((int8_t)entity.value != EMS_VALUE_INT_NOTSET) {
output[name] = serialized(Helpers::render_value(payload, entity.factor * (int8_t)entity.value, 2));
}
break;
case DeviceValueType::UINT:
if ((uint8_t)entity.value != EMS_VALUE_UINT_NOTSET) {
output[name] = serialized(Helpers::render_value(payload, entity.factor * (uint8_t)entity.value, 2));
}
break;
case DeviceValueType::SHORT:
if ((int16_t)entity.value != EMS_VALUE_SHORT_NOTSET) {
output[name] = serialized(Helpers::render_value(payload, entity.factor * (int16_t)entity.value, 2));
}
break;
case DeviceValueType::USHORT:
if ((uint16_t)entity.value != EMS_VALUE_USHORT_NOTSET) {
output[name] = serialized(Helpers::render_value(payload, entity.factor * (uint16_t)entity.value, 2));
}
break;
case DeviceValueType::ULONG:
case DeviceValueType::TIME:
if (entity.value != EMS_VALUE_ULONG_NOTSET) {
output[name] = serialized(Helpers::render_value(payload, entity.factor * entity.value, 2));
}
break;
case DeviceValueType::STRING:
if (entity.data.length() > 0) {
output[name] = entity.data;
}
break;
default:
// EMSESP::logger().warning("unknown value type");
break;
}
}
// process json output for info/commands and value_info
bool WebEntityService::get_value_info(JsonObject & output, const char * cmd) {
EMSESP::webEntityService.read([&](WebEntity & webEntity) { entityItems = &webEntity.entityItems; });
if (entityItems->size() == 0) {
return false;
}
if (Helpers::toLower(cmd) == "commands") {
output["info"] = "lists all values";
output["commands"] = "lists all commands";
for (const auto & entity : *entityItems) {
output[entity.name] = "custom entitiy";
}
return true;
}
if (strlen(cmd) == 0 || Helpers::toLower(cmd) == "values" || Helpers::toLower(cmd) == "info") {
// list all names
for (const EntityItem & entity : *entityItems) {
render_value(output, entity);
}
return (output.size() != 0);
}
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 & entity : *entityItems) {
if (Helpers::toLower(entity.name) == Helpers::toLower(command_s)) {
output["name"] = entity.name;
if (entity.uom > 0) {
output["uom"] = EMSdevice::uom_to_string(entity.uom);
}
output["type"] = entity.value_type == DeviceValueType::BOOL ? "boolean" : entity.value_type == DeviceValueType::STRING ? "string" : F_(number);
output["readable"] = true;
output["writeable"] = entity.writeable;
output["visible"] = true;
output["device_id"] = Helpers::hextoa(entity.device_id);
output["type_id"] = Helpers::hextoa(entity.type_id);
output["offset"] = entity.offset;
if (entity.value_type != DeviceValueType::BOOL && entity.value_type != DeviceValueType::STRING) {
output["factor"] = entity.factor;
} else if (entity.value_type == DeviceValueType::STRING) {
output["bytes"] = (uint8_t)entity.factor;
}
render_value(output, entity, true);
if (attribute_s) {
if (output.containsKey(attribute_s)) {
JsonVariant data = output[attribute_s];
output.clear();
output["api_data"] = data;
} else {
char error[100];
snprintf(error, sizeof(error), "cannot find attribute %s in entity %s", attribute_s, command_s);
output.clear();
output["message"] = error;
}
}
}
if (output.size()) {
return true;
}
}
output["message"] = "unknown command";
return false;
}
// publish single value
void WebEntityService::publish_single(const EntityItem & entity) {
if (!Mqtt::enabled() || !Mqtt::publish_single()) {
return;
}
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
if (Mqtt::publish_single2cmd()) {
snprintf(topic, sizeof(topic), "%s/%s", "custom", entity.name.c_str());
} else {
snprintf(topic, sizeof(topic), "%s/%s", "custom_data", entity.name.c_str());
}
StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> doc;
JsonObject output = doc.to<JsonObject>();
render_value(output, entity, true);
Mqtt::queue_publish(topic, output["value"].as<std::string>());
}
// publish to Mqtt
void WebEntityService::publish(const bool force) {
if (force) {
ha_registered_ = false;
}
if (!Mqtt::enabled()) {
return;
}
EMSESP::webEntityService.read([&](WebEntity & webEntity) { entityItems = &webEntity.entityItems; });
if (entityItems->size() == 0) {
return;
}
if (Mqtt::publish_single() && force) {
for (const EntityItem & entityItem : *entityItems) {
publish_single(entityItem);
}
}
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE);
JsonObject output = doc.to<JsonObject>();
for (const EntityItem & entityItem : *entityItems) {
render_value(output, entityItem);
// create HA config
if (Mqtt::ha_enabled() && !ha_registered_) {
StaticJsonDocument<EMSESP_JSON_SIZE_MEDIUM> config;
char stat_t[50];
snprintf(stat_t, sizeof(stat_t), "%s/custom_data", Mqtt::basename().c_str());
config["stat_t"] = stat_t;
char val_obj[50];
char val_cond[65];
snprintf(val_obj, sizeof(val_obj), "value_json['%s']", entityItem.name.c_str());
snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj);
config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + "}}";
char uniq_s[70];
snprintf(uniq_s, sizeof(uniq_s), "custom_%s", entityItem.name.c_str());
config["obj_id"] = uniq_s;
config["uniq_id"] = uniq_s; // same as object_id
config["name"] = entityItem.name.c_str();
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
if (entityItem.writeable) {
if (entityItem.value_type == DeviceValueType::BOOL) {
snprintf(topic, sizeof(topic), "switch/%s/custom_%s/config", Mqtt::basename().c_str(), entityItem.name.c_str());
} else if (entityItem.value_type == DeviceValueType::STRING) {
snprintf(topic, sizeof(topic), "sensor/%s/custom_%s/config", Mqtt::basename().c_str(), entityItem.name.c_str());
} else if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) {
snprintf(topic, sizeof(topic), "number/%s/custom_%s/config", Mqtt::basename().c_str(), entityItem.name.c_str());
} else {
snprintf(topic, sizeof(topic), "sensor/%s/custom_%s/config", Mqtt::basename().c_str(), entityItem.name.c_str());
}
char command_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(command_topic, sizeof(command_topic), "%s/custom/%s", Mqtt::basename().c_str(), entityItem.name.c_str());
config["cmd_t"] = command_topic;
} else {
if (entityItem.value_type == DeviceValueType::BOOL) {
snprintf(topic, sizeof(topic), "binary_sensor/%s/custom_%s/config", Mqtt::basename().c_str(), entityItem.name.c_str());
} else {
snprintf(topic, sizeof(topic), "sensor/%s/custom_%s/config", Mqtt::basename().c_str(), entityItem.name.c_str());
}
}
if (entityItem.value_type == DeviceValueType::BOOL) {
// applies to both Binary Sensor (read only) and a Switch (for a command)
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
config["pl_on"] = true;
config["pl_off"] = false;
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
config["pl_on"] = 1;
config["pl_off"] = 0;
} else {
char result[12];
config["pl_on"] = Helpers::render_boolean(result, true);
config["pl_off"] = Helpers::render_boolean(result, false);
}
}
JsonObject dev = config.createNestedObject("dev");
JsonArray ids = dev.createNestedArray("ids");
ids.add(Mqtt::basename());
// add "availability" section
Mqtt::add_avty_to_doc(stat_t, config.as<JsonObject>(), val_cond);
if (Mqtt::queue_ha(topic, config.as<JsonObject>())) {
ha_registered_ = true;
}
}
}
if (output.size() > 0) {
Mqtt::queue_publish("custom_data", output);
}
// EMSESP::logger().debug("publish %d custom entities", output.size());
}
// count only entities with valid value or command to show in dashboard
uint8_t WebEntityService::count_entities() {
EMSESP::webEntityService.read([&](WebEntity & webEntity) { entityItems = &webEntity.entityItems; });
if (entityItems->size() == 0) {
return 0;
}
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE);
JsonObject output = doc.to<JsonObject>();
uint8_t count = 0;
for (const EntityItem & entity : *entityItems) {
render_value(output, entity);
count += (output.containsKey(entity.name) || entity.writeable) ? 1 : 0;
}
return count;
}
uint8_t WebEntityService::has_commands() {
EMSESP::webEntityService.read([&](WebEntity & webEntity) { entityItems = &webEntity.entityItems; });
uint8_t count = 0;
for (const EntityItem & entity : *entityItems) {
count += entity.writeable ? 1 : 0;
}
return count;
}
// send to dashboard, msgpack don't like serialized, use number
void WebEntityService::generate_value_web(JsonObject & output) {
EMSESP::webEntityService.read([&](WebEntity & webEntity) { entityItems = &webEntity.entityItems; });
output["label"] = (std::string) "Custom Entities";
JsonArray data = output.createNestedArray("data");
uint8_t index = 0;
for (const EntityItem & entity : *entityItems) {
JsonObject obj = data.createNestedObject(); // create the object, we know there is a value
obj["id"] = "00" + entity.name;
obj["u"] = entity.uom;
if (entity.writeable) {
obj["c"] = entity.name;
if (entity.value_type != DeviceValueType::BOOL && entity.value_type != DeviceValueType::STRING) {
char s[10];
obj["s"] = Helpers::render_value(s, entity.factor, 1);
}
}
switch (entity.value_type) {
case DeviceValueType::BOOL: {
char s[12];
obj["v"] = Helpers::render_boolean(s, (uint8_t)entity.value, true);
JsonArray l = obj.createNestedArray("l");
l.add(Helpers::render_boolean(s, false, true));
l.add(Helpers::render_boolean(s, true, true));
break;
}
case DeviceValueType::INT:
if ((int8_t)entity.value != EMS_VALUE_INT_NOTSET) {
obj["v"] = Helpers::transformNumFloat(entity.factor * (int8_t)entity.value, 0);
}
break;
case DeviceValueType::UINT:
if ((uint8_t)entity.value != EMS_VALUE_UINT_NOTSET) {
obj["v"] = Helpers::transformNumFloat(entity.factor * (uint8_t)entity.value, 0);
}
break;
case DeviceValueType::SHORT:
if ((int16_t)entity.value != EMS_VALUE_SHORT_NOTSET) {
obj["v"] = Helpers::transformNumFloat(entity.factor * (int16_t)entity.value, 0);
}
break;
case DeviceValueType::USHORT:
if ((uint16_t)entity.value != EMS_VALUE_USHORT_NOTSET) {
obj["v"] = Helpers::transformNumFloat(entity.factor * (uint16_t)entity.value, 0);
}
break;
case DeviceValueType::ULONG:
case DeviceValueType::TIME:
if (entity.value != EMS_VALUE_ULONG_NOTSET) {
obj["v"] = Helpers::transformNumFloat(entity.factor * entity.value, 0);
}
break;
case DeviceValueType::STRING:
if (entity.data.length() > 0) {
obj["v"] = entity.data;
}
break;
default:
break;
}
// show only entities with value or command
if (!obj.containsKey("v") && !obj.containsKey("c")) {
data.remove(index);
} else {
index++;
}
}
}
// fetch telegram, called from emsesp::fetch
void WebEntityService::fetch() {
EMSESP::webEntityService.read([&](WebEntity & webEntity) { entityItems = &webEntity.entityItems; });
const uint8_t len[] = {1, 1, 1, 2, 2, 3, 3};
for (auto & entity : *entityItems) {
EMSESP::send_read_request(entity.type_id,
entity.device_id,
entity.offset,
entity.value_type == DeviceValueType::STRING ? (uint8_t)entity.factor : len[entity.value_type]);
}
// EMSESP::logger().debug("fetch custom entities");
}
// called on process telegram, read from telegram
bool WebEntityService::get_value(std::shared_ptr<const Telegram> telegram) {
bool has_change = false;
EMSESP::webEntityService.read([&](WebEntity & webEntity) { entityItems = &webEntity.entityItems; });
// read-length of BOOL, INT, UINT, SHORT, USHORT, ULONG, TIME
const uint8_t len[] = {1, 1, 1, 2, 2, 3, 3};
for (auto & entity : *entityItems) {
if (entity.value_type == DeviceValueType::STRING && telegram->type_id == entity.type_id && telegram->src == entity.device_id
&& telegram->offset == entity.offset) {
auto data = Helpers::data_to_hex(telegram->message_data, telegram->message_length);
if (entity.data != data) {
entity.data = data;
if (Mqtt::publish_single()) {
publish_single(entity);
} else if (EMSESP::mqtt_.get_publish_onchange(0)) {
has_change = true;
}
}
}
if (entity.value_type != DeviceValueType::STRING && telegram->type_id == entity.type_id && telegram->src == entity.device_id
&& telegram->offset <= entity.offset && (telegram->offset + telegram->message_length) >= (entity.offset + len[entity.value_type])) {
uint32_t value = 0;
for (uint8_t i = 0; i < len[entity.value_type]; i++) {
value = (value << 8) + telegram->message_data[i + entity.offset - telegram->offset];
}
if (value != entity.value) {
entity.value = value;
if (Mqtt::publish_single()) {
publish_single(entity);
} else if (EMSESP::mqtt_.get_publish_onchange(0)) {
has_change = true;
}
}
// EMSESP::logger().debug("custom entity %s received with value %d", entity.name.c_str(), (int)entity.val);
}
}
if (has_change) {
publish();
return true;
}
return false;
}
} // namespace emsesp

View File

@@ -0,0 +1,78 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2023 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 "../telegram.h"
#ifndef WebEntityService_h
#define WebEntityService_h
#define EMSESP_ENTITY_FILE "/config/emsespEntity.json"
#define EMSESP_ENTITY_SERVICE_PATH "/rest/entities" // GET and POST
namespace emsesp {
class EntityItem {
public:
uint8_t id;
uint8_t device_id;
uint16_t type_id;
uint8_t offset;
int8_t value_type;
uint8_t uom;
std::string name;
double factor;
bool writeable;
uint32_t value;
std::string data;
};
class WebEntity {
public:
std::list<EntityItem> entityItems;
static void read(WebEntity & webEntity, JsonObject & root);
static StateUpdateResult update(JsonObject & root, WebEntity & webEntity);
};
class WebEntityService : public StatefulService<WebEntity> {
public:
WebEntityService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
void begin();
void publish_single(const EntityItem & entity);
void publish(const bool force = false);
bool command_setvalue(const char * value, const std::string name);
bool get_value_info(JsonObject & output, const char * cmd);
bool get_value(std::shared_ptr<const Telegram> telegram);
void fetch();
void render_value(JsonObject & output, EntityItem entity, const bool useVal = false, const bool web = false);
uint8_t count_entities();
uint8_t has_commands();
void generate_value_web(JsonObject & output);
private:
HttpEndpoint<WebEntity> _httpEndpoint;
FSPersistence<WebEntity> _fsPersistence;
std::list<EntityItem> * entityItems; // pointer to the list of entity items
bool ha_registered_ = false;
};
} // namespace emsesp
#endif

View File

@@ -1,7 +1,7 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
*
* Copyright 2020-2023 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
@@ -24,25 +24,16 @@ namespace emsesp {
WebLogService::WebLogService(AsyncWebServer * server, SecurityManager * securityManager)
: events_(EVENT_SOURCE_LOG_PATH)
, setValues_(LOG_SETTINGS_PATH, std::bind(&WebLogService::setValues, this, _1, _2), 256) { // for POSTS
, setValues_(LOG_SETTINGS_PATH, std::bind(&WebLogService::setValues, this, _1, _2), 256) {
events_.setFilter(securityManager->filterRequest(AuthenticationPredicates::IS_ADMIN));
server->addHandler(&events_);
server->on(EVENT_SOURCE_LOG_PATH, HTTP_GET, std::bind(&WebLogService::forbidden, this, _1));
server->on(LOG_SETTINGS_PATH, HTTP_GET, std::bind(&WebLogService::getValues, this, _1)); // get settings
// for bring back the whole log
server->on(FETCH_LOG_PATH, HTTP_GET, std::bind(&WebLogService::fetchLog, this, _1));
// for bring back the whole log - is a command, hence a POST
server->on(FETCH_LOG_PATH, HTTP_POST, std::bind(&WebLogService::fetchLog, this, _1));
// get when page is loaded
server->on(LOG_SETTINGS_PATH, HTTP_GET, std::bind(&WebLogService::getValues, this, _1));
// for setting a level
server->addHandler(&setValues_);
}
void WebLogService::forbidden(AsyncWebServerRequest * request) {
request->send(403);
server->addHandler(&events_);
}
// start the log service with INFO level
@@ -211,12 +202,12 @@ void WebLogService::transmit(const QueuedLogMessage & message) {
}
// send the complete log buffer to the API, not filtering on log level
// done by resetting the pointer
void WebLogService::fetchLog(AsyncWebServerRequest * request) {
log_message_id_tail_ = 0;
request->send(200);
}
// sets the values like level after a POST
void WebLogService::setValues(AsyncWebServerRequest * request, JsonVariant & json) {
if (!json.is<JsonObject>()) {
@@ -248,4 +239,4 @@ void WebLogService::getValues(AsyncWebServerRequest * request) {
request->send(response);
}
} // namespace emsesp
} // namespace emsesp

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -58,7 +58,6 @@ class WebLogService : public uuid::log::Handler {
const std::shared_ptr<const uuid::log::Message> content_; // Log message content
};
void forbidden(AsyncWebServerRequest * request);
void transmit(const QueuedLogMessage & message);
void fetchLog(AsyncWebServerRequest * request);
void getValues(AsyncWebServerRequest * request);
@@ -67,7 +66,7 @@ class WebLogService : public uuid::log::Handler {
void setValues(AsyncWebServerRequest * request, JsonVariant & json);
AsyncCallbackJsonWebHandler setValues_; // for POSTs
AsyncCallbackJsonWebHandler setValues_; // for POSTs
uint64_t last_transmit_ = 0; // Last transmit time
size_t maximum_log_messages_ = MAX_LOG_MESSAGES; // Maximum number of log messages to buffer before they are output

View File

@@ -0,0 +1,405 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2023 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
WebSchedulerService::WebSchedulerService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(WebScheduler::read, WebScheduler::update, this, server, EMSESP_SCHEDULER_SERVICE_PATH, securityManager, AuthenticationPredicates::IS_AUTHENTICATED)
, _fsPersistence(WebScheduler::read, WebScheduler::update, this, fs, EMSESP_SCHEDULER_FILE) {
}
// load the settings when the service starts
void WebSchedulerService::begin() {
_fsPersistence.readFromFS();
EMSESP::logger().info("Starting Scheduler service");
}
// this creates the scheduler file, saving it to the FS
// and also calls when the Scheduler web page is refreshed
void WebScheduler::read(WebScheduler & webScheduler, JsonObject & root) {
JsonArray schedule = root.createNestedArray("schedule");
uint8_t counter = 0;
for (const ScheduleItem & scheduleItem : webScheduler.scheduleItems) {
JsonObject si = schedule.createNestedObject();
si["id"] = counter++; // id is only used to render the table and must be unique
si["active"] = scheduleItem.active;
si["flags"] = scheduleItem.flags;
si["time"] = scheduleItem.time;
si["cmd"] = scheduleItem.cmd;
si["value"] = scheduleItem.value;
si["name"] = scheduleItem.name;
}
}
// call on initialization and also when the Schedule web page is saved
// this loads the data into the internal class
StateUpdateResult WebScheduler::update(JsonObject & root, WebScheduler & webScheduler) {
#ifdef EMSESP_STANDALONE
// invoke some fake data for testing
const char * json =
"{[{\"id\":1,\"active\":true,\"flags\":31,\"time\": \"07:30\",\"cmd\": \"hc1/mode\",\"value\": \"day\",\"name\": \"turn on central heating\"}]}";
StaticJsonDocument<500> doc;
deserializeJson(doc, json);
root = doc.as<JsonObject>();
Serial.println(COLOR_BRIGHT_MAGENTA);
Serial.print("Using custom file: ");
serializeJson(root, Serial);
Serial.println(COLOR_RESET);
#endif
for (ScheduleItem & scheduleItem : webScheduler.scheduleItems) {
Command::erase_command(EMSdevice::DeviceType::SCHEDULER, scheduleItem.name.c_str());
}
webScheduler.scheduleItems.clear();
if (root["schedule"].is<JsonArray>()) {
for (const JsonObject schedule : root["schedule"].as<JsonArray>()) {
// create each schedule item, overwriting any previous settings
// ignore the id (as this is only used in the web for table rendering)
auto si = ScheduleItem();
si.active = schedule["active"];
si.flags = schedule["flags"];
si.time = schedule["time"].as<std::string>();
si.cmd = schedule["cmd"].as<std::string>();
si.value = schedule["value"].as<std::string>();
si.name = schedule["name"].as<std::string>();
// calculated elapsed minutes
si.elapsed_min = Helpers::string2minutes(si.time);
si.retry_cnt = 0xFF; // no startup retries
webScheduler.scheduleItems.push_back(si); // add to list
if (!webScheduler.scheduleItems.back().name.empty()) {
Command::add(
EMSdevice::DeviceType::SCHEDULER,
webScheduler.scheduleItems.back().name.c_str(),
[webScheduler](const char * value, const int8_t id) {
return EMSESP::webSchedulerService.command_setvalue(value, webScheduler.scheduleItems.back().name);
},
FL_(schedule_cmd),
CommandFlag::ADMIN_ONLY);
}
}
}
EMSESP::webSchedulerService.publish(true);
return StateUpdateResult::CHANGED;
}
// set active by api command
bool WebSchedulerService::command_setvalue(const char * value, const std::string name) {
bool v;
if (!Helpers::value2bool(value, v)) {
return false;
}
EMSESP::webSchedulerService.read([&](WebScheduler & webScheduler) { scheduleItems = &webScheduler.scheduleItems; });
for (ScheduleItem & scheduleItem : *scheduleItems) {
if (scheduleItem.name == name) {
if (scheduleItem.active == v) {
return true;
}
scheduleItem.active = v;
publish_single(name.c_str(), v);
if (EMSESP::mqtt_.get_publish_onchange(0)) {
publish();
}
return true;
}
}
return false;
}
// process json output for info/commands and value_info
bool WebSchedulerService::get_value_info(JsonObject & output, const char * cmd) {
EMSESP::webSchedulerService.read([&](WebScheduler & webScheduler) { scheduleItems = &webScheduler.scheduleItems; });
if (scheduleItems->size() == 0) {
return false;
}
if (Helpers::toLower(cmd) == "commands") {
output["info"] = "lists all values";
output["commands"] = "lists all commands";
for (const ScheduleItem & scheduleItem : *scheduleItems) {
if (!scheduleItem.name.empty()) {
output[scheduleItem.name] = "activate schedule";
}
}
return true;
}
if (strlen(cmd) == 0 || Helpers::toLower(cmd) == "values" || Helpers::toLower(cmd) == "info") {
// list all names
for (const ScheduleItem & scheduleItem : *scheduleItems) {
if (!scheduleItem.name.empty()) {
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
output[scheduleItem.name] = scheduleItem.active;
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
output[scheduleItem.name] = scheduleItem.active ? 1 : 0;
} else {
char result[12];
output[scheduleItem.name] = Helpers::render_boolean(result, scheduleItem.active);
}
}
}
return (output.size() > 0);
}
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;
}
JsonVariant data;
for (const ScheduleItem & scheduleItem : *scheduleItems) {
if (Helpers::toLower(scheduleItem.name) == Helpers::toLower(command_s)) {
output["name"] = scheduleItem.name;
output["type"] = "boolean";
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
output["value"] = scheduleItem.active;
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
output["value"] = scheduleItem.active ? 1 : 0;
} else {
char result[12];
output["value"] = Helpers::render_boolean(result, scheduleItem.active);
}
output["command"] = scheduleItem.cmd;
output["cmd_data"] = scheduleItem.value;
output["readable"] = true;
output["writeable"] = true;
output["visible"] = true;
}
}
if (attribute_s && output.containsKey(attribute_s)) {
data = output[attribute_s];
output.clear();
output["api_data"] = data;
}
if (output.size()) {
return true;
}
output["message"] = "unknown command";
return false;
}
// publish single value
void WebSchedulerService::publish_single(const char * name, const bool state) {
if (!Mqtt::publish_single() || name == nullptr || name[0] == '\0') {
return;
}
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
if (Mqtt::publish_single2cmd()) {
snprintf(topic, sizeof(topic), "%s/%s", F_(scheduler), name);
} else {
snprintf(topic, sizeof(topic), "%s%s/%s", F_(scheduler), "_data", name);
}
char payload[12];
Mqtt::queue_publish(topic, Helpers::render_boolean(payload, state));
}
// publish to Mqtt
void WebSchedulerService::publish(const bool force) {
EMSESP::webSchedulerService.read([&](WebScheduler & webScheduler) { scheduleItems = &webScheduler.scheduleItems; });
if (scheduleItems->size() == 0) {
return;
}
if (Mqtt::publish_single() && force) {
for (const ScheduleItem & scheduleItem : *scheduleItems) {
publish_single(scheduleItem.name.c_str(), scheduleItem.active);
}
}
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE);
for (const ScheduleItem & scheduleItem : *scheduleItems) {
if (!scheduleItem.name.empty() && !doc.containsKey(scheduleItem.name)) {
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
doc[scheduleItem.name] = scheduleItem.active;
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
doc[scheduleItem.name] = scheduleItem.active ? 1 : 0;
} else {
char result[12];
doc[scheduleItem.name] = Helpers::render_boolean(result, scheduleItem.active);
}
// create HA config
if (Mqtt::ha_enabled() && force) {
StaticJsonDocument<EMSESP_JSON_SIZE_MEDIUM> config;
char stat_t[50];
snprintf(stat_t, sizeof(stat_t), "%s/scheduler_data", Mqtt::basename().c_str());
config["stat_t"] = stat_t;
char val_obj[50];
char val_cond[65];
snprintf(val_obj, sizeof(val_obj), "value_json['%s']", scheduleItem.name.c_str());
snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj);
config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + "}}";
char uniq_s[70];
snprintf(uniq_s, sizeof(uniq_s), "scheduler_%s", scheduleItem.name.c_str());
config["obj_id"] = uniq_s;
config["uniq_id"] = uniq_s; // same as object_id
config["name"] = scheduleItem.name.c_str();
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
char command_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "switch/%s/scheduler_%s/config", Mqtt::basename().c_str(), scheduleItem.name.c_str());
snprintf(command_topic, sizeof(command_topic), "%s/scheduler/%s", Mqtt::basename().c_str(), scheduleItem.name.c_str());
config["cmd_t"] = command_topic;
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
config["pl_on"] = true;
config["pl_off"] = false;
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
config["pl_on"] = 1;
config["pl_off"] = 0;
} else {
char result[12];
config["pl_on"] = Helpers::render_boolean(result, true);
config["pl_off"] = Helpers::render_boolean(result, false);
}
JsonObject dev = config.createNestedObject("dev");
JsonArray ids = dev.createNestedArray("ids");
ids.add(Mqtt::basename());
// add "availability" section
Mqtt::add_avty_to_doc(stat_t, config.as<JsonObject>(), val_cond);
Mqtt::queue_ha(topic, config.as<JsonObject>());
}
}
}
if (doc.size() > 0) {
Mqtt::queue_publish("scheduler_data", doc.as<JsonObject>());
}
}
bool WebSchedulerService::has_commands() {
EMSESP::webSchedulerService.read([&](WebScheduler & webScheduler) { scheduleItems = &webScheduler.scheduleItems; });
if (scheduleItems->size() == 0) {
return false;
}
for (const ScheduleItem & scheduleItem : *scheduleItems) {
if (!scheduleItem.name.empty()) {
return true;
}
}
return false;
}
// execute scheduled command
bool WebSchedulerService::command(const char * cmd, const char * data) {
StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> doc_input;
JsonObject input = doc_input.to<JsonObject>();
if (strlen(data)) { // empty data queries a value
input["data"] = data;
}
StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> doc_output; // only for commands without output
JsonObject output = doc_output.to<JsonObject>();
// prefix "api/" to command string
char command_str[100];
snprintf(command_str, sizeof(command_str), "/api/%s", cmd);
uint8_t return_code = Command::process(command_str, true, input, output); // admin set
if (return_code == CommandRet::OK) {
EMSESP::logger().debug("Scheduled command %s with data %s successfully", cmd, data);
if (strlen(data) == 0 && output.size()) {
Mqtt::queue_publish("response", output);
}
return true;
}
char error[100];
if (output.size()) {
snprintf(error,
sizeof(error),
"Scheduled command %s failed with error: %s (%s)",
cmd,
(const char *)output["message"],
Command::return_code_string(return_code).c_str());
} else {
snprintf(error, sizeof(error), "Scheduled command %s failed with error code (%s)", cmd, Command::return_code_string(return_code).c_str());
}
emsesp::EMSESP::logger().err(error);
return false;
}
// process any scheduled jobs
// checks on the minute and at startup
void WebSchedulerService::loop() {
// initialize static value on startup
static int8_t last_tm_min = -1; // invalid value also used for startup commands
static uint32_t last_uptime_min = 0;
// get list of scheduler events and exit if it's empty
EMSESP::webSchedulerService.read([&](WebScheduler & webScheduler) { scheduleItems = &webScheduler.scheduleItems; });
if (scheduleItems->size() == 0) {
return;
}
// check startup commands
if (last_tm_min == -1) {
for (ScheduleItem & scheduleItem : *scheduleItems) {
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min == 0) {
scheduleItem.retry_cnt = command(scheduleItem.cmd.c_str(), scheduleItem.value.c_str()) ? 0xFF : 0;
}
}
last_tm_min = 0; // startup done, now use for RTC
}
// check timer every minute, sync to EMS-ESP clock
uint32_t uptime_min = uuid::get_uptime_sec() / 60;
if (last_uptime_min != uptime_min) {
for (ScheduleItem & scheduleItem : *scheduleItems) {
// retry startup commands not yet executed
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min == 0
&& scheduleItem.retry_cnt < MAX_STARTUP_RETRIES) {
scheduleItem.retry_cnt = command(scheduleItem.cmd.c_str(), scheduleItem.value.c_str()) ? 0xFF : scheduleItem.retry_cnt + 1;
}
// scheduled timer commands
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min > 0
&& (uptime_min % scheduleItem.elapsed_min == 0)) {
command(scheduleItem.cmd.c_str(), scheduleItem.value.c_str());
}
}
last_uptime_min = uptime_min;
}
// check calender, sync to RTC, only execute if year is valid
time_t now = time(nullptr);
tm * tm = localtime(&now);
if (tm->tm_min != last_tm_min && tm->tm_year > 120) {
// find the real dow and minute from RTC
uint8_t real_dow = 1 << tm->tm_wday; // 1 is Sunday
uint16_t real_min = tm->tm_hour * 60 + tm->tm_min;
for (const ScheduleItem & scheduleItem : *scheduleItems) {
if (scheduleItem.active && (real_dow & scheduleItem.flags) && real_min == scheduleItem.elapsed_min) {
command(scheduleItem.cmd.c_str(), scheduleItem.value.c_str());
}
}
last_tm_min = tm->tm_min;
}
}
} // namespace emsesp

View File

@@ -0,0 +1,76 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2023 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 WebSchedulerService_h
#define WebSchedulerService_h
#define EMSESP_SCHEDULER_FILE "/config/emsespScheduler.json"
#define EMSESP_SCHEDULER_SERVICE_PATH "/rest/schedule" // GET and POST
#define SCHEDULEFLAG_SCHEDULE_TIMER 0x80 // 7th bit for Timer
#define MAX_STARTUP_RETRIES 3 // retry the start-up commands x times
namespace emsesp {
class ScheduleItem {
public:
boolean active;
uint8_t flags;
uint16_t elapsed_min; // total mins from 00:00
std::string time; // HH:MM
std::string cmd;
std::string value;
std::string name;
uint8_t retry_cnt;
};
class WebScheduler {
public:
std::list<ScheduleItem> scheduleItems;
static void read(WebScheduler & webScheduler, JsonObject & root);
static StateUpdateResult update(JsonObject & root, WebScheduler & webScheduler);
};
class WebSchedulerService : public StatefulService<WebScheduler> {
public:
WebSchedulerService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
void begin();
void loop();
void publish_single(const char * name, const bool state);
void publish(const bool force = false);
bool has_commands();
bool command_setvalue(const char * value, const std::string name);
bool get_value_info(JsonObject & output, const char * cmd);
// make all functions public so we can test in the debug and standalone mode
#ifndef EMSESP_STANDALONE
private:
#endif
bool command(const char * cmd, const char * data);
HttpEndpoint<WebScheduler> _httpEndpoint;
FSPersistence<WebScheduler> _fsPersistence;
std::list<ScheduleItem> * scheduleItems; // pointer to the list of schedule events
};
} // namespace emsesp
#endif

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -26,12 +26,11 @@ using namespace std::placeholders; // for `_1` etc
WebSettingsService::WebSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(WebSettings::read, WebSettings::update, this, server, EMSESP_SETTINGS_SERVICE_PATH, securityManager)
, _fsPersistence(WebSettings::read, WebSettings::update, this, fs, EMSESP_SETTINGS_FILE)
, _boardProfileHandler(EMSESP_BOARD_PROFILE_SERVICE_PATH,
securityManager->wrapCallback(std::bind(&WebSettingsService::board_profile, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) {
_boardProfileHandler.setMethod(HTTP_POST);
_boardProfileHandler.setMaxContentLength(256);
server->addHandler(&_boardProfileHandler);
, _fsPersistence(WebSettings::read, WebSettings::update, this, fs, EMSESP_SETTINGS_FILE) {
// GET
server->on(EMSESP_BOARD_PROFILE_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WebSettingsService::board_profile, this, _1), AuthenticationPredicates::IS_ADMIN));
addUpdateHandler([&](const String & originId) { onUpdate(); }, false);
}
@@ -47,7 +46,6 @@ void WebSettings::read(WebSettings & settings, JsonObject & root) {
root["syslog_mark_interval"] = settings.syslog_mark_interval;
root["syslog_host"] = settings.syslog_host;
root["syslog_port"] = settings.syslog_port;
root["boiler_heatingoff"] = settings.boiler_heatingoff;
root["shower_timer"] = settings.shower_timer;
root["shower_alert"] = settings.shower_alert;
root["shower_alert_coldshot"] = settings.shower_alert_coldshot;
@@ -93,7 +91,8 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings)
#elif CONFIG_IDF_TARGET_ESP32S2
settings.board_profile = root["board_profile"] | "S2MINI";
#elif CONFIG_IDF_TARGET_ESP32S3
settings.board_profile = root["board_profile"] | "S3MINI";
// settings.board_profile = root["board_profile"] | "S3MINI";
settings.board_profile = root["board_profile"] | "S32S3"; // BBQKees Gateway S3
#elif CONFIG_IDF_TARGET_ESP32
settings.board_profile = root["board_profile"] | EMSESP_DEFAULT_BOARD_PROFILE;
#endif
@@ -167,7 +166,7 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings)
#ifndef EMSESP_STANDALONE
String old_syslog_host = settings.syslog_host;
settings.syslog_host = root["syslog_host"] | EMSESP_DEFAULT_SYSLOG_HOST;
if (!old_syslog_host.equals(settings.syslog_host)) {
if (old_syslog_host != settings.syslog_host) {
add_flags(ChangeFlags::SYSLOG);
}
#endif
@@ -177,13 +176,13 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings)
settings.pbutton_gpio = root["pbutton_gpio"] | default_pbutton_gpio;
check_flag(prev, settings.pbutton_gpio, ChangeFlags::BUTTON);
// dallas
// temperaturesensor
prev = settings.dallas_gpio;
settings.dallas_gpio = root["dallas_gpio"] | default_dallas_gpio;
check_flag(prev, settings.dallas_gpio, ChangeFlags::DALLAS);
check_flag(prev, settings.dallas_gpio, ChangeFlags::SENSOR);
prev = settings.dallas_parasite;
settings.dallas_parasite = root["dallas_parasite"] | EMSESP_DEFAULT_DALLAS_PARASITE;
check_flag(prev, settings.dallas_parasite, ChangeFlags::DALLAS);
check_flag(prev, settings.dallas_parasite, ChangeFlags::SENSOR);
// shower
prev = settings.shower_timer;
@@ -238,7 +237,7 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings)
check_flag(prev, settings.ems_bus_id, ChangeFlags::RESTART);
prev = settings.low_clock;
settings.low_clock = root["low_clock"] | false;
settings.low_clock = root["low_clock"];
check_flag(prev, settings.low_clock, ChangeFlags::RESTART);
//
@@ -247,32 +246,38 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings)
prev = settings.bool_format;
settings.bool_format = root["bool_format"] | EMSESP_DEFAULT_BOOL_FORMAT;
EMSESP::system_.bool_format(settings.bool_format);
if (Mqtt::ha_enabled())
if (Mqtt::ha_enabled()) {
check_flag(prev, settings.bool_format, ChangeFlags::MQTT);
}
prev = settings.enum_format;
settings.enum_format = root["enum_format"] | EMSESP_DEFAULT_ENUM_FORMAT;
EMSESP::system_.enum_format(settings.enum_format);
if (Mqtt::ha_enabled())
if (Mqtt::ha_enabled()) {
check_flag(prev, settings.enum_format, ChangeFlags::MQTT);
}
String old_locale = settings.locale;
settings.locale = root["locale"] | EMSESP_DEFAULT_LOCALE;
EMSESP::system_.locale(settings.locale);
if (Mqtt::ha_enabled() && old_locale != settings.locale) {
add_flags(ChangeFlags::MQTT);
}
//
// without checks or necessary restarts...
//
settings.locale = root["locale"] | EMSESP_DEFAULT_LOCALE;
EMSESP::system_.locale(settings.locale);
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.boiler_heatingoff = root["boiler_heatingoff"] | EMSESP_DEFAULT_BOILER_HEATINGOFF;
settings.notoken_api = root["notoken_api"] | EMSESP_DEFAULT_NOTOKEN_API;
settings.solar_maxflow = root["solar_maxflow"] | EMSESP_DEFAULT_SOLAR_MAXFLOW;
settings.fahrenheit = root["fahrenheit"] | false;
settings.fahrenheit = root["fahrenheit"];
EMSESP::system_.fahrenheit(settings.fahrenheit);
settings.readonly_mode = root["readonly_mode"] | false;
settings.readonly_mode = root["readonly_mode"];
EMSESP::system_.readonly_mode(settings.readonly_mode);
settings.bool_dashboard = root["bool_dashboard"] | EMSESP_DEFAULT_BOOL_FORMAT;
@@ -296,8 +301,8 @@ void WebSettingsService::onUpdate() {
EMSESP::shower_.start();
}
if (WebSettings::has_flags(WebSettings::ChangeFlags::DALLAS)) {
EMSESP::dallassensor_.start();
if (WebSettings::has_flags(WebSettings::ChangeFlags::SENSOR)) {
EMSESP::temperaturesensor_.start();
}
if (WebSettings::has_flags(WebSettings::ChangeFlags::UART)) {
@@ -337,29 +342,29 @@ void WebSettingsService::save() {
}
// build the json profile to send back
void WebSettingsService::board_profile(AsyncWebServerRequest * request, JsonVariant & json) {
if (json.is<JsonObject>()) {
void WebSettingsService::board_profile(AsyncWebServerRequest * request) {
if (request->hasParam("boardProfile")) {
std::string board_profile = request->getParam("boardProfile")->value().c_str();
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];
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);
root["board_profile"] = board_profile;
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);
return;
}
response->setLength();
request->send(response);
return;
}
AsyncWebServerResponse * response = request->beginResponse(200);

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -33,7 +33,6 @@ class WebSettings {
String locale;
uint8_t tx_mode;
uint8_t ems_bus_id;
bool boiler_heatingoff;
bool shower_timer;
bool shower_alert;
uint8_t shower_alert_trigger;
@@ -80,7 +79,7 @@ class WebSettings {
UART = (1 << 0), // 1
SYSLOG = (1 << 1), // 2
ADC = (1 << 2), // 4 - analog
DALLAS = (1 << 3), // 8
SENSOR = (1 << 3), // 8
SHOWER = (1 << 4), // 16
LED = (1 << 5), // 32
BUTTON = (1 << 6), // 64
@@ -123,11 +122,10 @@ class WebSettingsService : public StatefulService<WebSettings> {
void save();
private:
HttpEndpoint<WebSettings> _httpEndpoint;
FSPersistence<WebSettings> _fsPersistence;
AsyncCallbackJsonWebHandler _boardProfileHandler;
HttpEndpoint<WebSettings> _httpEndpoint;
FSPersistence<WebSettings> _fsPersistence;
void board_profile(AsyncWebServerRequest * request, JsonVariant & json);
void board_profile(AsyncWebServerRequest * request);
void onUpdate();
};

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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
@@ -32,6 +32,8 @@ WebStatusService::WebStatusService(AsyncWebServer * server, SecurityManager * se
// handles both WiFI and Ethernet
void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
#ifndef EMSESP_STANDALONE
switch (event) {
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
EMSESP::logger().warning("WiFi disconnected. Reason code=%s", disconnectReason(info.wifi_sta_disconnected.reason)); // IDF 4.0
@@ -39,10 +41,8 @@ void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
break;
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
#ifndef EMSESP_STANDALONE
EMSESP::logger().info("WiFi connected with IP=%s, hostname=%s", WiFi.localIP().toString().c_str(), WiFi.getHostname());
#endif
EMSESP::system_.syslog_init();
// EMSESP::system_.syslog_init();
mDNS_start();
EMSESP::system_.send_info_mqtt("connected");
break;
@@ -63,11 +63,9 @@ void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
case ARDUINO_EVENT_ETH_GOT_IP:
// prevent double calls
if (!EMSESP::system_.ethernet_connected()) {
#ifndef EMSESP_STANDALONE
EMSESP::logger().info("Ethernet connected with IP=%s, speed %d Mbps", ETH.localIP().toString().c_str(), ETH.linkSpeed());
#endif
// EMSESP::system_.send_heartbeat();
EMSESP::system_.syslog_init();
// EMSESP::system_.syslog_init();
EMSESP::system_.ethernet_connected(true);
mDNS_start();
EMSESP::system_.send_info_mqtt("connected");
@@ -84,7 +82,6 @@ void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
EMSESP::system_.ethernet_connected(false);
break;
#ifndef EMSESP_STANDALONE
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) {
if (networkSettings.enableIPv6) {
@@ -108,73 +105,74 @@ void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
} else {
EMSESP::logger().info("WiFi connected with IPv6=%s, hostname=%s", WiFi.localIPv6().toString().c_str(), WiFi.getHostname());
}
EMSESP::system_.syslog_init();
// EMSESP::system_.syslog_init();
mDNS_start();
EMSESP::system_.send_info_mqtt("connected");
break;
#endif
default:
break;
}
#endif
}
void WebStatusService::webStatusService(AsyncWebServerRequest * request) {
auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_MEDIUM_DYN);
auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_LARGE);
JsonObject root = response->getRoot();
root["status"] = EMSESP::bus_status(); // 0, 1 or 2
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_sensors"] = EMSESP::temperaturesensor_.no_sensors();
root["num_analogs"] = EMSESP::analogsensor_.no_sensors();
JsonArray statsJson = root.createNestedArray("stats");
JsonObject statJson;
statJson = statsJson.createNestedObject();
statJson["id"] = "0";
statJson["id"] = 0;
statJson["s"] = EMSESP::rxservice_.telegram_count();
statJson["f"] = EMSESP::rxservice_.telegram_error_count();
statJson["q"] = EMSESP::rxservice_.quality();
statJson = statsJson.createNestedObject();
statJson["id"] = "1";
statJson["id"] = 1;
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"] = "2";
statJson["id"] = 2;
statJson["s"] = EMSESP::txservice_.telegram_write_count();
statJson["f"] = EMSESP::txservice_.telegram_write_fail_count();
statJson["q"] = EMSESP::txservice_.write_quality();
if (EMSESP::dallassensor_.dallas_enabled()) {
if (EMSESP::temperaturesensor_.sensor_enabled()) {
statJson = statsJson.createNestedObject();
statJson["id"] = "3";
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["id"] = 3;
statJson["s"] = EMSESP::temperaturesensor_.reads();
statJson["f"] = EMSESP::temperaturesensor_.fails();
statJson["q"] =
EMSESP::temperaturesensor_.reads() == 0 ? 100 : 100 - (uint8_t)((100 * EMSESP::temperaturesensor_.fails()) / EMSESP::temperaturesensor_.reads());
}
if (EMSESP::analog_enabled()) {
statJson = statsJson.createNestedObject();
statJson["id"] = "4";
statJson["id"] = 4;
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());
}
if (Mqtt::enabled()) {
statJson = statsJson.createNestedObject();
statJson["id"] = "5";
statJson["id"] = 5;
statJson["s"] = Mqtt::publish_count();
statJson["f"] = Mqtt::publish_fails();
statJson["q"] = Mqtt::publish_count() == 0 ? 100 : 100 - (uint8_t)((100 * Mqtt::publish_fails()) / (Mqtt::publish_count() + Mqtt::publish_fails()));
}
statJson = statsJson.createNestedObject();
statJson["id"] = "6";
statJson["id"] = 6;
statJson["s"] = WebAPIService::api_count(); // + WebAPIService::api_fails();
statJson["f"] = WebAPIService::api_fails();
statJson["q"] =
@@ -183,7 +181,7 @@ void WebStatusService::webStatusService(AsyncWebServerRequest * request) {
#ifndef EMSESP_STANDALONE
if (EMSESP::system_.syslog_enabled()) {
statJson = statsJson.createNestedObject();
statJson["id"] = "7";
statJson["id"] = 7;
statJson["s"] = EMSESP::system_.syslog_count();
statJson["f"] = EMSESP::system_.syslog_fails();
statJson["q"] = EMSESP::system_.syslog_count() == 0
@@ -230,61 +228,61 @@ void WebStatusService::mDNS_start() const {
const char * WebStatusService::disconnectReason(uint8_t code) {
#ifndef EMSESP_STANDALONE
switch (code) {
case WIFI_REASON_UNSPECIFIED: // = 1,
return "unspecifiied";
case WIFI_REASON_AUTH_EXPIRE: // = 2,
case WIFI_REASON_UNSPECIFIED: // = 1,
return "unspecified";
case WIFI_REASON_AUTH_EXPIRE: // = 2,
return "auth expire";
case WIFI_REASON_AUTH_LEAVE: // = 3,
case WIFI_REASON_AUTH_LEAVE: // = 3,
return "auth leave";
case WIFI_REASON_ASSOC_EXPIRE: // = 4,
case WIFI_REASON_ASSOC_EXPIRE: // = 4,
return "assoc expired";
case WIFI_REASON_ASSOC_TOOMANY: // = 5,
case WIFI_REASON_ASSOC_TOOMANY: // = 5,
return "assoc too many";
case WIFI_REASON_NOT_AUTHED: // = 6,
return "not authed";
case WIFI_REASON_NOT_ASSOCED: // = 7,
return "not assoced";
case WIFI_REASON_ASSOC_LEAVE: // = 8,
case WIFI_REASON_NOT_AUTHED: // = 6,
return "not authenticated";
case WIFI_REASON_NOT_ASSOCED: // = 7,
return "not assoc";
case WIFI_REASON_ASSOC_LEAVE: // = 8,
return "assoc leave";
case WIFI_REASON_ASSOC_NOT_AUTHED: // = 9,
case WIFI_REASON_ASSOC_NOT_AUTHED: // = 9,
return "assoc not authed";
case WIFI_REASON_DISASSOC_PWRCAP_BAD: // = 10,
case WIFI_REASON_DISASSOC_PWRCAP_BAD: // = 10,
return "disassoc powerCAP bad";
case WIFI_REASON_DISASSOC_SUPCHAN_BAD: // = 11,
case WIFI_REASON_DISASSOC_SUPCHAN_BAD: // = 11,
return "disassoc supchan bad";
case WIFI_REASON_IE_INVALID: // = 13,
case WIFI_REASON_IE_INVALID: // = 13,
return "IE invalid";
case WIFI_REASON_MIC_FAILURE: // = 14,
case WIFI_REASON_MIC_FAILURE: // = 14,
return "MIC failure";
case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: // = 15,
case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: // = 15,
return "4way handshake timeout";
case WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT: // = 16,
return "group key-update timeout";
case WIFI_REASON_IE_IN_4WAY_DIFFERS: // = 17,
case WIFI_REASON_IE_IN_4WAY_DIFFERS: // = 17,
return "IE in 4way differs";
case WIFI_REASON_GROUP_CIPHER_INVALID: // = 18,
case WIFI_REASON_GROUP_CIPHER_INVALID: // = 18,
return "group cipher invalid";
case WIFI_REASON_PAIRWISE_CIPHER_INVALID: // = 19,
case WIFI_REASON_PAIRWISE_CIPHER_INVALID: // = 19,
return "pairwise cipher invalid";
case WIFI_REASON_AKMP_INVALID: // = 20,
case WIFI_REASON_AKMP_INVALID: // = 20,
return "AKMP invalid";
case WIFI_REASON_UNSUPP_RSN_IE_VERSION: // = 21,
case WIFI_REASON_UNSUPP_RSN_IE_VERSION: // = 21,
return "unsupported RSN_IE version";
case WIFI_REASON_INVALID_RSN_IE_CAP: // = 22,
case WIFI_REASON_INVALID_RSN_IE_CAP: // = 22,
return "invalid RSN_IE_CAP";
case WIFI_REASON_802_1X_AUTH_FAILED: // = 23,
case WIFI_REASON_802_1X_AUTH_FAILED: // = 23,
return "802 X1 auth failed";
case WIFI_REASON_CIPHER_SUITE_REJECTED: // = 24,
case WIFI_REASON_CIPHER_SUITE_REJECTED: // = 24,
return "cipher suite rejected";
case WIFI_REASON_BEACON_TIMEOUT: // = 200,
case WIFI_REASON_BEACON_TIMEOUT: // = 200,
return "beacon timeout";
case WIFI_REASON_NO_AP_FOUND: // = 201,
case WIFI_REASON_NO_AP_FOUND: // = 201,
return "no AP found";
case WIFI_REASON_AUTH_FAIL: // = 202,
case WIFI_REASON_AUTH_FAIL: // = 202,
return "auth fail";
case WIFI_REASON_ASSOC_FAIL: // = 203,
case WIFI_REASON_ASSOC_FAIL: // = 203,
return "assoc fail";
case WIFI_REASON_HANDSHAKE_TIMEOUT: // = 204,
case WIFI_REASON_HANDSHAKE_TIMEOUT: // = 204,
return "handshake timeout";
default:
return "unknown";

View File

@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
* Copyright 2020-2023 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