Merge remote-tracking branch 'origin/tech-upgrade' into dev

This commit is contained in:
Proddy
2023-02-27 16:38:38 +01:00
298 changed files with 18497 additions and 24785 deletions

View File

@@ -68,7 +68,7 @@ void AnalogSensor::reload() {
analog_enabled_ = true; // for local offline testing
#endif
for (auto sensor : sensors_) {
remove_ha_topic(sensor.gpio());
remove_ha_topic(sensor.type(), sensor.gpio());
sensor.ha_registered = false;
}
if (!analog_enabled_) {
@@ -333,7 +333,7 @@ bool AnalogSensor::update(uint8_t gpio, const std::string & name, double offset,
// if the sensor exists and we're using HA, delete the old HA record
if (found_sensor && Mqtt::ha_enabled()) {
remove_ha_topic(gpio); // the GPIO
remove_ha_topic(type, gpio); // the GPIO
}
// we didn't find it, it's new, so create and store it
@@ -379,21 +379,35 @@ 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
}
}
// send empty config topic to remove the entry from HA
void AnalogSensor::remove_ha_topic(const uint8_t gpio) const {
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];
snprintf(topic, sizeof(topic), "sensor/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), gpio);
Mqtt::publish_ha(topic);
#if CONFIG_IDF_TARGET_ESP32
if (type == AnalogType::DIGITAL_OUT && gpio != 25 && gpio != 26) {
#else
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
snprintf(topic, sizeof(topic), "number/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), gpio);
} else if (type >= AnalogType::PWM_0) {
snprintf(topic, sizeof(topic), "number/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), gpio);
} else if (type == AnalogType::DIGITAL_IN) {
snprintf(topic, sizeof(topic), "binary-sensor/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), gpio);
} else {
snprintf(topic, sizeof(topic), "sensor/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), gpio);
}
Mqtt::queue_remove_topic(topic);
}
// send all sensor values as a JSON package to MQTT
@@ -447,13 +461,16 @@ void AnalogSensor::publish_values(const bool force) {
snprintf(stat_t, sizeof(stat_t), "%s/analogsensor_data", Mqtt::base().c_str()); // use base path
config["stat_t"] = stat_t;
char str[50];
char val_obj[50];
char val_cond[65];
if (Mqtt::is_nested()) {
snprintf(str, sizeof(str), "{{value_json['%02d'].value}}", sensor.gpio());
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());
} else {
snprintf(str, sizeof(str), "{{value_json['%s']}", sensor.name().c_str());
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"] = str;
config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + "}}";
char uniq_s[70];
if (Mqtt::entity_format() == 2) {
@@ -462,31 +479,85 @@ void AnalogSensor::publish_values(const bool force) {
snprintf(uniq_s, sizeof(uniq_s), "analogsensor_%02d", sensor.gpio());
}
config["object_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
snprintf(str, sizeof(str), "%s", sensor.name().c_str());
config["name"] = str;
char name[50];
snprintf(name, sizeof(name), "%s", sensor.name().c_str());
config["name"] = name;
if (sensor.uom() != DeviceValueUOM::NONE) {
config["unit_of_meas"] = EMSdevice::uom_to_string(sensor.uom());
}
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
// Set commands for some analog types
char command_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
#if CONFIG_IDF_TARGET_ESP32
if (sensor.type() == AnalogType::DIGITAL_OUT && sensor.gpio() != 25 && sensor.gpio() != 26) {
#else
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());
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);
}
} else if (sensor.type() == AnalogType::DIGITAL_OUT) { // DAC
snprintf(topic, sizeof(topic), "number/%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());
config["cmd_t"] = command_topic;
config["min"] = 0;
config["max"] = 255;
config["mode"] = "box"; // auto, slider or box
config["step"] = 1;
} else if (sensor.type() >= AnalogType::PWM_0) {
snprintf(topic, sizeof(topic), "number/%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());
config["cmd_t"] = command_topic;
config["min"] = 0;
config["max"] = 100;
config["mode"] = "box"; // auto, slider or box
config["step"] = 0.1;
} else if (sensor.type() == AnalogType::COUNTER) {
snprintf(topic, sizeof(topic), "sensor/%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());
config["cmd_t"] = command_topic;
config["stat_cla"] = "total_increasing";
// config["mode"] = "box"; // auto, slider or box
// 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());
} else {
snprintf(topic, sizeof(topic), "sensor/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), sensor.gpio());
config["stat_cla"] = "measurement";
}
JsonObject dev = config.createNestedObject("dev");
JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp");
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "sensor/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), sensor.gpio());
// add "availability" section
Mqtt::add_avty_to_doc(stat_t, config.as<JsonObject>(), val_cond);
Mqtt::publish_ha(topic, config.as<JsonObject>());
Mqtt::queue_ha(topic, config.as<JsonObject>());
sensor.ha_registered = true;
}
}
}
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
@@ -689,7 +760,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);
@@ -699,4 +770,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
@@ -160,7 +160,7 @@ class AnalogSensor {
bool update(uint8_t gpio, const std::string & name, double offset, double factor, uint8_t uom, int8_t type);
bool get_value_info(JsonObject & output, const char * cmd, const int8_t id) const;
#ifdef EMSESP_DEBUG
#if defined(EMSESP_TEST)
void test();
#endif
@@ -170,7 +170,7 @@ class AnalogSensor {
static uuid::log::Logger logger_;
void remove_ha_topic(const uint8_t id) const;
void remove_ha_topic(const int8_t type, const uint8_t id) const;
bool command_setvalue(const char * value, const int8_t gpio);
void measure();
bool command_info(const char * value, const int8_t id, JsonObject & output) const;

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,12 +52,6 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
}
}
#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();
@@ -210,13 +204,11 @@ const char * Command::parse_command_string(const char * command, int8_t & id) {
id = command[2] - '0';
command += 3;
} else if (!strncmp(lowerCmd, "wwc", 3) && command[3] == '1' && command[4] == '0') {
id = DeviceValueTAG::TAG_WWC10 - DeviceValueTAG::TAG_HC1 + 1; //18;
command += 5;
} else if (!strncmp(lowerCmd, "wwc", 3) && command[3] >= '1' && command[3] <= '9') {
id = command[3] - '1' + DeviceValueTAG::TAG_WWC1 - DeviceValueTAG::TAG_HC1 + 1; //9;
id = DeviceValueTAG::TAG_WWC10 - DeviceValueTAG::TAG_HC1 + 1; //18; } else if (!strncmp(lowerCmd, "wwc", 3) && command[3] >= '1' && command[3] <= '9') {
id = command[3] - '0' + 8;
command += 4;
} else if (!strncmp(lowerCmd, "id", 2) && command[2] == '1' && command[3] >= '0' && command[3] <= '9') {
id = command[3] - '0' + 10;
id = command[3] - '1' + DeviceValueTAG::TAG_WWC1 - DeviceValueTAG::TAG_HC1 + 1; //9;
command += 4;
} else if (!strncmp(lowerCmd, "id", 2) && command[2] >= '1' && command[2] <= '9') {
id = command[2] - '0';
@@ -231,11 +223,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 +268,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 +343,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/dallas/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 +409,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_);
}

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
@@ -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,26 @@ 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)
// In testing just take one language (en) to save on Flash space
#define MAKE_TRANSLATION(list_name, shortname, en, ...) static const char * const __pstr__L_##list_name[] = {shortname, en, nullptr};
#else
#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
@@ -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;
@@ -214,7 +214,7 @@ void DallasSensor::loop() {
// LOG_DEBUG("Adding %d dallas sensor(s) from first scan", firstscan_);
} else if ((scancnt_ <= 0) && (firstscan_ != sensors_.size())) { // check 2 times for no change of sensor #
scancnt_ = SCAN_START;
sensors_.clear(); // restart scaning and clear to get correct numbering
sensors_.clear(); // restart scanning and clear to get correct numbering
}
state_ = State::IDLE;
}
@@ -439,7 +439,7 @@ 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()) {
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());
@@ -447,7 +447,7 @@ void DallasSensor::publish_sensor(const Sensor & sensor) {
snprintf(topic, sizeof(topic), "%s%s/%s", (F_(dallassensor)), "_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));
}
}
@@ -456,19 +456,21 @@ void DallasSensor::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);
Mqtt::queue_remove_topic(topic);
}
// send all dallas sensor values as a JSON package to MQTT
void DallasSensor::publish_values(const bool force) {
if (!Mqtt::enabled()) {
return;
}
uint8_t num_sensors = sensors_.size();
if (num_sensors == 0) {
@@ -514,13 +516,16 @@ void DallasSensor::publish_values(const bool force) {
config["unit_of_meas"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES);
char str[50];
char val_obj[50];
char val_cond[65];
if (Mqtt::is_nested()) {
snprintf(str, sizeof(str), "{{value_json['%s'].temp}}", sensor.id().c_str());
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());
} else {
snprintf(str, sizeof(str), "{{value_json['%s']}}", sensor.name().c_str());
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"] = str;
config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + "}}";
char uniq_s[70];
if (Mqtt::entity_format() == 2) {
@@ -529,16 +534,20 @@ void DallasSensor::publish_values(const bool force) {
snprintf(uniq_s, sizeof(uniq_s), "dallassensor_%s", sensor.id().c_str());
}
config["object_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
snprintf(str, sizeof(str), "%s", sensor.name().c_str());
config["name"] = str;
char name[50];
snprintf(name, sizeof(name), "%s", sensor.name().c_str());
config["name"] = name;
JsonObject dev = config.createNestedObject("dev");
JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp");
// add "availability" section
Mqtt::add_avty_to_doc(stat_t, config.as<JsonObject>(), val_cond);
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
// use '_' as HA doesn't like '-' in the topic name
std::string sensorid = sensor.id();
@@ -546,14 +555,14 @@ void DallasSensor::publish_values(const bool force) {
snprintf(topic, sizeof(topic), "sensor/%s/dallassensor_%s/config", Mqtt::basename().c_str(), sensorid.c_str());
Mqtt::publish_ha(topic, config.as<JsonObject>());
Mqtt::queue_ha(topic, config.as<JsonObject>());
sensor.ha_registered = true;
}
}
}
Mqtt::publish("dallassensor_data", doc.as<JsonObject>());
Mqtt::queue_publish("dallassensor_data", doc.as<JsonObject>());
}
@@ -596,9 +605,7 @@ bool DallasSensor::Sensor::apply_customization() {
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
if (id_ == sensor.id) {
set_name(sensor.name);
set_offset(sensor.offset);
@@ -613,7 +620,7 @@ bool DallasSensor::Sensor::apply_customization() {
}
// hard coded tests
#ifdef EMSESP_DEBUG
#if defined(EMSESP_TEST)
void DallasSensor::test() {
// add 2 dallas sensors
uint8_t addr[ADDR_LEN] = {1, 2, 3, 4, 5, 6, 7, 8};
@@ -630,4 +637,4 @@ void DallasSensor::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
@@ -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

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
@@ -169,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
@@ -175,4 +175,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));
@@ -848,7 +847,7 @@ void Boiler::check_active(const bool force) {
if (heatingActive_ != val || force) {
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
@@ -872,7 +871,7 @@ void Boiler::check_active(const bool force) {
if (tapwaterActive_ != val || force) {
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
}
}
@@ -1456,7 +1455,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);
@@ -1712,6 +1712,7 @@ 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
if (v == selFlowTemp_) {
EMSESP::txservice_.add(Telegram::Operation::TX_WRITE, device_id(), EMS_TYPE_UBASetPoints, 0, (uint8_t *)&v, 1, 0, false);
return true;
}

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
@@ -92,8 +92,8 @@ class Boiler : public EMSdevice {
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
uint8_t wwAltOpPrioHeat_; // alternating operation, prioritize heat time
uint8_t wwAltOpPrioWw_; // alternating operation, prioritize dhw time
// main
uint8_t reset_; // for reset command
@@ -389,6 +389,14 @@ class Boiler : public EMSdevice {
bool set_pvCooling(const char * value, const int8_t id);
bool set_hpCircPumpWw(const char * value, const int8_t id);
bool set_auxLimit(const char * value, const int8_t id);
inline bool set_auxMaxLimit(const char * value, const int8_t id) {
return set_auxLimit(value, 14);
}
inline bool set_auxLimitStart(const char * value, const int8_t id) {
return set_auxLimit(value, 15);
}
bool set_auxLimit(const char * value, const int8_t id);
inline bool set_auxMaxLimit(const char * value, const int8_t id) {
return set_auxLimit(value, 14);
@@ -460,4 +468,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
@@ -32,14 +32,25 @@ Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, c
register_telegram_type(0x999, "HPFunctionTest", true, MAKE_PF_CB(process_HPFunctionTest));
register_telegram_type(0x9A0, "HPTemperature", false, MAKE_PF_CB(process_HPTemperature));
register_telegram_type(0x99B, "HPFlowTemp", false, MAKE_PF_CB(process_HPFlowTemp));
register_telegram_type(0x99C, "HPComp", false, MAKE_PF_CB(process_HPComp));
// device values
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &airHumidity_, DeviceValueType::UINT, FL_(airHumidity), DeviceValueUOM::PERCENT);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &dewTemperature_, DeviceValueType::UINT, FL_(dewTemperature), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &flowTemp_, DeviceValueType::UINT, FL_(curFlowTemp), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &retTemp_, DeviceValueType::UINT, FL_(retTemp), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &sysRetTemp_, DeviceValueType::UINT, FL_(sysRetTemp), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&flowTemp_,
DeviceValueType::SHORT,
DeviceValueNumOp::DV_NUMOP_DIV10,
FL_(curFlowTemp),
DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &retTemp_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(retTemp), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&sysRetTemp_,
DeviceValueType::SHORT,
DeviceValueNumOp::DV_NUMOP_DIV10,
FL_(sysRetTemp),
DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpTa4_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(hpTa4), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpTr1_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(hpTr1), DeviceValueUOM::DEGREES);
@@ -51,6 +62,9 @@ Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, c
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpJr0_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(hpPl1), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpJr1_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(hpPh1), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &heatingPumpMod_, DeviceValueType::UINT, FL_(heatingPumpMod), DeviceValueUOM::PERCENT);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpCompSpd_, DeviceValueType::UINT, FL_(hpCompSpd), DeviceValueUOM::PERCENT);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&controlStrategy_,
DeviceValueType::ENUM,
@@ -177,6 +191,7 @@ void Heatpump::process_HPFlowTemp(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, flowTemp_, 4);
has_update(telegram, retTemp_, 6);
has_update(telegram, sysRetTemp_, 14);
has_update(telegram, heatingPumpMod_, 19);
}
// 0x0998 HPSettings
@@ -193,6 +208,14 @@ void Heatpump::process_HPSettings(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, switchOverTemp_, 14);
}
// 0x099C HPComp
// 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);
}
// 0x999 HPFunctionTest
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
@@ -41,6 +41,8 @@ class Heatpump : public EMSdevice {
uint8_t energyPriceEl_;
uint8_t energyPricePV_;
int8_t switchOverTemp_;
uint8_t heatingPumpMod_;
uint8_t hpCompSpd_;
// Function test
uint8_t airPurgeMode_;
@@ -50,20 +52,20 @@ class Heatpump : public EMSdevice {
uint8_t heatDrainPan_;
uint8_t heatCable_;
// HM200 temperature
int16_t flowTemp_;
int16_t retTemp_;
int16_t sysRetTemp_;
int16_t hpTc3_; // condenser temp.
int16_t hpTr1_; // compressor temp.
int16_t hpTr3_; // cond. temp. heating
int16_t hpTr4_; // cond. temp. clg
int16_t hpTr5_; // suction line temp.
int16_t hpTr6_; // hot gas temp.
int16_t hpTl2_; // inlet air temperature
int16_t hpTa4_; // drain pan temp.
int16_t hpJr0_; // low pressure sensor
int16_t hpJr1_; // high pressure sensor
// HM200 temperatures
int16_t flowTemp_; // TH1
int16_t retTemp_; // TH2
int16_t sysRetTemp_; // TH3
int16_t hpTc3_; // condenser temp.
int16_t hpTr1_; // compressor temp.
int16_t hpTr3_; // cond. temp. heating
int16_t hpTr4_; // cond. temp. clg
int16_t hpTr5_; // suction line temp.
int16_t hpTr6_; // hot gas temp.
int16_t hpTl2_; // inlet air temperature
int16_t hpTa4_; // drain pan temp.
int16_t hpJr0_; // low pressure sensor
int16_t hpJr1_; // high pressure sensor
void process_HPMonitor1(std::shared_ptr<const Telegram> telegram);
void process_HPMonitor2(std::shared_ptr<const Telegram> telegram);
@@ -71,6 +73,8 @@ class Heatpump : public EMSdevice {
void process_HPFunctionTest(std::shared_ptr<const Telegram> telegram);
void process_HPTemperature(std::shared_ptr<const Telegram> telegram);
void process_HPFlowTemp(std::shared_ptr<const Telegram> telegram);
void process_HPComp(std::shared_ptr<const Telegram> telegram);
bool set_controlStrategy(const char * value, const int8_t id);
bool set_lowNoiseMode(const char * value, const int8_t id);
bool set_lowNoiseStart(const char * value, const int8_t id);
@@ -80,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

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
@@ -317,6 +317,7 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &cylPumpMod_, DeviceValueType::UINT, FL_(cylPumpMod), DeviceValueUOM::PERCENT);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &valveStatus_, DeviceValueType::BOOL, FL_(valveStatus), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &vs1Status_, DeviceValueType::BOOL, FL_(vs1Status), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &cylHeated_, DeviceValueType::BOOL, FL_(cylHeated), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &collectorShutdown_, DeviceValueType::BOOL, FL_(collectorShutdown), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
@@ -820,6 +821,7 @@ void Solar::process_SM100Status(std::shared_ptr<const Telegram> telegram) {
* byte 10 = PS1 Solar circuit pump for collector array 1: test=b0001(1), on=b0100(4) and off=b0011(3)
*/
void Solar::process_SM100Status2(std::shared_ptr<const Telegram> telegram) {
has_bitupdate(telegram, vs1Status_, 0, 2); // on if bit 2 set
has_bitupdate(telegram, valveStatus_, 4, 2); // on if bit 2 set
has_bitupdate(telegram, solarPump_, 10, 2); // on if bit 2 set
has_bitupdate(telegram, solarPump2_, 1, 2); // on if bit 2 set

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
@@ -45,6 +45,7 @@ class Solar : public EMSdevice {
uint8_t solarPump2Mod_; // PS4: modulation solar pump
uint8_t m1Valve_; // M1: heat assistance valve
uint8_t m1Power_; // M1: heat assistance valve
uint8_t vs1Status_; // VS1: status
// 0x363 heat counter
uint16_t heatCntFlowTemp_;

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
@@ -216,9 +216,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
}
@@ -398,9 +396,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;
@@ -1192,10 +1190,9 @@ 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) {
// exit if the 15th byte (second from last) is 0x00, which I think is calculated flow setpoint temperature
// with weather controlled RC35s this value is >=5, otherwise can be zero and our setpoint temps will be incorrect
// see https://github.com/emsesp/EMS-ESP/issues/373#issuecomment-627907301
if (telegram->offset > 0 || telegram->message_length < 15) {
// Check if heatingciruit is active, see https://github.com/emsesp/EMS-ESP32/issues/786
// some RC30_N have only 13 byte, use byte 0 for active detection.
if (telegram->offset > 0 || telegram->message_data[0] == 0x00) {
return;
}
@@ -3299,9 +3296,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;
}
@@ -3344,7 +3339,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:
@@ -4000,7 +3994,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:
@@ -4294,4 +4288,4 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
}
}
} // 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
@@ -555,4 +555,4 @@ class Thermostat : 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
@@ -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);
}
}
@@ -223,6 +225,10 @@ uint8_t EMSdevice::device_name_2_device_type(const char * topic) {
return DeviceType::HEATSOURCE;
}
if (!strcmp(lowtopic, F_(heatsource))) {
return DeviceType::HEATSOURCE;
}
return DeviceType::UNKNOWN;
}
@@ -264,8 +270,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 +281,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 +299,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 +321,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 +349,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 +362,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 +465,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 +507,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 +516,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
@@ -544,6 +550,10 @@ void EMSdevice::add_device_value(uint8_t tag,
return;
}
if (ignore) {
return;
}
// add the device entity
devicevalues_.emplace_back(
device_type_, tag, value_p, type, options, options_single, numeric_operator, short_name, fullname, custom_fullname, uom, has_cmd, min, max, state);
@@ -705,12 +715,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 +776,7 @@ void EMSdevice::publish_value(void * value_p) const {
}
if (payload[0] != '\0') {
Mqtt::publish(topic, payload);
Mqtt::queue_publish(topic, payload);
}
}
}
@@ -780,7 +790,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
@@ -871,17 +881,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;
}
@@ -976,7 +988,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 +1000,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;
}
}
@@ -1018,9 +1032,10 @@ void EMSdevice::generate_values_web_customization(JsonArray & output) {
}
}
}
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,7 +1053,8 @@ 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])) {
// TODO check if pointer reference is safe. strcpy better?
if (dv.tag == tag && dv.short_name == FL_(haclimate[0])) {
if (dv.min != min || dv.max != max) {
dv.min = min;
dv.max = max;
@@ -1054,7 +1070,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 +1122,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.has_tag()) {
// 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 +1150,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 +1258,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 +1276,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);
@@ -1325,14 +1354,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["name"] = 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 +1475,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 +1491,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];
@@ -1468,10 +1499,6 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
output["api_data"] = data;
return true;
} else {
char error[100];
snprintf(error, sizeof(error), "cannot find attribute %s in entity %s", attribute_s, command_s);
output.clear();
output["message"] = error;
return false;
}
}
@@ -1492,7 +1519,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 +1528,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 +1542,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
@@ -1613,6 +1642,7 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
json[name] = time_value;
}
}
// 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] = "";
@@ -1642,22 +1672,6 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
return has_values;
}
// remove the Home Assistant configs for each device value/entity if its not visible or active or marked as read-only
// this is called when an MQTT publish is done via an EMS Device in emsesp.cpp::publish_device_values()
void EMSdevice::mqtt_ha_entity_config_remove() {
for (auto & dv : devicevalues_) {
if (dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED)
&& ((dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE)) || (!dv.has_state(DeviceValueState::DV_ACTIVE)))) {
dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED);
if (dv.short_name == FL_(climate)[0]) {
Mqtt::publish_ha_climate_config(dv.tag, false, true); // delete topic (remove = true)
} else {
Mqtt::publish_ha_sensor_config(dv, "", "", true); // delete topic (remove = true)
}
}
}
}
// create the Home Assistant configs for each device value / entity
// this is called when an MQTT publish is done via an EMS Device in emsesp.cpp::publish_device_values()
void EMSdevice::mqtt_ha_entity_config_create() {
@@ -1667,25 +1681,32 @@ 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);
Mqtt::publish_ha_climate_config(dv.tag, true, false, dv.min, dv.max); // roomTemp
} 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);
Mqtt::publish_ha_climate_config(dv.tag, false, false, dv.min, dv.max); // no roomTemp
}
}
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);
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
if (ESP.getFreeHeap() < (65 * 1024)) {
break;
}
#endif
}
ha_config_done(!create_device_config);
@@ -1694,7 +1715,6 @@ void EMSdevice::mqtt_ha_entity_config_create() {
// remove all config topics in HA
void EMSdevice::ha_config_clear() {
for (auto & dv : devicevalues_) {
Mqtt::publish_ha_sensor_config(dv, "", "", true); // delete topic (remove = true)
dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED);
}
@@ -1711,7 +1731,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";
@@ -1725,7 +1745,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
@@ -1736,7 +1756,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 {
@@ -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();
@@ -289,9 +291,8 @@ class EMSdevice {
void publish_all_values();
void mqtt_ha_entity_config_create();
void mqtt_ha_entity_config_remove();
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);
@@ -306,13 +307,6 @@ class EMSdevice {
ha_config_done_ = v;
}
bool ha_config_firstrun() const {
return ha_config_firstrun_;
}
void ha_config_firstrun(const bool v) {
ha_config_firstrun_ = v;
}
enum Brand : uint8_t {
NO_BRAND = 0, // 0
BOSCH, // 1
@@ -366,6 +360,7 @@ class EMSdevice {
static constexpr uint8_t EMS_DEVICE_ID_RFSENSOR = 0x40; // RF sensor only sending, no reply
static constexpr uint8_t EMS_DEVICE_ID_RFBASE = 0x50;
static constexpr uint8_t EMS_DEVICE_ID_ROOMTHERMOSTAT = 0x17; // TADO using this with no version reply
static constexpr uint8_t EMS_DEVICE_ID_TADO_OLD = 0x19; // TADO using this with no broadcast and version
// generic type IDs
static constexpr uint16_t EMS_TYPE_VERSION = 0x02; // type ID for Version information. Generic across all EMS devices.
@@ -415,7 +410,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
@@ -429,16 +434,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_firstrun_ = true; // this means a first setup of HA is needed after a restart
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)
@@ -449,10 +453,6 @@ 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

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
@@ -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,12 @@ 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());
#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());
#endif
WebStatusService EMSESP::webStatusService = WebStatusService(&webServer, EMSESP::esp8266React.getSecurityManager());
@@ -49,12 +55,15 @@ 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
@@ -309,7 +318,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 +331,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,7 +375,7 @@ 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);
@@ -427,7 +444,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());
@@ -467,7 +484,7 @@ void EMSESP::publish_all(bool force) {
}
}
// 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;
@@ -526,39 +543,21 @@ void EMSESP::reset_mqtt_ha() {
}
// 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());
// group by device type
if (Mqtt::ha_enabled()) {
for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_type)) {
// specially for MQTT Discovery
// we may have some RETAINED /config topics that reference fields in the data payloads that no longer exist
// remove them immediately to prevent HA from complaining
// we need to do this first before the data payload is published, and only done once!
if (emsdevice->ha_config_firstrun()) {
emsdevice->ha_config_clear();
emsdevice->ha_config_firstrun(false);
return;
} else {
// see if we need to delete and /config topics before adding the payloads
emsdevice->mqtt_ha_entity_config_remove();
}
}
}
}
for (uint8_t tag = DeviceValueTAG::TAG_BOILER_DATA_WW; tag <= DeviceValueTAG::TAG_HS16; tag++) {
JsonObject json_hc = json;
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;
}
@@ -566,16 +565,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
@@ -636,7 +636,7 @@ void EMSESP::publish_response(std::shared_ptr<const Telegram> telegram) {
doc["value"] = value;
}
Mqtt::publish("response", doc.as<JsonObject>());
Mqtt::queue_publish("response", doc.as<JsonObject>());
}
// builds json with the detail of each value, for a specific EMS device type or the dallas sensor
@@ -1057,7 +1057,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
// see: https://github.com/emsesp/EMS-ESP32/issues/103#issuecomment-911717342 and https://github.com/emsesp/EMS-ESP32/issues/624
name = "RF room temperature sensor";
device_type = DeviceType::THERMOSTAT;
} else if (device_id == EMSdevice::EMS_DEVICE_ID_ROOMTHERMOSTAT) {
} else if (device_id == EMSdevice::EMS_DEVICE_ID_ROOMTHERMOSTAT || device_id == EMSdevice::EMS_DEVICE_ID_TADO_OLD) {
name = "Generic thermostat";
device_type = DeviceType::THERMOSTAT;
flags = DeviceFlags::EMS_DEVICE_FLAG_RC10 | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE;
@@ -1068,7 +1068,7 @@ 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";
@@ -1083,16 +1083,16 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
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);
@@ -1199,7 +1199,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;
}
@@ -1349,89 +1349,6 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
}
}
// 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;
@@ -1455,29 +1372,153 @@ void EMSESP::scheduled_fetch_values() {
}
}
// 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_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
// 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
}
// 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
}
// 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
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
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,6 +43,7 @@
#include "web/WebDataService.h"
#include "web/WebSettingsService.h"
#include "web/WebCustomizationService.h"
#include "web/WebSchedulerService.h"
#include "web/WebAPIService.h"
#include "web/WebLogService.h"
@@ -55,6 +56,7 @@
#include "dallassensor.h"
#include "analogsensor.h"
#include "console.h"
#include "console_stream.h"
#include "shower.h"
#include "roomcontrol.h"
#include "command.h"
@@ -62,25 +64,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 +87,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();
@@ -219,7 +217,6 @@ class EMSESP {
static System system_;
static DallasSensor dallassensor_;
static AnalogSensor analogsensor_;
static Console console_;
static Shower shower_;
static RxService rxservice_;
static TxService txservice_;
@@ -232,14 +229,9 @@ class EMSESP {
static WebAPIService webAPIService;
static WebLogService webLogService;
static WebCustomizationService webCustomizationService;
static uuid::log::Logger logger();
static WebSchedulerService webSchedulerService;
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);
@@ -272,6 +264,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

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
@@ -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,319 @@
*/
// 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(dallassensor)
MAKE_WORD(alert)
MAKE_WORD(pump)
MAKE_WORD(heatsource)
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")
// 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, "Format: < NTP | dd.mm.yyyy-hh:mm:ss-day(0-6)-dst(0/1) >")
MAKE_NOTRANSLATION(tpl_switchtime, "Format: <nn> [ not_set | day hh:mm on|off ]")
MAKE_NOTRANSLATION(tpl_switchtime1, "Format: <nn> [ not_set | day hh:mm Tn ]")
MAKE_NOTRANSLATION(tpl_holidays, "Format: < dd.mm.yyyy-dd.mm.yyyy >")
MAKE_NOTRANSLATION(tpl_date, "Format: < dd.mm.yyyy >")
MAKE_NOTRANSLATION(tpl_input, "Format: <inv>[<evu1><evu2><evu3><comp><aux><cool><heat><dhw><pv>]")
MAKE_NOTRANSLATION(tpl_input4, "Format: <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_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_(room), FL_(outdoor))
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))
#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();
}

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
@@ -42,6 +42,7 @@ uint8_t Mqtt::entity_format_;
bool Mqtt::ha_enabled_;
uint8_t Mqtt::nested_format_;
std::string Mqtt::discovery_prefix_;
uint8_t Mqtt::discovery_type_;
bool Mqtt::send_response_;
bool Mqtt::publish_single_;
bool Mqtt::publish_single2cmd_;
@@ -62,14 +63,14 @@ std::string Mqtt::lastpayload_ = "";
// Home Assistant specific
// icons from https://materialdesignicons.com used with the UOMs (unit of measurements)
MAKE_PSTR_WORD(measurement)
MAKE_PSTR_WORD(total_increasing)
MAKE_PSTR(icondegrees, "mdi:coolant-temperature") // DeviceValueUOM::DEGREES
MAKE_PSTR(iconpercent, "mdi:percent-outline") // DeviceValueUOM::PERCENT
MAKE_PSTR(iconkb, "mdi:memory") // DeviceValueUOM::KB
MAKE_PSTR(iconlmin, "mdi:water-boiler") // DeviceValueUOM::LMIN
MAKE_PSTR(iconua, "mdi:lightning-bolt-circle") // DeviceValueUOM::UA
MAKE_PSTR(iconnum, "mdi:counter") // DeviceValueUOM::NONE
MAKE_WORD(measurement)
MAKE_WORD(total_increasing)
MAKE_WORD_CUSTOM(icondegrees, "mdi:coolant-temperature") // DeviceValueUOM::DEGREES
MAKE_WORD_CUSTOM(iconpercent, "mdi:percent-outline") // DeviceValueUOM::PERCENT
MAKE_WORD_CUSTOM(iconkb, "mdi:memory") // DeviceValueUOM::KB
MAKE_WORD_CUSTOM(iconlmin, "mdi:water-boiler") // DeviceValueUOM::LMIN
MAKE_WORD_CUSTOM(iconua, "mdi:lightning-bolt-circle") // DeviceValueUOM::UA
MAKE_WORD_CUSTOM(iconnum, "mdi:counter") // DeviceValueUOM::NONE
uuid::log::Logger Mqtt::logger_{F_(mqtt), uuid::log::Facility::DAEMON};
@@ -136,7 +137,7 @@ void Mqtt::loop() {
uint32_t currentMillis = uuid::get_uptime();
// publish top item from MQTT queue to stop flooding
// publish MQTT queue, but timed to avoid overloading the TCP pipe
if ((uint32_t)(currentMillis - last_mqtt_poll_) > MQTT_PUBLISH_WAIT) {
last_mqtt_poll_ = currentMillis;
process_queue();
@@ -211,7 +212,7 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) {
return;
}
shell.printfln("MQTT queue (%d/%d messages):", mqtt_messages_.size(), MAX_MQTT_MESSAGES);
shell.printfln("MQTT queue (%d):", mqtt_messages_.size());
for (const auto & message : mqtt_messages_) {
auto content = message.content_;
@@ -248,7 +249,7 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) {
shell.println();
}
#if defined(EMSESP_DEBUG)
#if defined(EMSESP_TEST)
// simulate receiving a MQTT message, used only for testing
void Mqtt::incoming(const char * topic, const char * payload) {
if (payload != nullptr) {
@@ -300,16 +301,16 @@ void Mqtt::on_message(const char * topic, const char * payload, size_t len) cons
if (!(mf.mqtt_subfunction_)(message)) {
LOG_ERROR("error: invalid payload %s for this topic %s", message, topic);
if (send_response_) {
Mqtt::publish("response", "error: invalid data");
Mqtt::queue_publish("response", "error: invalid data");
}
}
return;
}
}
StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> input_doc;
StaticJsonDocument<EMSESP_JSON_SIZE_LARGE_DYN> output_doc;
JsonObject input, output;
StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> input_doc;
DynamicJsonDocument output_doc(EMSESP_JSON_SIZE_XLARGE);
JsonObject input, output;
// convert payload into a json doc
// if the payload doesn't not contain the key 'value' or 'data', treat the whole payload as the 'value'
@@ -335,12 +336,12 @@ void Mqtt::on_message(const char * topic, const char * payload, size_t len) cons
}
LOG_ERROR(error);
if (send_response_) {
Mqtt::publish("response", error);
Mqtt::queue_publish("response", error);
}
} else {
// all good, send back json output from call
if (send_response_) {
Mqtt::publish("response", output);
Mqtt::queue_publish("response", output);
}
}
}
@@ -370,9 +371,7 @@ void Mqtt::show_topic_handlers(uuid::console::Shell & shell, const uint8_t devic
void Mqtt::on_publish(uint16_t packetId) const {
// find the MQTT message in the queue and remove it
if (mqtt_messages_.empty()) {
#if defined(EMSESP_DEBUG)
LOG_DEBUG("[DEBUG] No message stored for ACK pid %d", packetId);
#endif
LOG_DEBUG("No message stored for ACK pid %d", packetId);
return;
}
@@ -381,7 +380,7 @@ void Mqtt::on_publish(uint16_t packetId) const {
// if the last published failed, don't bother checking it. wait for the next retry
if (mqtt_message.packet_id_ == 0) {
#if defined(EMSESP_DEBUG)
LOG_DEBUG("[DEBUG] ACK for failed message pid 0");
LOG_DEBUG("ACK for failed message pid 0");
#endif
return;
}
@@ -391,9 +390,7 @@ void Mqtt::on_publish(uint16_t packetId) const {
mqtt_publish_fails_++; // increment error count
}
#if defined(EMSESP_DEBUG)
LOG_DEBUG("[DEBUG] ACK pid %d", packetId);
#endif
LOG_DEBUG("ACK pid %d", packetId);
mqtt_messages_.pop_front(); // always remove from queue, regardless if there was a successful ACK
}
@@ -453,18 +450,24 @@ void Mqtt::start() {
}
// if already initialized, don't do it again
// also to prevent duplicated loading from MqttSettingsService::onConfigUpdated()
if (initialized_) {
return;
}
initialized_ = true;
// add the 'publish' command ('call system publish' in console or via API)
Command::add(EMSdevice::DeviceType::SYSTEM, F_(publish), System::command_publish, FL_(publish_cmd));
mqttClient_->onConnect([this](bool sessionPresent) { on_connect(); });
mqttClient_->onDisconnect([this](AsyncMqttClientDisconnectReason reason) {
if (!connecting_) {
// only show the error once, not every 2 seconds
if (!connecting_ && first_connect_attempted_) {
return;
}
connecting_ = false;
first_connect_attempted_ = true;
connecting_ = false;
if (reason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) {
LOG_WARNING("MQTT disconnected: TCP");
} else if (reason == AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED) {
@@ -482,7 +485,10 @@ void Mqtt::start() {
// create last will topic with the base prefixed. It has to be static because asyncmqttclient destroys the reference
static char will_topic[MQTT_TOPIC_MAX_SIZE];
snprintf(will_topic, MQTT_TOPIC_MAX_SIZE, "%s/status", mqtt_base_.c_str());
if (!mqtt_base_.empty()) {
snprintf(will_topic, MQTT_TOPIC_MAX_SIZE, "%s/status", mqtt_base_.c_str());
}
mqttClient_->setWill(will_topic, 1, true, "offline"); // with qos 1, retain true
mqttClient_->onMessage([this](const char * topic, const char * payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
@@ -583,7 +589,7 @@ void Mqtt::on_connect() {
resubscribe();
// publish to the last will topic (see Mqtt::start() function) to say we're alive
publish_retain("status", "online", true); // with retain on
queue_publish_retain("status", "online", true); // with retain on
mqtt_publish_fails_ = 0; // reset fail count to 0
@@ -601,7 +607,7 @@ void Mqtt::on_connect() {
// e.g. homeassistant/sensor/ems-esp/status/config
// all the values from the heartbeat payload will be added as attributes to the entity state
void Mqtt::ha_status() {
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
StaticJsonDocument<EMSESP_JSON_SIZE_LARGE> doc;
char uniq[70];
if (Mqtt::entity_format() == 2) {
@@ -632,13 +638,12 @@ void Mqtt::ha_status() {
#ifndef EMSESP_STANDALONE
dev["cu"] = "http://" + (EMSESP::system_.ethernet_connected() ? ETH.localIP().toString() : WiFi.localIP().toString());
#endif
JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp");
char topic[MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "binary_sensor/%s/system_status/config", mqtt_basename_.c_str());
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
Mqtt::queue_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
// create the sensors - must match the MQTT payload keys
// these are all from the heartbeat MQTT topic
@@ -652,6 +657,7 @@ void Mqtt::ha_status() {
publish_system_ha_sensor_config(DeviceValueType::INT, "Uptime (sec)", "uptime_sec", DeviceValueUOM::SECONDS);
publish_system_ha_sensor_config(DeviceValueType::BOOL, "NTP status", "ntp_status", DeviceValueUOM::CONNECTIVITY);
publish_system_ha_sensor_config(DeviceValueType::INT, "Free memory", "freemem", DeviceValueUOM::KB);
publish_system_ha_sensor_config(DeviceValueType::INT, "Max Alloc", "max_alloc", DeviceValueUOM::KB);
publish_system_ha_sensor_config(DeviceValueType::INT, "MQTT fails", "mqttfails", DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::INT, "Rx received", "rxreceived", DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::INT, "Rx fails", "rxfails", DeviceValueUOM::NONE);
@@ -661,93 +667,93 @@ void Mqtt::ha_status() {
}
// add sub or pub task to the queue.
// returns a pointer to the message created
// the base is not included in the topic
std::shared_ptr<const MqttMessage> Mqtt::queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, bool retain) {
void Mqtt::queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, bool retain) {
if (topic.empty()) {
return nullptr;
return;
}
// take the topic and prefix the base, unless its for HA
std::shared_ptr<MqttMessage> message;
message = std::make_shared<MqttMessage>(operation, topic, payload, retain);
#if defined(EMSESP_DEBUG)
if (operation == Operation::PUBLISH) {
if (message->payload.empty()) {
LOG_INFO("[DEBUG] Adding to queue: (publish) topic='%s' empty payload", message->topic.c_str());
} else {
LOG_INFO("[DEBUG] Adding to queue: (publish) topic='%s' payload=%s", message->topic.c_str(), message->payload.c_str());
}
} else {
LOG_INFO("[DEBUG] Adding to queue: (subscribe) topic='%s'", message->topic.c_str());
#ifndef EMSESP_STANDALONE
// anything below 60MB available free heap is dangerously low, so we stop adding to prevent a crash
// instead of doing a mqtt_messages_.pop_front()
if (mqtt_messages_.size() >= MAX_MQTT_MESSAGES || ESP.getFreeHeap() < (60 * 1024)) {
LOG_WARNING("Queue overflow (queue count=%d, topic=%s)", mqtt_messages_.size(), topic.c_str());
mqtt_publish_fails_++;
return; // don't add to top of queue
}
#endif
// if the queue is full, make room but removing the last one
if (mqtt_messages_.size() >= MAX_MQTT_MESSAGES) {
mqtt_messages_.pop_front();
LOG_WARNING("Queue overflow, removing one message");
mqtt_publish_fails_++;
// take the topic and prefix the base, unless its for HA
std::shared_ptr<MqttMessage> message = std::make_shared<MqttMessage>(operation, topic, payload, retain);
if (operation == Operation::PUBLISH) {
if (message->payload.empty()) {
LOG_DEBUG("Adding to queue: (remove) topic='%s'", message->topic.c_str());
} else {
LOG_DEBUG("Adding to queue: (publish) topic='%s' payload=%s", message->topic.c_str(), message->payload.c_str());
}
} else {
LOG_DEBUG("Adding to queue: (subscribe) topic='%s'", message->topic.c_str());
}
mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message));
return mqtt_messages_.back().content_; // this is because the message has been moved
return;
}
// add MQTT message to queue, payload is a string
std::shared_ptr<const MqttMessage> Mqtt::queue_publish_message(const std::string & topic, const std::string & payload, bool retain) {
void Mqtt::queue_publish_message(const std::string & topic, const std::string & payload, bool retain) {
if (!enabled()) {
return nullptr;
return;
};
return queue_message(Operation::PUBLISH, topic, payload, retain);
queue_message(Operation::PUBLISH, topic, payload, retain);
}
// add MQTT subscribe message to queue
std::shared_ptr<const MqttMessage> Mqtt::queue_subscribe_message(const std::string & topic) {
return queue_message(Operation::SUBSCRIBE, topic, "", false); // no payload
void Mqtt::queue_subscribe_message(const std::string & topic) {
queue_message(Operation::SUBSCRIBE, topic, "", false); // no payload
}
// add MQTT unsubscribe message to queue
std::shared_ptr<const MqttMessage> Mqtt::queue_unsubscribe_message(const std::string & topic) {
return queue_message(Operation::UNSUBSCRIBE, topic, "", false); // no payload
void Mqtt::queue_unsubscribe_message(const std::string & topic) {
queue_message(Operation::UNSUBSCRIBE, topic, "", false); // no payload
}
// MQTT Publish, using a user's retain flag
void Mqtt::publish(const std::string & topic, const std::string & payload) {
void Mqtt::queue_publish(const std::string & topic, const std::string & payload) {
queue_publish_message(topic, payload, mqtt_retain_);
}
// MQTT Publish, using a user's retain flag - except for char * strings
void Mqtt::publish(const char * topic, const char * payload) {
void Mqtt::queue_publish(const char * topic, const char * payload) {
queue_publish_message((topic), payload, mqtt_retain_);
}
// MQTT Publish, using a specific retain flag, topic is a flash string
void Mqtt::publish(const char * topic, const std::string & payload) {
void Mqtt::queue_publish(const char * topic, const std::string & payload) {
queue_publish_message((topic), payload, mqtt_retain_);
}
void Mqtt::publish(const char * topic, const JsonObject & payload) {
publish_retain(topic, payload, mqtt_retain_);
void Mqtt::queue_publish(const char * topic, const JsonObject & payload) {
queue_publish_retain(topic, payload, mqtt_retain_);
}
// publish json doc, only if its not empty
void Mqtt::publish(const std::string & topic, const JsonObject & payload) {
publish_retain(topic, payload, mqtt_retain_);
void Mqtt::queue_publish(const std::string & topic, const JsonObject & payload) {
queue_publish_retain(topic, payload, mqtt_retain_);
}
// MQTT Publish, using a specific retain flag, topic is a flash string, forcing retain flag
void Mqtt::publish_retain(const char * topic, const std::string & payload, bool retain) {
void Mqtt::queue_publish_retain(const char * topic, const std::string & payload, bool retain) {
queue_publish_message((topic), payload, retain);
}
// publish json doc, only if its not empty, using the retain flag
void Mqtt::publish_retain(const std::string & topic, const JsonObject & payload, bool retain) {
publish_retain(topic.c_str(), payload, retain);
void Mqtt::queue_publish_retain(const std::string & topic, const JsonObject & payload, bool retain) {
queue_publish_retain(topic.c_str(), payload, retain);
}
void Mqtt::publish_retain(const char * topic, const JsonObject & payload, bool retain) {
void Mqtt::queue_publish_retain(const char * topic, const JsonObject & payload, bool retain) {
if (enabled() && payload.size()) {
std::string payload_text;
serializeJson(payload, payload_text); // convert json to string
@@ -756,21 +762,20 @@ void Mqtt::publish_retain(const char * topic, const JsonObject & payload, bool r
}
// publish empty payload to remove the topic
void Mqtt::publish_ha(const char * topic) {
void Mqtt::queue_remove_topic(const char * topic) {
if (!enabled()) {
return;
}
std::string fulltopic = Mqtt::discovery_prefix() + topic;
#if defined(EMSESP_DEBUG)
LOG_DEBUG("[DEBUG] Publishing empty HA topic=%s", fulltopic.c_str());
#endif
queue_publish_message(fulltopic, "", true); // publish with retain to remove from broker
if (ha_enabled_) {
queue_publish_message(Mqtt::discovery_prefix() + topic, "", true); // publish with retain to remove from broker
} else {
queue_publish_message(topic, "", true); // publish with retain to remove from broker
}
}
// publish a Home Assistant config topic and payload, with retain flag off.
void Mqtt::publish_ha(const char * topic, const JsonObject & payload) {
// queue a Home Assistant config topic and payload, with retain flag off.
void Mqtt::queue_ha(const char * topic, const JsonObject & payload) {
if (!enabled()) {
return;
}
@@ -779,15 +784,7 @@ void Mqtt::publish_ha(const char * topic, const JsonObject & payload) {
payload_text.reserve(measureJson(payload) + 1);
serializeJson(payload, payload_text); // convert json to string
std::string fulltopic = Mqtt::discovery_prefix() + topic;
#if defined(EMSESP_STANDALONE)
LOG_DEBUG("Publishing HA topic=%s, payload=%s", fulltopic.c_str(), payload_text.c_str());
#elif defined(EMSESP_DEBUG)
LOG_DEBUG("[debug] Publishing HA topic=%s, payload=%s", fulltopic.c_str(), payload_text.c_str());
#endif
// queue messages if the MQTT connection is not yet established. to ensure we don't miss messages
queue_publish_message(fulltopic, payload_text, true); // with retain true
queue_publish_message(Mqtt::discovery_prefix() + topic, payload_text, true); // with retain true
}
// take top from queue and perform the publish or subscribe action
@@ -812,9 +809,7 @@ void Mqtt::process_queue() {
// if this has already been published and we're waiting for an ACK, don't publish again
// it will have a real packet ID
if (mqtt_message.packet_id_ > 0) {
#if defined(EMSESP_DEBUG)
LOG_DEBUG("[DEBUG] Waiting for QOS-ACK");
#endif
LOG_DEBUG("Waiting for QOS-ACK");
// if we don't get the ack within 10 minutes, republish with new packet_id
if (uuid::get_uptime_sec() - last_publish_queue_ < 600) {
return;
@@ -857,19 +852,25 @@ void Mqtt::process_queue() {
}
// else try and publish it
// this is where the *real* publish happens
uint16_t packet_id = mqttClient_->publish(topic, mqtt_qos_, message->retain, message->payload.c_str(), message->payload.size(), false, mqtt_message.id_);
lasttopic_ = topic;
lastpayload_ = message->payload;
LOG_DEBUG("Publishing topic %s (#%02d, retain=%d, retry=%d, size=%d, pid=%d)",
LOG_DEBUG("Published topic %s (#%02d, retain=%d, retry=%d, size=%d, pid=%d)",
topic,
mqtt_message.id_,
message->retain,
mqtt_message.retry_count_ + 1,
message->payload.size(),
packet_id);
#if defined(EMSESP_DEBUG)
LOG_DEBUG("Payload:%s", message->payload.c_str()); // extra message for #784
#endif
/*
if (!message->payload.empty()) {
LOG_DEBUG("Payload: %s", message->payload.c_str()); // extra message for #784
}
*/
if (packet_id == 0) {
// it failed. if we retried n times, give up. remove from queue
if (mqtt_message.retry_count_ == (MQTT_PUBLISH_MAX_RETRY - 1)) {
@@ -889,9 +890,7 @@ void Mqtt::process_queue() {
// but add the packet_id so we can check it later
if (mqtt_qos_ != 0) {
mqtt_messages_.front().packet_id_ = packet_id;
#if defined(EMSESP_DEBUG)
LOG_DEBUG("[DEBUG] Setting packetID for ACK to %d", packet_id);
#endif
LOG_DEBUG("Setting packetID for ACK to %d", packet_id);
return;
}
@@ -900,8 +899,8 @@ void Mqtt::process_queue() {
// create's a ha sensor config topic from a device value object
// and also takes a flag (create_device_config) used to also create the main HA device config. This is only needed for one entity
void Mqtt::publish_ha_sensor_config(DeviceValue & dv, const std::string & model, const std::string & brand, const bool remove, const bool create_device_config) {
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> dev_json;
void Mqtt::publish_ha_sensor_config(DeviceValue & dv, const char * model, const char * brand, const bool remove, const bool create_device_config) {
StaticJsonDocument<EMSESP_JSON_SIZE_LARGE> dev_json;
// always create the ids
JsonArray ids = dev_json.createNestedArray("ids");
@@ -948,8 +947,8 @@ void Mqtt::publish_ha_sensor_config(DeviceValue & dv, const std::string & model,
// publish HA sensor for System using the heartbeat tag
void Mqtt::publish_system_ha_sensor_config(uint8_t type, const char * name, const char * entity, const uint8_t uom) {
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
JsonObject dev_json = doc.createNestedObject("dev");
StaticJsonDocument<EMSESP_JSON_SIZE_LARGE> doc;
JsonObject dev_json = doc.createNestedObject("dev");
JsonArray ids = dev_json.createNestedArray("ids");
ids.add("ems-esp");
@@ -963,7 +962,7 @@ void Mqtt::publish_system_ha_sensor_config(uint8_t type, const char * name, cons
void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdevice::DeviceValueType
uint8_t tag, // EMSdevice::DeviceValueTAG
const char * const fullname, // fullname, already translated
const char * const en_name, // original name
const char * const en_name, // original name in english
const uint8_t device_type, // EMSdevice::DeviceType
const char * const entity, // same as shortname
const uint8_t uom, // EMSdevice::DeviceValueUOM (0=NONE)
@@ -983,15 +982,17 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
// create the device name
auto device_name = EMSdevice::device_type_2_device_name(device_type);
// create entity by add the hc/wwc tag if present, separating with a _
bool has_tag = ((tag < DeviceValue::NUM_TAGS) && (tag != DeviceValue::DeviceValueTAG::TAG_NONE) && strlen(DeviceValue::DeviceValueTAG_s[tag][0]));
// create entity by add the hc/wwc tag if present, separating with an _
char entity_with_tag[50];
if (tag >= DeviceValueTAG::TAG_HC1) {
snprintf(entity_with_tag, sizeof(entity_with_tag), "%s_%s", EMSdevice::tag_to_mqtt(tag).c_str(), entity);
snprintf(entity_with_tag, sizeof(entity_with_tag), "%s_%s", EMSdevice::tag_to_mqtt(tag), entity);
} else {
snprintf(entity_with_tag, sizeof(entity_with_tag), "%s", entity);
}
// build unique identifier also used as object_id and becomes the Entity ID in HA
// build unique identifier also used as object_id which also becomes the Entity ID in HA
char uniq_id[80];
if (Mqtt::entity_format() == 2) {
// prefix base name to each uniq_id and use the shortname
@@ -1005,15 +1006,14 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
char uniq_s[60];
strlcpy(uniq_s, en_name, sizeof(uniq_s));
Helpers::replace_char(uniq_s, ' ', '_');
if (EMSdevice::tag_to_string(tag).empty()) {
if (has_tag) {
snprintf(uniq_id, sizeof(uniq_id), "%s_%s", device_name, Helpers::toLower(uniq_s).c_str());
} else {
snprintf(uniq_id, sizeof(uniq_id), "%s_%s_%s", device_name, EMSdevice::tag_to_string(tag, false).c_str(), Helpers::toLower(uniq_s).c_str());
snprintf(uniq_id, sizeof(uniq_id), "%s_%s_%s", device_name, DeviceValue::DeviceValueTAG_s[tag][0], Helpers::toLower(uniq_s).c_str());
}
}
// build a config topic that will be prefix onto a HA type (e.g. number, switch)
// e.g. homeassistant/number/ems-esp/thermostat_hc1_manualtemp
char config_topic[70];
snprintf(config_topic, sizeof(config_topic), "%s/%s_%s/config", mqtt_basename_.c_str(), device_name, entity_with_tag);
@@ -1030,9 +1030,15 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
case DeviceValueType::SHORT:
case DeviceValueType::USHORT:
case DeviceValueType::ULONG:
// number - https://www.home-assistant.io/integrations/number.mqtt
// https://developers.home-assistant.io/docs/core/entity/number
snprintf(topic, sizeof(topic), "number/%s", config_topic);
if (discovery_type() == 0) {
// Home Assistant
// number - https://www.home-assistant.io/integrations/number.mqtt
snprintf(topic, sizeof(topic), "number/%s", config_topic);
} else {
// Domoticz
// Does not support number, use sensor
snprintf(topic, sizeof(topic), "sensor/%s", config_topic);
}
break;
case DeviceValueType::BOOL:
// switch - https://www.home-assistant.io/integrations/switch.mqtt
@@ -1060,13 +1066,13 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
// if we're asking to remove this topic, send an empty payload and exit
// https://github.com/emsesp/EMS-ESP32/issues/196
if (remove) {
LOG_DEBUG("Removing HA config for %s", uniq_id);
publish_ha(topic);
LOG_DEBUG("Queuing removing topic %s", topic);
queue_remove_topic(topic);
return;
}
// build the payload
DynamicJsonDocument doc(EMSESP_JSON_SIZE_HA_CONFIG);
StaticJsonDocument<EMSESP_JSON_SIZE_LARGE> doc;
doc["uniq_id"] = uniq_id;
doc["obj_id"] = uniq_id; // same as unique_id
@@ -1074,6 +1080,8 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
const char * sc_ha = "stat_cla"; // state class
const char * uom_ha = "unit_of_meas"; // unit of measure
char sample_val[30] = "0"; // sample, correct(!) entity value, used only to prevent warning/error in HA if real value is not published yet
// handle commands, which are device entities that are writable
// we add the command topic parameter
// note: there is no way to handle strings in HA so datetimes (e.g. set_datetime, set_holiday, set_wwswitchtime etc) are excluded
@@ -1081,7 +1089,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
// command topic back to EMS-ESP
char command_topic[MQTT_TOPIC_MAX_SIZE];
if (tag >= DeviceValueTAG::TAG_HC1) {
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s/%s", mqtt_basename_.c_str(), device_name, EMSdevice::tag_to_mqtt(tag).c_str(), entity);
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s/%s", mqtt_basename_.c_str(), device_name, EMSdevice::tag_to_mqtt(tag), entity);
} else {
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", mqtt_basename_.c_str(), device_name, entity);
}
@@ -1089,10 +1097,11 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
// for enums, add options
if (type == DeviceValueType::ENUM) {
JsonArray option_list = doc.createNestedArray("ops"); //options
JsonArray option_list = doc.createNestedArray("ops"); // options
for (uint8_t i = 0; i < options_size; i++) {
option_list.add(Helpers::translated_word(options[i]));
}
snprintf(sample_val, sizeof(sample_val), "'%s'", Helpers::translated_word(options[0]));
} else if (type != DeviceValueType::STRING && type != DeviceValueType::BOOL) {
// Must be Numeric....
doc["mode"] = "box"; // auto, slider or box
@@ -1109,6 +1118,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
if (dv_set_min != 0 || dv_set_max != 0) {
doc["min"] = dv_set_min;
doc["max"] = dv_set_max;
snprintf(sample_val, sizeof(sample_val), "%i", dv_set_min);
}
// set icons
@@ -1136,27 +1146,26 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
char ha_name[70];
char * F_name = strdup(fullname);
Helpers::CharToUpperUTF8(F_name); // capitalize first letter
if (EMSdevice::tag_to_string(tag).empty()) {
snprintf(ha_name, sizeof(ha_name), "%s", F_name); // no tag
if (has_tag) {
// exclude heartbeat tag
snprintf(ha_name, sizeof(ha_name), "%s %s", DeviceValue::DeviceValueTAG_s[tag][0], F_name);
} else {
snprintf(ha_name, sizeof(ha_name), "%s %s", EMSdevice::tag_to_string(tag).c_str(), F_name);
snprintf(ha_name, sizeof(ha_name), "%s", F_name); // no tag
}
free(F_name); // very important!
doc["name"] = ha_name;
// value template
// if its nested mqtt format then use the appended entity name, otherwise take the original name
char val_tpl[75];
if (is_nested()) {
if (tag >= DeviceValueTAG::TAG_HC1) {
snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s.%s}}", EMSdevice::tag_to_mqtt(tag).c_str(), entity);
} else {
snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", entity);
}
char val_obj[100];
char val_cond[200];
if (is_nested() && tag >= DeviceValueTAG::TAG_HC1) {
snprintf(val_obj, sizeof(val_obj), "value_json.%s.%s", EMSdevice::tag_to_mqtt(tag), entity);
snprintf(val_cond, sizeof(val_cond), "value_json.%s is defined and %s is defined", EMSdevice::tag_to_mqtt(tag), val_obj);
} else {
snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", entity);
snprintf(val_obj, sizeof(val_obj), "value_json.%s", entity);
snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj);
}
doc["val_tpl"] = val_tpl;
// special case to handle booleans
// applies to both Binary Sensor (read only) and a Switch (for a command)
@@ -1165,6 +1174,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
doc["pl_on"] = true;
doc["pl_off"] = false;
snprintf(sample_val, sizeof(sample_val), "false");
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
doc["pl_on"] = 1;
doc["pl_off"] = 0;
@@ -1172,8 +1182,8 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
char result[12];
doc["pl_on"] = Helpers::render_boolean(result, true);
doc["pl_off"] = Helpers::render_boolean(result, false);
snprintf(sample_val, sizeof(sample_val), "'%s'", Helpers::render_boolean(result, false));
}
doc[sc_ha] = F_(measurement); //do we want this???
} else {
// always set the uom, using the standards except for hours/minutes/seconds
// using HA specific codes from https://github.com/home-assistant/core/blob/dev/homeassistant/const.py
@@ -1184,10 +1194,10 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
} else if (uom == DeviceValueUOM::SECONDS) {
doc[uom_ha] = "s";
} else if (uom != DeviceValueUOM::NONE) {
// default
doc[uom_ha] = EMSdevice::uom_to_string(uom);
doc[uom_ha] = EMSdevice::uom_to_string(uom); // default
}
}
doc["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + " else " + sample_val + "}}";
// this next section is adding the state class, device class and sometimes the icon
// used for Sensor and Binary Sensor Entities in HA
@@ -1284,7 +1294,11 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
// add the dev json object to the end
doc["dev"] = dev_json;
publish_ha(topic, doc.as<JsonObject>());
// add "availability" section
add_avty_to_doc(stat_t, doc.as<JsonObject>(), val_cond);
// TODO queue it or send it directly via publish?
queue_ha(topic, doc.as<JsonObject>());
}
void Mqtt::publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp, const bool remove, const int16_t min, const uint16_t max) {
@@ -1295,6 +1309,9 @@ void Mqtt::publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp,
char hc_mode_s[30];
char seltemp_s[30];
char currtemp_s[30];
char hc_mode_cond[80];
char seltemp_cond[80];
char currtemp_cond[170];
char mode_str_tpl[400];
char name_s[10];
char uniq_id_s[60];
@@ -1305,31 +1322,40 @@ void Mqtt::publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp,
snprintf(topic, sizeof(topic), "climate/%s/thermostat_hc%d/config", mqtt_basename_.c_str(), hc_num);
if (remove) {
publish_ha(topic); // publish empty payload with retain flag
queue_remove_topic(topic); // publish empty payload with retain flag
return;
}
if (Mqtt::is_nested()) {
// nested format
snprintf(hc_mode_s, sizeof(hc_mode_s), "value_json.hc%d.mode", hc_num);
snprintf(seltemp_s, sizeof(seltemp_s), "{{value_json.hc%d.seltemp}}", hc_num);
snprintf(hc_mode_cond, sizeof(hc_mode_cond), "value_json.hc%d is undefined or %s is undefined", hc_num, hc_mode_s);
snprintf(seltemp_s, sizeof(seltemp_s), "value_json.hc%d.seltemp", hc_num);
snprintf(seltemp_cond, sizeof(seltemp_cond), "value_json.hc%d is defined and %s is defined", hc_num, seltemp_s);
if (has_roomtemp) {
snprintf(currtemp_s, sizeof(currtemp_s), "{{value_json.hc%d.currtemp}}", hc_num);
snprintf(currtemp_s, sizeof(currtemp_s), "value_json.hc%d.currtemp", hc_num);
snprintf(currtemp_cond, sizeof(currtemp_cond), "value_json.hc%d is defined and %s is defined", hc_num, currtemp_s);
}
snprintf(topic_t, sizeof(topic_t), "~/%s", Mqtt::tag_to_topic(EMSdevice::DeviceType::THERMOSTAT, DeviceValueTAG::TAG_NONE).c_str());
} else {
// single format
snprintf(hc_mode_s, sizeof(hc_mode_s), "value_json.mode");
snprintf(seltemp_s, sizeof(seltemp_s), "{{value_json.seltemp}}");
snprintf(hc_mode_cond, sizeof(hc_mode_cond), "%s is undefined", hc_mode_s);
snprintf(seltemp_s, sizeof(seltemp_s), "value_json.seltemp");
snprintf(seltemp_cond, sizeof(seltemp_cond), "%s is defined", seltemp_s);
if (has_roomtemp) {
snprintf(currtemp_s, sizeof(currtemp_s), "{{value_json.currtemp}}");
snprintf(currtemp_s, sizeof(currtemp_s), "value_json.currtemp");
snprintf(currtemp_cond, sizeof(currtemp_cond), "%s is defined", currtemp_s);
}
snprintf(topic_t, sizeof(topic_t), "~/%s", Mqtt::tag_to_topic(EMSdevice::DeviceType::THERMOSTAT, DeviceValueTAG::TAG_HC1 + hc_num - 1).c_str());
}
snprintf(mode_str_tpl,
sizeof(mode_str_tpl),
"{%%if %s=='manual'%%}heat{%%elif %s=='day'%%}heat{%%elif %s=='night'%%}off{%%elif %s=='off'%%}off{%%else%%}auto{%%endif%%}",
"{%%if %s%%}off{%%elif %s=='manual'%%}heat{%%elif %s=='day'%%}heat{%%elif %s=='night'%%}off{%%elif %s=='off'%%}off{%%else%%}auto{%%endif%%}",
hc_mode_cond,
hc_mode_s,
hc_mode_s,
hc_mode_s,
@@ -1346,7 +1372,7 @@ void Mqtt::publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp,
snprintf(temp_cmd_s, sizeof(temp_cmd_s), "~/thermostat/hc%d/seltemp", hc_num);
snprintf(mode_cmd_s, sizeof(temp_cmd_s), "~/thermostat/hc%d/mode", hc_num);
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
StaticJsonDocument<EMSESP_JSON_SIZE_LARGE> doc; // doc is 787 typically so 1024 should be enough
doc["~"] = mqtt_base_;
doc["uniq_id"] = uniq_id_s;
@@ -1356,17 +1382,17 @@ void Mqtt::publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp,
doc["mode_stat_tpl"] = mode_str_tpl;
doc["temp_cmd_t"] = temp_cmd_s;
doc["temp_stat_t"] = topic_t;
doc["temp_stat_tpl"] = seltemp_s;
doc["mode_cmd_t"] = mode_cmd_s;
doc["temp_stat_tpl"] = (std::string) "{{" + seltemp_s + " if " + seltemp_cond + " else 0}}";
if (has_roomtemp) {
doc["curr_temp_t"] = topic_t;
doc["curr_temp_tpl"] = currtemp_s;
doc["curr_temp_tpl"] = (std::string) "{{" + currtemp_s + " if " + currtemp_cond + " else 0}}";
}
doc["min_temp"] = Helpers::render_value(min_s, min, 0, EMSESP::system_.fahrenheit() ? 2 : 0);
doc["max_temp"] = Helpers::render_value(max_s, max, 0, EMSESP::system_.fahrenheit() ? 2 : 0);
doc["temp_step"] = "0.5";
doc["min_temp"] = Helpers::render_value(min_s, min, 0, EMSESP::system_.fahrenheit() ? 2 : 0);
doc["max_temp"] = Helpers::render_value(max_s, max, 0, EMSESP::system_.fahrenheit() ? 2 : 0);
doc["temp_step"] = "0.5";
doc["mode_cmd_t"] = mode_cmd_s;
// the HA climate component only responds to auto, heat and off
JsonArray modes = doc.createNestedArray("modes");
@@ -1379,7 +1405,10 @@ void Mqtt::publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp,
JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp-thermostat");
publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
// add "availability" section
add_avty_to_doc(topic_t, doc.as<JsonObject>(), seltemp_cond, has_roomtemp ? currtemp_cond : nullptr, hc_mode_cond);
queue_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
}
// based on the device and tag, create the MQTT topic name (without the basename)
@@ -1394,11 +1423,49 @@ std::string Mqtt::tag_to_topic(uint8_t device_type, uint8_t tag) {
std::string topic = EMSdevice::device_type_2_device_name(device_type);
// if there is a tag add it
if (!EMSdevice::tag_to_mqtt(tag).empty() && ((tag == DeviceValueTAG::TAG_BOILER_DATA_WW) || (!is_nested() && tag >= DeviceValueTAG::TAG_HC1))) {
if ((tag == DeviceValueTAG::TAG_BOILER_DATA_WW) || (!is_nested() && tag >= DeviceValueTAG::TAG_HC1)) {
return topic + "_data_" + EMSdevice::tag_to_mqtt(tag);
} else {
return topic + "_data";
}
}
// adds "availability" section to HA Discovery config
void Mqtt::add_avty_to_doc(const char * state_t, const JsonObject & doc, const char * cond1, const char * cond2, const char * negcond) {
const char * tpl_draft = "{{'online' if %s else 'offline'}}";
char tpl[150];
JsonArray avty = doc.createNestedArray("avty");
StaticJsonDocument<512> avty_json;
snprintf(tpl, sizeof(tpl), "%s/status", mqtt_base_.c_str());
avty_json["t"] = tpl;
snprintf(tpl, sizeof(tpl), tpl_draft, "value == 'online'");
avty_json["val_tpl"] = tpl;
avty.add(avty_json);
avty.clear();
avty_json["t"] = state_t;
snprintf(tpl, sizeof(tpl), tpl_draft, cond1 == nullptr ? "value is defined" : cond1);
avty_json["val_tpl"] = tpl;
avty.add(avty_json);
if (cond2 != nullptr) {
avty.clear();
snprintf(tpl, sizeof(tpl), tpl_draft, cond2);
avty_json["val_tpl"] = tpl;
avty.add(avty_json);
}
if (negcond != nullptr) {
avty.clear();
snprintf(tpl, sizeof(tpl), "{{'offline' if %s else 'online'}}", negcond);
avty_json["val_tpl"] = tpl;
avty.add(avty_json);
}
doc["avty_mode"] = "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
@@ -29,13 +29,12 @@
using uuid::console::Shell;
namespace emsesp {
// 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;
@@ -79,19 +78,18 @@ class Mqtt {
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 void queue_publish(const std::string & topic, const std::string & payload);
static void queue_publish(const char * topic, const char * payload);
static void queue_publish(const std::string & topic, const JsonObject & payload);
static void queue_publish(const char * topic, const JsonObject & payload);
static void queue_publish(const char * topic, const std::string & payload);
static void queue_publish_retain(const std::string & topic, const JsonObject & payload, bool retain);
static void queue_publish_retain(const char * topic, const std::string & payload, bool retain);
static void queue_publish_retain(const char * topic, const JsonObject & payload, bool retain);
static void queue_ha(const char * topic, const JsonObject & payload);
static void 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(DeviceValue & dv, const char * model, const char * brand, const bool remove, const bool create_device_config = false);
static void publish_ha_sensor_config(uint8_t type,
uint8_t tag,
const char * const fullname,
@@ -116,7 +114,7 @@ class Mqtt {
static void ha_status();
#if defined(EMSESP_DEBUG)
#if defined(EMSESP_TEST)
void incoming(const char * topic, const char * payload = ""); // for testing only
#endif
@@ -186,6 +184,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;
}
@@ -240,6 +242,9 @@ class Mqtt {
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_;
@@ -261,13 +266,13 @@ class Mqtt {
static AsyncMqttClient * 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 uint32_t MQTT_PUBLISH_WAIT = 100; // delay in ms 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 void queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, bool retain);
static void queue_publish_message(const std::string & topic, const std::string & payload, 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;
@@ -298,6 +303,8 @@ class Mqtt {
uint32_t last_publish_heartbeat_ = 0;
uint32_t last_publish_queue_ = 0;
bool first_connect_attempted_ = false;
static bool connecting_;
static bool initialized_;
static uint32_t mqtt_publish_fails_;
@@ -325,6 +332,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

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,13 +144,13 @@ 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";
@@ -168,8 +168,8 @@ void Shower::set_shower_state(bool state, bool force) {
doc["stat_t"] = stat_t;
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
doc["pl_on"] = true;
doc["pl_off"] = false;
doc["pl_on"] = "true";
doc["pl_off"] = "false";
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
doc["pl_on"] = 1;
doc["pl_off"] = 0;
@@ -183,9 +183,13 @@ void Shower::set_shower_state(bool state, bool force) {
JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp");
// 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
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

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,28 @@ Adafruit_NeoPixel pixels(1, 7, NEO_GRB + NEO_KHZ800);
namespace emsesp {
// Languages supported. Note: the order is important and must match locale_translations.h
#if defined(EMSESP_TEST)
// in Debug mode use one language (en) to save flash memory needed for the tests
const char * const languages[] = {EMSESP_LOCALE_EN};
#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};
#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;
}
@@ -136,13 +144,15 @@ bool System::command_publish(const char * value, const int8_t id) {
}
}
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 +173,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 +185,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 +195,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 +223,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 +232,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 +242,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 +252,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 +355,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 +392,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 +409,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 +440,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,21 +521,6 @@ 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
}
@@ -554,15 +541,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 +568,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
@@ -623,7 +613,8 @@ bool System::heartbeat_json(JsonObject & output) {
}
#ifndef EMSESP_STANDALONE
output["freemem"] = ESP.getFreeHeap() / 1024; // kilobytes
output["freemem"] = getHeapMem();
output["max_alloc"] = getMaxAllocMem();
#endif
#ifndef EMSESP_STANDALONE
@@ -644,11 +635,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.
}
}
@@ -661,6 +654,7 @@ void System::network_init(bool refresh) {
last_system_check_ = 0; // force the LED to go from fast flash to pulse
send_heartbeat();
// no ethernet present
if (phy_type_ == PHY_type::PHY_TYPE_NONE) {
return;
}
@@ -671,14 +665,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
@@ -732,24 +726,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
// MQTT subscribe "ems-esp/system/#"
Mqtt::subscribe(EMSdevice::DeviceType::SYSTEM, "system/#", nullptr); // use empty function callback
}
@@ -880,19 +872,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 +907,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 +935,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 +944,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());
@@ -990,15 +991,18 @@ bool System::check_restore() {
std::string settings_type = input["type"];
if (settings_type == "settings") {
// It's a settings file. Parse each section separately. If it's system related it will require a reboot
reboot_required = saveSettings(NETWORK_SETTINGS_FILE, "Network", input);
reboot_required |= saveSettings(AP_SETTINGS_FILE, "AP", input);
reboot_required |= saveSettings(MQTT_SETTINGS_FILE, "MQTT", input);
reboot_required |= saveSettings(NTP_SETTINGS_FILE, "NTP", input);
reboot_required |= saveSettings(SECURITY_SETTINGS_FILE, "Security", input);
reboot_required = saveSettings(NETWORK_SETTINGS_FILE, "Network Settings", input);
reboot_required |= saveSettings(AP_SETTINGS_FILE, "AP Settings", input);
reboot_required |= saveSettings(MQTT_SETTINGS_FILE, "MQTT Settings", input);
reboot_required |= saveSettings(NTP_SETTINGS_FILE, "NTP Settings", input);
reboot_required |= saveSettings(SECURITY_SETTINGS_FILE, "Security Settings", input);
reboot_required |= saveSettings(EMSESP_SETTINGS_FILE, "Settings", 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 {
LOG_ERROR("Unrecognized file uploaded");
}
@@ -1029,15 +1033,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
}
}
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 +1048,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 +1071,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
EMSESP::esp8266React.getMqttSettingsService()->update(
[&](MqttSettings & mqttSettings) {
mqttSettings.entity_format = 0; // use old Entity ID format from v3.4
@@ -1119,7 +1121,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 +1143,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 +1153,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 +1165,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 +1231,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;
@@ -1383,11 +1384,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

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,13 @@ 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);
#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 +229,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 +299,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
@@ -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
@@ -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) {
@@ -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;
}
@@ -537,9 +531,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
@@ -639,13 +631,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
#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 {

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") {
@@ -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") {
@@ -569,11 +640,13 @@ 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 == "dallas") {
shell.printfln("Testing adding Dallas sensor");
emsesp::EMSESP::dallassensor_.test();
ok = true;
}
if (command == "dallas_full") {
@@ -590,6 +663,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
EMSESP::dallassensor_.update("01-0203-0405-0607", "testdallas", 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,10 +696,11 @@ 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);
@@ -650,8 +726,9 @@ 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");
@@ -675,6 +752,7 @@ 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") {
@@ -695,10 +773,10 @@ 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);
@@ -728,7 +806,7 @@ 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") {
@@ -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");
@@ -778,8 +856,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
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;
*/
@@ -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");
@@ -951,7 +1024,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
Mqtt::base("home/cellar/heating");
EMSESP::mqtt_.incoming("home/cellar/heating/thermostat/mode"); // empty payload, sends reponse
#if defined(EMSESP_STANDALONE)
// Web API TESTS
AsyncWebServerRequest request;
@@ -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") {
@@ -1131,6 +1207,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
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") {
@@ -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
@@ -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"
@@ -52,7 +51,8 @@ namespace emsesp {
// #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

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 +1 @@
#define EMSESP_APP_VERSION "3.5.1-dev.0"
#define EMSESP_APP_VERSION "3.6.0-dev.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
@@ -100,8 +100,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 +126,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";

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
@@ -178,7 +178,7 @@ void WebCustomizationService::reset_customization(AsyncWebServerRequest * reques
// 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
@@ -201,7 +201,7 @@ 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;
size_t buffer = EMSESP_JSON_SIZE_XXXXLARGE;
auto * response = new MsgpackAsyncJsonResponse(true, buffer);
while (!response->getSize()) {
delete response;
@@ -328,4 +328,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

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
@@ -70,7 +70,7 @@ 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
@@ -83,7 +83,7 @@ void WebDataService::core_data(AsyncWebServerRequest * request) {
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["b"] = emsdevice->brand_to_char(); // brand
obj["n"] = emsdevice->name(); // name
obj["d"] = emsdevice->device_id(); // deviceid
obj["p"] = emsdevice->product_id(); // productid
@@ -106,7 +106,7 @@ void WebDataService::core_data(AsyncWebServerRequest * request) {
// /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
@@ -167,7 +167,7 @@ void WebDataService::sensor_data(AsyncWebServerRequest * request) {
// 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;
size_t buffer = EMSESP_JSON_SIZE_XXXXLARGE;
auto * response = new MsgpackAsyncJsonResponse(false, buffer);
while (!response->getSize()) {
delete response;
@@ -186,12 +186,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);
@@ -251,7 +245,9 @@ 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);

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,7 +136,7 @@ void WebLogService::operator<<(std::shared_ptr<uuid::log::Message> message) {
log_messages_.pop_front();
}
log_messages_.emplace_back(log_message_id_++, std::move(message));
log_messages_.emplace_back(++log_message_id_, std::move(message));
EMSESP::esp8266React.getNTPSettingsService()->read([&](NTPSettings & settings) {
if (!settings.enabled || (time(nullptr) < 1500000000L)) {
@@ -191,9 +191,9 @@ char * WebLogService::messagetime(char * out, const uint64_t t, const size_t buf
// send to web eventsource
void WebLogService::transmit(const QueuedLogMessage & message) {
auto jsonDocument = DynamicJsonDocument(EMSESP_JSON_SIZE_MEDIUM);
JsonObject logEvent = jsonDocument.to<JsonObject>();
char time_string[25];
StaticJsonDocument<EMSESP_JSON_SIZE_MEDIUM> jsonDocument;
JsonObject logEvent = jsonDocument.to<JsonObject>();
char time_string[25];
logEvent["t"] = messagetime(time_string, message.content_->uptime_ms, sizeof(time_string));
logEvent["l"] = message.content_->level;
@@ -212,32 +212,8 @@ void WebLogService::transmit(const QueuedLogMessage & message) {
// send the complete log buffer to the API, not filtering on log level
void WebLogService::fetchLog(AsyncWebServerRequest * request) {
// auto * response = new MsgpackAsyncJsonResponse(false, EMSESP_JSON_SIZE_LARGE_DYN + 192 * log_messages_.size());
size_t buffer = EMSESP_JSON_SIZE_XLARGE_DYN + 192 * log_messages_.size();
auto * response = new MsgpackAsyncJsonResponse(false, buffer);
while (!response->getSize()) {
delete response;
buffer -= 1024;
response = new MsgpackAsyncJsonResponse(false, buffer);
}
JsonObject root = response->getRoot();
JsonArray log = root.createNestedArray("events");
log_message_id_tail_ = log_messages_.back().id_;
last_transmit_ = uuid::get_uptime_ms();
for (const auto & message : log_messages_) {
JsonObject logEvent = log.createNestedObject();
char time_string[25];
logEvent["t"] = messagetime(time_string, message.content_->uptime_ms, sizeof(time_string));
logEvent["l"] = message.content_->level;
logEvent["i"] = message.id_;
logEvent["n"] = message.content_->name;
logEvent["m"] = message.content_->text;
}
log_message_id_tail_ = log_messages_.back().id_;
response->setLength();
request->send(response);
log_message_id_tail_ = 0;
request->send(200);
}
// sets the values like level after a POST
@@ -271,4 +247,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

View File

@@ -0,0 +1,191 @@
/*
* 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
void WebScheduler::read(WebScheduler & webScheduler, JsonObject & root) {
JsonArray schedule = root.createNestedArray("schedule");
uint8_t count = 0;
char s[3];
for (const ScheduleItem & scheduleItem : webScheduler.scheduleItems) {
JsonObject si = schedule.createNestedObject();
si["id"] = Helpers::smallitoa(s, count++); // create unique ID as a string
si["active"] = scheduleItem.active;
si["flags"] = scheduleItem.flags;
si["time"] = scheduleItem.time;
si["cmd"] = scheduleItem.cmd;
si["value"] = scheduleItem.value;
si["description"] = scheduleItem.description;
}
}
// call on initialization and also when the page is saved via web UI
// 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 =
"{[{\"active\":true,\"flags\":31,\"time\": \"07:30\",\"cmd\": \"hc1/mode\",\"value\": \"day\",\"description\": \"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
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.id = schedule["id"].as<std::string>();
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.description = schedule["description"].as<std::string>();
// calculated elapsed minutes
si.elapsed_min = Helpers::string2minutes(si.time);
si.retry_cnt = 0xFF; // no starup retries
webScheduler.scheduleItems.push_back(si); // add to list
}
}
return StateUpdateResult::CHANGED;
}
// 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 && Mqtt::enabled() && Mqtt::send_response() && 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
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,74 @@
/*
* 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 statup commands x times
namespace emsesp {
class ScheduleItem {
public:
std::string id; // unqiue id
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 description;
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();
private:
bool command(const char * cmd, const char * data);
// make all functions public so we can test in the debug and standalone mode
#ifndef EMSESP_STANDALONE
private:
#endif
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
@@ -240,14 +240,22 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings)
settings.low_clock = root["low_clock"] | false;
check_flag(prev, settings.low_clock, ChangeFlags::RESTART);
String old_local = settings.locale;
settings.locale = root["locale"] | EMSESP_DEFAULT_LOCALE;
EMSESP::system_.locale(settings.locale);
#ifndef EMSESP_STANDALONE
if (!old_local.equals(settings.locale)) {
add_flags(ChangeFlags::RESTART); // force restart
//
// these may need mqtt restart to rebuild HA discovery topics
//
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()) {
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()) {
check_flag(prev, settings.enum_format, ChangeFlags::MQTT);
}
#endif
//
// these may need mqtt restart to rebuild HA discovery topics
@@ -267,6 +275,9 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings)
//
// 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);

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
@@ -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,19 +105,19 @@ 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

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