Merge remote-tracking branch 'origin/dev' for 3.2.0 release

This commit is contained in:
proddy
2021-08-06 12:06:08 +02:00
155 changed files with 4430 additions and 3172 deletions

View File

@@ -28,90 +28,105 @@ std::vector<Command::CmdFunction> Command::cmdfunctions_;
// calls a command
// id may be used to represent a heating circuit for example, it's optional
// returns false if error or not found
bool Command::call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id) {
// returns 0 if the command errored, 1 (TRUE) if ok, 2 if not found, 3 if error or 4 if not allowed
uint8_t Command::call(const uint8_t device_type, const char * cmd, const char * value, bool authenticated, const int8_t id) {
int8_t id_new = id;
char cmd_new[20] = {'\0'};
strlcpy(cmd_new, cmd, 20);
// find the command
auto cf = find_command(device_type, cmd_new, id_new);
if ((cf == nullptr) || (cf->cmdfunction_json_)) {
LOG_WARNING(F("Command %s on %s not found"), cmd, EMSdevice::device_type_2_device_name(device_type).c_str());
return false; // command not found, or requires a json
return CommandRet::NOT_FOUND;
}
// check if we're allowed to call it
if (cf->has_flags(CommandFlag::ADMIN_ONLY) && !authenticated) {
LOG_WARNING(F("Command %s on %s not permitted. requires admin."), cmd, EMSdevice::device_type_2_device_name(device_type).c_str());
return CommandRet::NOT_ALLOWED;
}
#ifdef EMSESP_DEBUG
std::string dname = EMSdevice::device_type_2_device_name(device_type);
if (value == nullptr) {
LOG_DEBUG(F("[DEBUG] Calling %s command '%s'"), dname.c_str(), cmd);
LOG_INFO(F("Calling %s command '%s'"), dname.c_str(), cmd);
} else if (id == -1) {
LOG_DEBUG(F("[DEBUG] Calling %s command '%s', value %s, id is default"), dname.c_str(), cmd, value);
LOG_INFO(F("Calling %s command '%s', value %s, id is default"), dname.c_str(), cmd, value);
} else {
LOG_DEBUG(F("[DEBUG] Calling %s command '%s', value %s, id is %d"), dname.c_str(), cmd, value, id);
LOG_INFO(F("Calling %s command '%s', value %s, id is %d"), dname.c_str(), cmd, value, id);
}
#endif
return ((cf->cmdfunction_)(value, id_new));
return ((cf->cmdfunction_)(value, id_new)) ? CommandRet::OK : CommandRet::ERROR;
}
// calls a command. Takes a json object for output.
// id may be used to represent a heating circuit for example
// returns false if error or not found
bool Command::call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id, JsonObject & json) {
// returns 0 if the command errored, 1 (TRUE) if ok, 2 if not found, 3 if error or 4 if not allowed
uint8_t Command::call(const uint8_t device_type, const char * cmd, const char * value, bool authenticated, const int8_t id, JsonObject & json) {
int8_t id_new = id;
char cmd_new[20] = {'\0'};
strlcpy(cmd_new, cmd, 20);
auto cf = find_command(device_type, cmd_new, id_new);
#ifdef EMSESP_DEBUG
// check if we're allowed to call it
if (cf != nullptr) {
if (cf->has_flags(CommandFlag::ADMIN_ONLY) && !authenticated && value != nullptr) {
LOG_WARNING(F("Command %s on %s not permitted. requires admin."), cmd, EMSdevice::device_type_2_device_name(device_type).c_str());
return CommandRet::NOT_ALLOWED; // command not allowed
}
}
std::string dname = EMSdevice::device_type_2_device_name(device_type);
if (value == nullptr) {
LOG_DEBUG(F("[DEBUG] Calling %s command '%s'"), dname.c_str(), cmd);
LOG_INFO(F("Calling %s command '%s'"), dname.c_str(), cmd);
} else if (id == -1) {
LOG_DEBUG(F("[DEBUG] Calling %s command '%s', value %s, id is default"), dname.c_str(), cmd, value);
LOG_INFO(F("Calling %s command '%s', value %s, id is default"), dname.c_str(), cmd, value);
} else {
LOG_DEBUG(F("[DEBUG] Calling %s command '%s', value %s, id is %d"), dname.c_str(), cmd, value, id);
LOG_INFO(F("Calling %s command '%s', value %s, id is %d"), dname.c_str(), cmd, value, id);
}
#endif
// check if json object is empty, if so quit
if (json.isNull()) {
LOG_WARNING(F("Ignore call for command %s in %s because no json"), cmd, EMSdevice::device_type_2_device_name(device_type).c_str());
return false;
LOG_WARNING(F("Ignore call for command %s in %s because it has no json body"), cmd, EMSdevice::device_type_2_device_name(device_type).c_str());
return CommandRet::ERROR;
}
// this is for endpoints that don't have commands, i.e not writable (e.g. boiler/syspress)
if (cf == nullptr) {
return EMSESP::get_device_value_info(json, cmd_new, id_new, device_type);
return EMSESP::get_device_value_info(json, cmd_new, id_new, device_type) ? CommandRet::OK : CommandRet::ERROR;
}
if (cf->cmdfunction_json_) {
return ((cf->cmdfunction_json_)(value, id_new, json));
return ((cf->cmdfunction_json_)(value, id_new, json)) ? CommandRet::OK : CommandRet::ERROR;
} else {
if ((device_type != EMSdevice::DeviceType::SYSTEM) && (value == nullptr || strlen(value) == 0 || strcmp(value, "?") == 0 || strcmp(value, "*") == 0)) {
return EMSESP::get_device_value_info(json, cmd_new, id_new, device_type);
return EMSESP::get_device_value_info(json, cmd_new, id_new, device_type) ? CommandRet::OK : CommandRet::ERROR;
}
return ((cf->cmdfunction_)(value, id_new));
return ((cf->cmdfunction_)(value, id_new)) ? CommandRet::OK : CommandRet::ERROR;
}
}
// strip prefixes, check, and find command
Command::CmdFunction * Command::find_command(const uint8_t device_type, char * cmd, int8_t & id) {
// TODO special cases for id=0 and id=-1 will be removed in V3 API
// no command for id0
if (id == 0) {
return nullptr;
}
// empty command is info with id0
if (cmd[0] == '\0') {
strcpy(cmd, "info");
id = 0;
}
// convert cmd to lowercase
for (char * p = cmd; *p; p++) {
*p = tolower(*p);
}
// TODO hack for commands that could have hc or wwc prefixed. will be removed in new API V3 eventually
// scan for prefix hc.
for (uint8_t i = DeviceValueTAG::TAG_HC1; i <= DeviceValueTAG::TAG_HC4; i++) {
const char * tag = EMSdevice::tag_to_string(i).c_str();
@@ -151,33 +166,39 @@ Command::CmdFunction * Command::find_command(const uint8_t device_type, char * c
}
// add a command to the list, which does not return json
void Command::add(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb, const __FlashStringHelper * description, uint8_t flag) {
// these commands are not callable directly via MQTT subscriptions either
void Command::add(const uint8_t device_type, const __FlashStringHelper * cmd, const cmd_function_p cb, const __FlashStringHelper * description, uint8_t flags) {
// if the command already exists for that device type don't add it
if (find_command(device_type, uuid::read_flash_string(cmd).c_str()) != nullptr) {
return;
}
// if the description is empty, it's hidden which means it will not show up in Web or Console as an available command
bool hidden = (description == nullptr);
// if the description is empty, it's hidden which means it will not show up in Web API or Console as an available command
if (description == nullptr) {
flags |= CommandFlag::HIDDEN;
}
cmdfunctions_.emplace_back(device_type, flag, cmd, cb, nullptr, description, hidden); // callback for json is nullptr
cmdfunctions_.emplace_back(device_type, flags, cmd, cb, nullptr, description); // callback for json is nullptr
// see if we need to subscribe
if (Mqtt::enabled()) {
Mqtt::register_command(device_type, cmd, cb, flag);
Mqtt::register_command(device_type, cmd, cb, flags);
}
}
// add a command to the list, which does return json object as output
// flag is fixed
// optional parameter hidden for commands that will not show up on the Console
void Command::add_with_json(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_json_p cb, const __FlashStringHelper * description, bool hidden) {
// add a command to the list, which does return a json object as output
// flag is fixed to MqttSubFlag::FLAG_NOSUB
void Command::add_json(const uint8_t device_type,
const __FlashStringHelper * cmd,
const cmd_json_function_p cb,
const __FlashStringHelper * description,
uint8_t flags) {
// if the command already exists for that device type don't add it
if (find_command(device_type, uuid::read_flash_string(cmd).c_str()) != nullptr) {
return;
}
cmdfunctions_.emplace_back(device_type, MqttSubFlag::FLAG_NOSUB, cmd, nullptr, cb, description, hidden); // callback for json is included
cmdfunctions_.emplace_back(device_type, CommandFlag::MQTT_SUB_FLAG_NOSUB | flags, cmd, nullptr, cb, description); // callback for json is included
}
// see if a command exists for that device type
@@ -213,7 +234,7 @@ bool Command::list(const uint8_t device_type, JsonObject & json) {
// create a list of commands, sort them
std::list<std::string> sorted_cmds;
for (const auto & cf : cmdfunctions_) {
if ((cf.device_type_ == device_type) && !cf.hidden_) {
if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN)) {
sorted_cmds.push_back(uuid::read_flash_string(cf.cmd_));
}
}
@@ -221,7 +242,7 @@ bool Command::list(const uint8_t device_type, JsonObject & json) {
for (auto & cl : sorted_cmds) {
for (const auto & cf : cmdfunctions_) {
if ((cf.device_type_ == device_type) && !cf.hidden_ && cf.description_ && (cl == uuid::read_flash_string(cf.cmd_))) {
if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN) && cf.description_ && (cl == uuid::read_flash_string(cf.cmd_))) {
json[cl] = cf.description_;
}
}
@@ -240,7 +261,7 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
// create a list of commands, sort them
std::list<std::string> sorted_cmds;
for (const auto & cf : cmdfunctions_) {
if ((cf.device_type_ == device_type) && !cf.hidden_) {
if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN)) {
sorted_cmds.push_back(uuid::read_flash_string(cf.cmd_));
}
}
@@ -261,13 +282,13 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
for (auto & cl : sorted_cmds) {
// find and print the description
for (const auto & cf : cmdfunctions_) {
if ((cf.device_type_ == device_type) && !cf.hidden_ && cf.description_ && (cl == uuid::read_flash_string(cf.cmd_))) {
if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN) && cf.description_ && (cl == uuid::read_flash_string(cf.cmd_))) {
uint8_t i = cl.length();
shell.print(" ");
if (cf.flag_ == FLAG_HC) {
if (cf.has_flags(MQTT_SUB_FLAG_HC)) {
shell.print("[hc] ");
i += 5;
} else if (cf.flag_ == FLAG_WWC) {
} else if (cf.has_flags(MQTT_SUB_FLAG_WWC)) {
shell.print("[wwc] ");
i += 6;
}
@@ -278,6 +299,11 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
}
shell.print(COLOR_BRIGHT_CYAN);
shell.print(uuid::read_flash_string(cf.description_));
if (cf.has_flags(CommandFlag::ADMIN_ONLY)) {
shell.print(' ');
shell.print(COLOR_BRIGHT_RED);
shell.print('*');
}
shell.print(COLOR_RESET);
}
}
@@ -337,7 +363,7 @@ void Command::show_devices(uuid::console::Shell & shell) {
// output list of all commands to console
// calls show with verbose mode set
void Command::show_all(uuid::console::Shell & shell) {
shell.println(F("Available commands per device: "));
shell.println(F("Available commands: "));
// show system first
shell.print(COLOR_BOLD_ON);

View File

@@ -34,34 +34,65 @@ using uuid::console::Shell;
namespace emsesp {
using cmdfunction_p = std::function<bool(const char * data, const int8_t id)>;
using cmdfunction_json_p = std::function<bool(const char * data, const int8_t id, JsonObject & json)>;
// mqtt flags for command subscriptions
enum CommandFlag : uint8_t {
MQTT_SUB_FLAG_NORMAL = 0, // 0
MQTT_SUB_FLAG_HC = (1 << 0), // 1
MQTT_SUB_FLAG_WWC = (1 << 1), // 2
MQTT_SUB_FLAG_NOSUB = (1 << 2), // 4
HIDDEN = (1 << 3), // 8
ADMIN_ONLY = (1 << 4) // 16
};
// return status after calling a Command
enum CommandRet : uint8_t {
ERRORED = 0,
OK, // 1 or TRUE
NOT_FOUND, // 2
ERROR, // 3
NOT_ALLOWED // needs authentication
};
using cmd_function_p = std::function<bool(const char * data, const int8_t id)>;
using cmd_json_function_p = std::function<bool(const char * data, const int8_t id, JsonObject & json)>;
class Command {
public:
struct CmdFunction {
uint8_t device_type_; // DeviceType::
uint8_t flag_; // mqtt flags for command subscriptions
uint8_t flags_; // mqtt flags for command subscriptions
const __FlashStringHelper * cmd_;
cmdfunction_p cmdfunction_;
cmdfunction_json_p cmdfunction_json_;
const cmd_function_p cmdfunction_;
const cmd_json_function_p cmdfunction_json_;
const __FlashStringHelper * description_;
bool hidden_; // if its command not to be shown on the Console
CmdFunction(const uint8_t device_type,
const uint8_t flag,
const uint8_t flags,
const __FlashStringHelper * cmd,
cmdfunction_p cmdfunction,
cmdfunction_json_p cmdfunction_json,
const __FlashStringHelper * description,
bool hidden = false)
const cmd_function_p cmdfunction,
const cmd_json_function_p cmdfunction_json,
const __FlashStringHelper * description)
: device_type_(device_type)
, flag_(flag)
, flags_(flags)
, cmd_(cmd)
, cmdfunction_(cmdfunction)
, cmdfunction_json_(cmdfunction_json)
, description_(description)
, hidden_(hidden) {
, description_(description) {
}
inline void add_flags(uint8_t flags) {
flags_ |= flags;
}
inline bool has_flags(uint8_t flags) const {
return (flags_ & flags) == flags;
}
inline void remove_flags(uint8_t flags) {
flags_ &= ~flags;
}
inline uint8_t flags() const {
return flags_;
}
};
@@ -69,11 +100,21 @@ class Command {
return cmdfunctions_;
}
static bool call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id, JsonObject & json);
static bool call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id = -1);
static void add(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb, const __FlashStringHelper * description, uint8_t flag = 0);
static void
add_with_json(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_json_p cb, const __FlashStringHelper * description, bool hidden = false);
static uint8_t call(const uint8_t device_type, const char * cmd, const char * value, bool authenticated, const int8_t id, JsonObject & json);
static uint8_t call(const uint8_t device_type, const char * cmd, const char * value, bool authenticated, const int8_t id = -1);
static void add(const uint8_t device_type,
const __FlashStringHelper * cmd,
const cmd_function_p cb,
const __FlashStringHelper * description,
uint8_t flags = CommandFlag::MQTT_SUB_FLAG_NORMAL);
static void add_json(const uint8_t device_type,
const __FlashStringHelper * cmd,
const cmd_json_function_p cb,
const __FlashStringHelper * description,
uint8_t flags = CommandFlag::MQTT_SUB_FLAG_NORMAL);
static void show_all(uuid::console::Shell & shell);
static Command::CmdFunction * find_command(const uint8_t device_type, const char * cmd);
static Command::CmdFunction * find_command(const uint8_t device_type, char * cmd, int8_t & id);

View File

@@ -376,15 +376,8 @@ void EMSESPShell::add_console_commands() {
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN);
JsonObject json = doc.to<JsonObject>();
bool ok = false;
// validate that a command is present
if (arguments.size() < 2) {
// // no cmd specified, default to empty command
// if (Command::call(device_type, "", "", -1, json)) {
// serializeJsonPretty(doc, shell);
// shell.println();
// return;
// }
shell.print(F("Missing command. Available commands are: "));
Command::show(shell, device_type, false); // non-verbose mode
return;
@@ -392,30 +385,36 @@ void EMSESPShell::add_console_commands() {
const char * cmd = arguments[1].c_str();
uint8_t cmd_return = CommandRet::OK;
if (arguments.size() == 2) {
// no value specified, just the cmd
ok = Command::call(device_type, cmd, nullptr, -1, json);
cmd_return = Command::call(device_type, cmd, nullptr, true, -1, json);
} else if (arguments.size() == 3) {
if (strncmp(cmd, "info", 4) == 0) {
// info has a id but no value
ok = Command::call(device_type, cmd, nullptr, atoi(arguments.back().c_str()), json);
cmd_return = Command::call(device_type, cmd, nullptr, true, atoi(arguments.back().c_str()), json);
} else {
// has a value but no id
ok = Command::call(device_type, cmd, arguments.back().c_str(), -1, json);
// has a value but no id so use -1
cmd_return = Command::call(device_type, cmd, arguments.back().c_str(), true, -1, json);
}
} else {
// use value, which could be an id or hc
ok = Command::call(device_type, cmd, arguments[2].c_str(), atoi(arguments[3].c_str()), json);
cmd_return = Command::call(device_type, cmd, arguments[2].c_str(), true, atoi(arguments[3].c_str()), json);
}
if (ok && json.size()) {
if (cmd_return == CommandRet::OK && json.size()) {
serializeJsonPretty(doc, shell);
shell.println();
return;
} else if (!ok) {
shell.println(F("Unknown command, value, or id."));
}
if (cmd_return == CommandRet::NOT_FOUND) {
shell.println(F("Unknown command"));
shell.print(F("Available commands are: "));
Command::show(shell, device_type, false); // non-verbose mode
} else if (cmd_return != CommandRet::OK) {
shell.println(F("Bad syntax"));
}
},
[&](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) -> std::vector<std::string> {
@@ -711,6 +710,47 @@ void Console::load_system_commands(unsigned int context) {
});
});
EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN,
flash_string_vector{F_(sensorname)},
flash_string_vector{F_(sensorid_optional), F_(name_optional), F_(offset_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.size() == 0) {
EMSESP::webSettingsService.read([&](WebSettings & settings) {
for (uint8_t i = 0; i < NUM_SENSOR_NAMES; i++) {
if (!settings.sensor[i].id.isEmpty()) {
shell.print(settings.sensor[i].id);
shell.print(" : ");
shell.print(settings.sensor[i].name);
shell.print(" : ");
char buf[10];
shell.println(Helpers::render_value(buf, settings.sensor[i].offset, 10));
}
}
});
return;
}
if (arguments.size() == 1) {
EMSESP::dallassensor_.update(arguments.front().c_str(), "", 0);
// shell.println(EMSESP::dallassensor_.get_name(arguments.front().c_str()));
return;
}
int16_t offset = 0;
float val;
if (arguments.size() == 2) {
if (Helpers::value2float(arguments.back().c_str(), val)) {
offset = (10 * val);
EMSESP::dallassensor_.update(arguments.front().c_str(), "", offset);
return;
}
} else if (arguments.size() == 3) {
if (Helpers::value2float(arguments.back().c_str(), val)) {
offset = (10 * val);
}
}
EMSESP::dallassensor_.update(arguments.front().c_str(), arguments[1].c_str(), offset);
});
EMSESPShell::commands
->add_command(context,
CommandFlags::ADMIN,

View File

@@ -41,12 +41,12 @@ void DallasSensor::start() {
bus_.begin(dallas_gpio_);
#endif
// API calls
Command::add_with_json(
Command::add_json(
EMSdevice::DeviceType::DALLASSENSOR,
F_(info),
[&](const char * value, const int8_t id, JsonObject & json) { return command_info(value, id, json); },
F_(info_cmd));
Command::add_with_json(
Command::add_json(
EMSdevice::DeviceType::DALLASSENSOR,
F_(commands),
[&](const char * value, const int8_t id, JsonObject & json) { return command_commands(value, id, json); },
@@ -57,8 +57,9 @@ void DallasSensor::start() {
// load the MQTT settings
void DallasSensor::reload() {
EMSESP::webSettingsService.read([&](WebSettings & settings) {
dallas_gpio_ = settings.dallas_gpio;
parasite_ = settings.dallas_parasite;
dallas_gpio_ = settings.dallas_gpio;
parasite_ = settings.dallas_parasite;
dallas_format_ = settings.dallas_format;
});
if (Mqtt::ha_enabled()) {
@@ -139,7 +140,11 @@ void DallasSensor::loop() {
bool found = false;
for (auto & sensor : sensors_) {
if (sensor.id() == get_id(addr)) {
changed_ |= (t != sensor.temperature_c);
t += sensor.offset();
if (t != sensor.temperature_c) {
sensor.temperature_c = t;
changed_ |= true;
}
sensor.temperature_c = t;
sensor.read = true;
found = true;
@@ -149,7 +154,7 @@ void DallasSensor::loop() {
// add new sensor
if (!found && (sensors_.size() < (MAX_SENSORS - 1))) {
sensors_.emplace_back(addr);
sensors_.back().temperature_c = t;
sensors_.back().temperature_c = t + sensors_.back().offset();
sensors_.back().read = true;
changed_ = true;
}
@@ -183,7 +188,7 @@ void DallasSensor::loop() {
scancnt_ = 0;
} else if (scancnt_ == SCAN_START + 1) { // startup
firstscan_ = sensors_.size();
LOG_DEBUG(F("Adding %d dallassensor(s) from first scan"), firstscan_);
LOG_DEBUG(F("Adding %d dallas sensor(s) from first scan"), firstscan_);
} else if ((scancnt_ <= 0) && (firstscan_ != sensors_.size())) { // check 2 times for no change of sensor #
scancnt_ = SCAN_START;
sensors_.clear(); // restart scaning and clear to get correct numbering
@@ -293,7 +298,7 @@ uint64_t DallasSensor::Sensor::id() const {
return id_;
}
std::string DallasSensor::Sensor::to_string() const {
std::string DallasSensor::Sensor::id_string() const {
std::string str(20, '\0');
snprintf_P(&str[0],
str.capacity() + 1,
@@ -305,6 +310,133 @@ std::string DallasSensor::Sensor::to_string() const {
return str;
}
std::string DallasSensor::Sensor::to_string(const bool name) const {
std::string str = id_string();
EMSESP::webSettingsService.read([&](WebSettings & settings) {
if (settings.dallas_format == Dallas_Format::NAME || name) {
for (uint8_t i = 0; i < NUM_SENSOR_NAMES; i++) {
if (strcmp(settings.sensor[i].id.c_str(), str.c_str()) == 0) {
str = settings.sensor[i].name.c_str();
}
}
}
});
return str;
}
int16_t DallasSensor::Sensor::offset() const {
std::string str = id_string();
int16_t offset = 0; // default value
EMSESP::webSettingsService.read([&](WebSettings & settings) {
for (uint8_t i = 0; i < NUM_SENSOR_NAMES; i++) {
if (strcmp(settings.sensor[i].id.c_str(), str.c_str()) == 0) {
offset = settings.sensor[i].offset;
}
}
});
return offset;
}
// if HA enabled with MQTT Discovery, delete the old config entry by sending an empty topic
// if we're using the name in the MQTT topic name (Dallas format = NAME)
void DallasSensor::delete_ha_config(uint8_t index, const char * name) {
if (Mqtt::ha_enabled() && (dallas_format_ == Dallas_Format::NAME)) {
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
// use '_' as HA doesn't like '-' in the topic name
std::string topicname = name;
std::replace(topicname.begin(), topicname.end(), '-', '_');
snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/dallassensor_%s/config"), Mqtt::base().c_str(), topicname.c_str());
Mqtt::publish(topic);
registered_ha_[index] = false; // forces a recreate of the HA config topic
}
}
// update dallas information like name and offset
bool DallasSensor::update(const char * idstr, const char * name, int16_t offset) {
bool ok = false;
char id[20];
strlcpy(id, idstr, sizeof(id));
// check for number and convert to id
if (strlen(id) > 0 && strlen(id) <= 2 && id[0] >= '1' && id[0] <= '9') {
uint8_t no = atoi(idstr) - 1;
if (no < sensors_.size()) {
strlcpy(id, sensors_[no].id_string().c_str(), sizeof(id));
}
}
// check valid id
if (strlen(id) != 17 || id[2] != '-' || id[7] != '-' || id[12] != '-') {
LOG_WARNING(F("Invalid sensor id: %s"), id);
return ok;
}
EMSESP::webSettingsService.update(
[&](WebSettings & settings) {
// check for new name of stored id
for (uint8_t i = 0; i < NUM_SENSOR_NAMES; i++) {
if (strcmp(id, settings.sensor[i].id.c_str()) == 0) {
if (strlen(name) == 0 && offset == 0) { // delete entry if name and offset is empty
LOG_INFO(F("Deleting entry for sensor %s"), id);
delete_ha_config(i, settings.sensor[i].name.c_str());
settings.sensor[i].id = "";
settings.sensor[i].name = "";
settings.sensor[i].offset = 0;
} else {
char result[10];
LOG_INFO(F("Renaming sensor ID %s to %s with offset %s"), id, name, Helpers::render_value(result, offset, 10));
delete_ha_config(i, settings.sensor[i].name.c_str()); // remove old name in HA
settings.sensor[i].name = (strlen(name) == 0) ? id : name;
settings.sensor[i].offset = offset;
}
ok = true;
return StateUpdateResult::CHANGED;
}
}
// check for free place
for (uint8_t i = 0; i < NUM_SENSOR_NAMES; i++) {
if (settings.sensor[i].id.isEmpty()) {
settings.sensor[i].id = id;
settings.sensor[i].name = (strlen(name) == 0) ? id : name;
settings.sensor[i].offset = offset;
char result[10];
LOG_INFO(F("Adding sensor ID %s to %s with offset %s"), id, name, Helpers::render_value(result, offset, 10));
ok = true;
return StateUpdateResult::CHANGED;
}
}
// check if there is a unused id and overwrite it
for (uint8_t i = 0; i < NUM_SENSOR_NAMES; i++) {
bool found = false;
for (const auto & sensor : sensors_) {
if (strcmp(sensor.id_string().c_str(), settings.sensor[i].id.c_str()) == 0) {
found = true;
}
}
if (!found) {
char result[10];
LOG_INFO(F("Renaming sensor ID %s to %s with offset %s"), id, name, Helpers::render_value(result, offset, 10));
delete_ha_config(i, settings.sensor[i].name.c_str()); // remove old name in HA
settings.sensor[i].id = id;
settings.sensor[i].name = (strlen(name) == 0) ? id : name;
settings.sensor[i].offset = offset;
ok = true;
return StateUpdateResult::CHANGED;
}
}
LOG_ERROR(F("No more empty sensor slots, remove one first"));
return StateUpdateResult::UNCHANGED;
},
"local");
return ok;
}
// check to see if values have been updated
bool DallasSensor::updated_values() {
if (changed_) {
@@ -338,10 +470,10 @@ bool DallasSensor::command_info(const char * value, const int8_t id, JsonObject
dataSensor["temp"] = (float)(sensor.temperature_c) / 10;
}
} else { // show according to format
if (Mqtt::dallas_format() == Mqtt::Dallas_Format::SENSORID && Helpers::hasValue(sensor.temperature_c)) {
json[sensor.to_string()] = (float)(sensor.temperature_c) / 10;
} else if (Helpers::hasValue(sensor.temperature_c)) {
if (dallas_format_ == Dallas_Format::NUMBER && Helpers::hasValue(sensor.temperature_c)) {
json[sensorID] = (float)(sensor.temperature_c) / 10;
} else if (Helpers::hasValue(sensor.temperature_c)) {
json[sensor.to_string()] = (float)(sensor.temperature_c) / 10;
}
}
}
@@ -360,29 +492,22 @@ void DallasSensor::publish_values(const bool force) {
DynamicJsonDocument doc(100 * num_sensors);
uint8_t sensor_no = 1;
// dallas format is overriden when using Home Assistant
// uint8_t dallas_format = Mqtt::ha_enabled() ? Mqtt::Dallas_Format::NUMBER : Mqtt::dallas_format();
uint8_t dallas_format = Mqtt::dallas_format();
for (const auto & sensor : sensors_) {
char sensorID[10]; // sensor{1-n}
snprintf_P(sensorID, 10, PSTR("sensor%d"), sensor_no);
if (dallas_format == Mqtt::Dallas_Format::SENSORID) {
// e.g. dallassensor_data = {"28-EA41-9497-0E03":23.3,"28-233D-9497-0C03":24.0}
if (Helpers::hasValue(sensor.temperature_c)) {
doc[sensor.to_string()] = (float)(sensor.temperature_c) / 10;
}
} else if (dallas_format == Mqtt::Dallas_Format::NUMBER) {
if (dallas_format_ == Dallas_Format::NUMBER) {
// e.g. dallassensor_data = {"sensor1":{"id":"28-EA41-9497-0E03","temp":23.3},"sensor2":{"id":"28-233D-9497-0C03","temp":24.0}}
JsonObject dataSensor = doc.createNestedObject(sensorID);
dataSensor["id"] = sensor.to_string();
if (Helpers::hasValue(sensor.temperature_c)) {
dataSensor["temp"] = (float)(sensor.temperature_c) / 10;
}
} else if (Helpers::hasValue(sensor.temperature_c)) {
doc[sensor.to_string()] = (float)(sensor.temperature_c) / 10;
}
// create the HA MQTT config
// to e.g. homeassistant/sensor/ems-esp/dallas_28-233D-9497-0C03/config
// to e.g. homeassistant/sensor/ems-esp/dallassensor_28-233D-9497-0C03/config
if (Mqtt::ha_enabled()) {
if (!(registered_ha_[sensor_no - 1]) || force) {
StaticJsonDocument<EMSESP_JSON_SIZE_MEDIUM> config;
@@ -395,7 +520,7 @@ void DallasSensor::publish_values(const bool force) {
config["unit_of_meas"] = FJSON("°C");
char str[50];
if (dallas_format == Mqtt::Dallas_Format::SENSORID) {
if (dallas_format_ != Dallas_Format::NUMBER) {
snprintf_P(str, sizeof(str), PSTR("{{value_json['%s']}}"), sensor.to_string().c_str());
} else {
snprintf_P(str, sizeof(str), PSTR("{{value_json.sensor%d.temp}}"), sensor_no);
@@ -403,14 +528,14 @@ void DallasSensor::publish_values(const bool force) {
config["val_tpl"] = str;
// name as sensor number not the long unique ID
if (dallas_format == Mqtt::Dallas_Format::SENSORID) {
if (dallas_format_ != Dallas_Format::NUMBER) {
snprintf_P(str, sizeof(str), PSTR("Dallas Sensor %s"), sensor.to_string().c_str());
} else {
snprintf_P(str, sizeof(str), PSTR("Dallas Sensor %d"), sensor_no);
}
config["name"] = str;
snprintf_P(str, sizeof(str), PSTR("dallas_%s"), sensor.to_string().c_str());
snprintf_P(str, sizeof(str), PSTR("dallasensor_%s"), sensor.to_string().c_str());
config["uniq_id"] = str;
JsonObject dev = config.createNestedObject("dev");
@@ -421,13 +546,13 @@ void DallasSensor::publish_values(const bool force) {
ids.add("ems-esp-dallas"); // Different ids as the other portions of the EMS-ESP
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
if (dallas_format == Mqtt::Dallas_Format::SENSORID) {
if (dallas_format_ == Dallas_Format::NUMBER) {
snprintf_P(topic, sizeof(topic), PSTR("sensor/%s/dallassensor_%d/config"), Mqtt::base().c_str(), sensor_no);
} else {
// use '_' as HA doesn't like '-' in the topic name
std::string topicname = sensor.to_string();
std::replace(topicname.begin(), topicname.end(), '-', '_');
snprintf_P(topic, sizeof(topic), PSTR("sensor/%s/dallas_sensor%s/config"), Mqtt::base().c_str(), topicname.c_str());
} else {
snprintf_P(topic, sizeof(topic), PSTR("sensor/%s/dallas_sensor%d/config"), Mqtt::base().c_str(), sensor_no);
snprintf_P(topic, sizeof(topic), PSTR("sensor/%s/dallassensor_%s/config"), Mqtt::base().c_str(), topicname.c_str());
}
Mqtt::publish_ha(topic, config.as<JsonObject>());

View File

@@ -36,6 +36,8 @@
namespace emsesp {
enum Dallas_Format : uint8_t { SENSORID = 1, NUMBER, NAME };
class DallasSensor {
public:
class Sensor {
@@ -44,7 +46,9 @@ class DallasSensor {
~Sensor() = default;
uint64_t id() const;
std::string to_string() const;
std::string id_string() const;
std::string to_string(const bool name = false) const;
int16_t offset() const;
int16_t temperature_c = EMS_VALUE_SHORT_NOTSET;
bool read = false;
@@ -76,6 +80,16 @@ class DallasSensor {
return (dallas_gpio_ != 0);
}
uint8_t dallas_format() {
return dallas_format_;
}
void dallas_format(uint8_t dallas_format) {
dallas_format_ = dallas_format;
}
bool update(const char * idstr, const char * name, int16_t offset);
private:
static constexpr uint8_t MAX_SENSORS = 20;
@@ -119,21 +133,23 @@ class DallasSensor {
bool command_info(const char * value, const int8_t id, JsonObject & json);
bool command_commands(const char * value, const int8_t id, JsonObject & json);
void delete_ha_config(uint8_t index, const char * name);
uint32_t last_activity_ = uuid::get_uptime();
uint32_t last_publish_ = uuid::get_uptime();
State state_ = State::IDLE;
std::vector<Sensor> sensors_;
bool registered_ha_[MAX_SENSORS];
int8_t scancnt_ = SCAN_START;
uint8_t firstscan_ = 0;
uint8_t dallas_gpio_ = 0;
bool parasite_ = false;
bool changed_ = false;
uint32_t sensorfails_ = 0;
uint32_t sensorreads_ = 0;
int8_t scanretry_ = 0;
int8_t scancnt_ = SCAN_START;
uint8_t firstscan_ = 0;
uint8_t dallas_gpio_ = 0;
bool parasite_ = false;
bool changed_ = false;
uint32_t sensorfails_ = 0;
uint32_t sensorreads_ = 0;
int8_t scanretry_ = 0;
uint8_t dallas_format_ = 0;
};
} // namespace emsesp

View File

@@ -84,6 +84,10 @@
#define EMSESP_DEFAULT_BOOL_FORMAT 1 // on/off
#endif
#ifndef EMSESP_DEFAULT_ENUM_FORMAT
#define EMSESP_DEFAULT_ENUM_FORMAT 1 // Text
#endif
#ifndef EMSESP_DEFAULT_ANALOG_ENABLED
#define EMSESP_DEFAULT_ANALOG_ENABLED false
#endif
@@ -156,4 +160,8 @@
#define EMSESP_DEFAULT_SOLAR_MAXFLOW 30
#endif
#ifndef EMSESP_DEFAULT_SENSOR_NAME
#define EMSESP_DEFAULT_SENSOR_NAME ""
#endif
#endif

View File

@@ -32,7 +32,7 @@
{122, DeviceType::BOILER, F("Proline"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{123, DeviceType::BOILER, F("GBx72/Trendline/Cerapur/Greenstar Si/27i"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{131, DeviceType::BOILER, F("GB212"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{133, DeviceType::BOILER, F("GB125/Logamatic MC110"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{133, DeviceType::BOILER, F("Logano GB125/KB195i/Logamatic MC110"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{167, DeviceType::BOILER, F("Cerapur Aero"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{170, DeviceType::BOILER, F("Logano GB212"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{172, DeviceType::BOILER, F("Enviline/Compress 6000AW/Hybrid 7000iAW/SupraEco"), DeviceFlags::EMS_DEVICE_FLAG_HEATPUMP},

View File

@@ -84,17 +84,22 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_telegram_type(0x48D, F("HpPower"), false, MAKE_PF_CB(process_HpPower));
register_telegram_type(0x48F, F("HpOutdoor"), false, MAKE_PF_CB(process_HpOutdoor));
}
// MQTT commands for boiler topic
register_device_value(
TAG_BOILER_DATA, &dummybool_, DeviceValueType::BOOL, nullptr, FL_(wwtapactivated), DeviceValueUOM::BOOLEAN, MAKE_CF_CB(set_tapwarmwater_activated));
register_device_value(TAG_BOILER_DATA, &dummy8u_, DeviceValueType::ENUM, FL_(enum_reset), FL_(reset), DeviceValueUOM::NONE, MAKE_CF_CB(set_reset));
// add values
// reserve_device_values(90);
// main - boiler_data topic
register_device_value(TAG_BOILER_DATA, &id_, DeviceValueType::UINT, nullptr, FL_(ID), DeviceValueUOM::NONE);
id_ = product_id;
id_ = product_id; // note, must set the value after it has been initialized to have affect
// first commands
register_device_value(TAG_BOILER_DATA,
&wWTapActivated_,
DeviceValueType::CMD,
FL_(enum_bool),
FL_(wwtapactivated),
DeviceValueUOM::BOOLEAN,
MAKE_CF_CB(set_tapwarmwater_activated));
// reset is a command, so uses a dummy variable which is unused. It will not be shown in MQTT, Web or Console
register_device_value(TAG_BOILER_DATA, &dummy8u_, DeviceValueType::CMD, FL_(enum_reset), FL_(reset), DeviceValueUOM::LIST, MAKE_CF_CB(set_reset));
register_device_value(TAG_BOILER_DATA, &heatingActive_, DeviceValueType::BOOL, nullptr, FL_(heatingActive), DeviceValueUOM::BOOLEAN);
register_device_value(TAG_BOILER_DATA, &tapwaterActive_, DeviceValueType::BOOL, nullptr, FL_(tapwaterActive), DeviceValueUOM::BOOLEAN);
@@ -136,15 +141,17 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_device_value(TAG_BOILER_DATA, &serviceCode_, DeviceValueType::TEXT, nullptr, FL_(serviceCode), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA, &serviceCodeNumber_, DeviceValueType::USHORT, nullptr, FL_(serviceCodeNumber), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA, &maintenanceMessage_, DeviceValueType::TEXT, nullptr, FL_(maintenanceMessage), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA, &maintenanceDate_, DeviceValueType::TEXT, nullptr, FL_(maintenanceDate), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA,
&maintenanceType_,
DeviceValueType::ENUM,
FL_(enum_off_time_date),
FL_(maintenanceType),
DeviceValueUOM::NONE,
DeviceValueUOM::LIST,
MAKE_CF_CB(set_maintenance));
register_device_value(TAG_BOILER_DATA, &maintenanceTime_, DeviceValueType::USHORT, nullptr, FL_(maintenanceTime), DeviceValueUOM::HOURS);
register_device_value(
TAG_BOILER_DATA, &maintenanceTime_, DeviceValueType::USHORT, nullptr, FL_(maintenanceTime), DeviceValueUOM::HOURS, MAKE_CF_CB(set_maintenancetime));
register_device_value(
TAG_BOILER_DATA, &maintenanceDate_, DeviceValueType::TEXT, nullptr, FL_(maintenanceDate), DeviceValueUOM::NONE, MAKE_CF_CB(set_maintenancedate));
// heatpump info
if (model() == EMS_DEVICE_FLAG_HEATPUMP) {
register_device_value(TAG_BOILER_DATA, &upTimeControl_, DeviceValueType::TIME, FL_(div60), FL_(upTimeControl), DeviceValueUOM::MINUTES);
@@ -184,14 +191,16 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_device_value(TAG_BOILER_DATA_WW, &wWSetTemp_, DeviceValueType::UINT, nullptr, FL_(wWSetTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_warmwater_temp));
register_device_value(TAG_BOILER_DATA_WW, &wWType_, DeviceValueType::ENUM, FL_(enum_flow), FL_(wWType), DeviceValueUOM::NONE);
register_device_value(
TAG_BOILER_DATA_WW, &wWComfort_, DeviceValueType::ENUM, FL_(enum_comfort), FL_(wWComfort), DeviceValueUOM::NONE, MAKE_CF_CB(set_warmwater_mode));
TAG_BOILER_DATA_WW, &wWComfort_, DeviceValueType::ENUM, FL_(enum_comfort), FL_(wWComfort), DeviceValueUOM::LIST, MAKE_CF_CB(set_warmwater_mode));
register_device_value(
TAG_BOILER_DATA_WW, &wWFlowTempOffset_, DeviceValueType::UINT, nullptr, FL_(wWFlowTempOffset), DeviceValueUOM::NONE, MAKE_CF_CB(set_wWFlowTempOffset));
register_device_value(
TAG_BOILER_DATA_WW, &wWMaxPower_, DeviceValueType::UINT, nullptr, FL_(wWMaxPower), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_warmwater_maxpower));
register_device_value(
TAG_BOILER_DATA_WW, &wWCircPump_, DeviceValueType::BOOL, nullptr, FL_(wWCircPump), DeviceValueUOM::BOOLEAN, MAKE_CF_CB(set_warmwater_circulation_pump));
register_device_value(TAG_BOILER_DATA_WW, &wWChargeType_, DeviceValueType::ENUM, FL_(enum_charge), FL_(wWChargeType), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA_WW, &wWChargeType_, DeviceValueType::ENUM, FL_(enum_charge), FL_(wWChargeType), DeviceValueUOM::LIST);
register_device_value(TAG_BOILER_DATA_WW, &wWHystOn_, DeviceValueType::INT, nullptr, FL_(wWHystOn), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_ww_hyst_on));
register_device_value(TAG_BOILER_DATA_WW, &wWHystOff_, DeviceValueType::INT, nullptr, FL_(wWHystOff), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_ww_hyst_off));
register_device_value(TAG_BOILER_DATA_WW,
&wWDisinfectionTemp_,
DeviceValueType::UINT,
@@ -204,7 +213,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
DeviceValueType::ENUM,
FL_(enum_freq),
FL_(wWCircMode),
DeviceValueUOM::NONE,
DeviceValueUOM::LIST,
MAKE_CF_CB(set_warmwater_circulation_mode));
register_device_value(TAG_BOILER_DATA_WW, &wWCirc_, DeviceValueType::BOOL, nullptr, FL_(wWCirc), DeviceValueUOM::BOOLEAN, MAKE_CF_CB(set_warmwater_circulation));
register_device_value(TAG_BOILER_DATA_WW, &wWCurTemp_, DeviceValueType::USHORT, FL_(div10), FL_(wWCurTemp), DeviceValueUOM::DEGREES);
@@ -289,6 +298,11 @@ void Boiler::check_active(const bool force) {
Mqtt::publish(F_(heating_active), Helpers::render_boolean(s, b));
}
// check if we can use tapactivated in flow systems
if ((wWType_ == 1) && !Helpers::hasValue(wWTapActivated_, EMS_VALUE_BOOL)) {
wWTapActivated_ = 1;
}
// check if tap water is active, bits 1 and 4 must be set
// also check if there is a flowsensor and flow-type
static bool flowsensor = false;
@@ -315,8 +329,8 @@ void Boiler::process_UBAParameterWW(std::shared_ptr<const Telegram> telegram) {
// has_update(telegram->read_bitvalue(wwEquipt_,0,3)); // 8=boiler has ww
has_update(telegram->read_value(wWActivated_, 1)); // 0xFF means on
has_update(telegram->read_value(wWSelTemp_, 2));
// has_update(telegram->read_value(wW?_, 3)); // Hyst on (default -5)
// has_update(telegram->read_value(wW?_, 4)); // (0xFF) Maybe: Hyst off -1?
has_update(telegram->read_value(wWHystOn_, 3)); // Hyst on (default -5)
has_update(telegram->read_value(wWHystOff_, 4)); // Hyst off (default -1)
has_update(telegram->read_value(wWFlowTempOffset_, 5)); // default 40
has_update(telegram->read_value(wWCircPump_, 6)); // 0xFF means on
has_update(telegram->read_value(wWCircMode_, 7)); // 1=1x3min 6=6x3min 7=continuous
@@ -454,9 +468,6 @@ void Boiler::process_UBAMonitorFastPlus(std::shared_ptr<const Telegram> telegram
17)); // can be 0 if no sensor, handled in export_values
has_update(telegram->read_value(sysPress_, 21));
//has_update(telegram->read_value(temperatur_, 13)); // unknown temperature
//has_update(telegram->read_value(temperatur_, 27)); // unknown temperature
// read 3 char service code / installation status as appears on the display
if ((telegram->message_length > 3) && (telegram->offset == 0)) {
serviceCode_[0] = (serviceCode_[0] == '~') ? 0xF0 : serviceCode_[0];
@@ -526,7 +537,7 @@ void Boiler::process_UBAMonitorSlowPlus(std::shared_ptr<const Telegram> telegram
/*
* UBAParametersPlus - type 0xE6
* parameters originaly taken from
* parameters originally taken from
* https://github.com/Th3M3/buderus_ems-wiki/blob/master/Einstellungen%20des%20Regelger%C3%A4ts%20MC110.md
* 88 0B E6 00 01 46 00 00 46 0A 00 01 06 FA 0A 01 02 64 01 00 00 1E 00 3C 01 00 00 00 01 00 9A
* from: issue #732
@@ -540,7 +551,7 @@ void Boiler::process_UBAParametersPlus(std::shared_ptr<const Telegram> telegram)
has_update(telegram->read_value(boilHystOff_, 8));
has_update(telegram->read_value(boilHystOn_, 9));
has_update(telegram->read_value(burnMinPeriod_, 10));
// has_update(telegram->read_value(pumpType_, 11)); // guess, RC300 manual: powercontroled, pressurcontrolled 1-4?
// has_update(telegram->read_value(pumpType_, 11)); // guess, RC300 manual: power controlled, pressure controlled 1-4?
// has_update(telegram->read_value(pumpDelay_, 12)); // guess
// has_update(telegram->read_value(pumpModMax_, 13)); // guess
// has_update(telegram->read_value(pumpModMin_, 14)); // guess
@@ -554,6 +565,8 @@ void Boiler::process_UBAParameterWWPlus(std::shared_ptr<const Telegram> telegram
11)); // 1=1x3min... 6=6x3min, 7=continuous
// has_update(telegram->read_value(wWDisinfectTemp_, 12)); // settings, status in E9
// has_update(telegram->read_value(wWSelTemp_, 6)); // settings, status in E9
has_update(telegram->read_value(wWHystOn_, 7));
has_update(telegram->read_value(wWHystOff_, 8));
}
// 0xE9 - WW monitor ems+
@@ -902,6 +915,42 @@ bool Boiler::set_max_power(const char * value, const int8_t id) {
return true;
}
// set ww on hysteresis
bool Boiler::set_ww_hyst_on(const char * value, const int8_t id) {
int v = 0;
if (!Helpers::value2number(value, v)) {
LOG_WARNING(F("Set ww on hysteresis: Invalid value"));
return false;
}
LOG_INFO(F("Setting ww on hysteresis on to %d C"), v);
if (get_toggle_fetch(EMS_TYPE_UBAParameterWWPlus)) {
write_command(EMS_TYPE_UBAParameterWWPlus, 7, v, EMS_TYPE_UBAParameterWWPlus);
} else {
write_command(EMS_TYPE_UBAParameterWW, 3, v, EMS_TYPE_UBAParameterWW);
}
return true;
}
// set ww off hysteresis
bool Boiler::set_ww_hyst_off(const char * value, const int8_t id) {
int v = 0;
if (!Helpers::value2number(value, v)) {
LOG_WARNING(F("Set ww off hysteresis: Invalid value"));
return false;
}
LOG_INFO(F("Setting ww off hysteresis off to %d C"), v);
if (get_toggle_fetch(EMS_TYPE_UBAParameterWWPlus)) {
write_command(EMS_TYPE_UBAParameterWWPlus, 8, v, EMS_TYPE_UBAParameterWWPlus);
} else {
write_command(EMS_TYPE_UBAParameterWW, 4, v, EMS_TYPE_UBAParameterWW);
}
return true;
}
// set warm water max power
bool Boiler::set_warmwater_maxpower(const char * value, const int8_t id) {
int v = 0;
@@ -1063,17 +1112,12 @@ bool Boiler::set_warmwater_activated(const char * value, const int8_t id) {
LOG_INFO(F("Setting boiler warm water active %s"), v ? "on" : "off");
// https://github.com/emsesp/EMS-ESP/issues/268
uint8_t n;
if (EMSbus::is_ht3()) {
n = (v ? 0x08 : 0x00); // 0x08 is on, 0x00 is off
} else {
n = (v ? 0xFF : 0x00); // 0xFF is on, 0x00 is off
}
// 08 for HT3 seems to be wrong, see https://github.com/emsesp/EMS-ESP32/issues/89
if (get_toggle_fetch(EMS_TYPE_UBAParameterWWPlus)) {
write_command(EMS_TYPE_UBAParameterWWPlus, 1, v ? 1 : 0, EMS_TYPE_UBAParameterWWPlus);
} else {
write_command(EMS_TYPE_UBAParameterWW, 1, n, 0x34);
write_command(EMS_TYPE_UBAParameterWW, 1, v ? 0xFF : 0, EMS_TYPE_UBAParameterWW);
}
return true;
@@ -1098,14 +1142,16 @@ bool Boiler::set_tapwarmwater_activated(const char * value, const int8_t id) {
// a setting of 0x00 puts it back into normal operating mode
// when in test mode we're able to mess around with the 3-way valve settings
if (!v) {
// on
// DHW off
message_data[0] = 0x5A; // test mode on
message_data[1] = 0x00; // burner output 0%
message_data[3] = 0x64; // boiler pump capacity 100%
message_data[4] = 0xFF; // 3-way valve hot water only
wWTapActivated_ = 0;
} else {
// get out of test mode. Send all zeros.
// telegram: 0B 08 1D 00 00
wWTapActivated_ = 1;
}
write_command(EMS_TYPE_UBAFunctionTest, 0, message_data, sizeof(message_data), 0);
@@ -1268,5 +1314,40 @@ bool Boiler::set_maintenance(const char * value, const int8_t id) {
LOG_WARNING(F("Setting maintenance: wrong format"));
return false;
}
//maintenance
bool Boiler::set_maintenancetime(const char * value, const int8_t id) {
int hrs;
if (Helpers::value2number(value, hrs)) {
if (hrs > 99 && hrs < 25600) {
LOG_INFO(F("Setting maintenance time %d hours"), hrs);
uint8_t data[2] = {1, (uint8_t)(hrs / 100)};
write_command(0x15, 0, data, 2, 0x15);
return true;
}
}
LOG_WARNING(F("Setting maintenance: wrong format"));
return false;
}
//maintenance
bool Boiler::set_maintenancedate(const char * value, const int8_t id) {
if (strlen(value) == 10) { // date
uint8_t day = (value[0] - '0') * 10 + (value[1] - '0');
uint8_t month = (value[3] - '0') * 10 + (value[4] - '0');
uint8_t year = (uint8_t)(Helpers::atoint(&value[6]) - 2000);
if (day > 0 && day < 32 && month > 0 && month < 13) {
LOG_INFO(F("Setting maintenance date to %02d.%02d.%04d"), day, month, year + 2000);
uint8_t data[5] = {2, (uint8_t)(maintenanceTime_ / 100), day, month, year};
write_command(0x15, 0, data, 5, 0x15);
} else {
LOG_WARNING(F("Setting maintenance: wrong format %d.%d.%d"), day, month, year + 2000);
return false;
}
return true;
}
LOG_WARNING(F("Setting maintenance: wrong format"));
return false;
}
} // namespace emsesp

View File

@@ -49,7 +49,7 @@ class Boiler : public EMSdevice {
static constexpr uint8_t EMS_TYPE_UBAParameters = 0x16;
static constexpr uint8_t EMS_TYPE_UBAParametersPlus = 0xE6;
static constexpr uint8_t EMS_TYPE_UBAParameterWWPlus = 0xEA;
static constexpr uint16_t EMS_TYPE_UBAInfomration = 0x495;
static constexpr uint16_t EMS_TYPE_UBAInformation = 0x495;
static constexpr uint16_t EMS_TYPE_UBAEnergySupplied = 0x494;
static constexpr uint8_t EMS_BOILER_SELFLOWTEMP_HEATING = 20; // was originally 70, changed to 30 for issue #193, then to 20 with issue #344
@@ -83,6 +83,9 @@ class Boiler : public EMSdevice {
uint32_t wWStarts_; // Warm Water # starts
uint32_t wWStarts2_; // Warm water control starts
uint32_t wWWorkM_; // Warm Water # minutes
int8_t wWHystOn_;
int8_t wWHystOff_;
uint8_t wWTapActivated_; // maintenance-mode to switch DHW off
uint16_t mixerTemp_; // mixing temperature
uint16_t tankMiddleTemp_; // Tank middle temperature (TS3)
@@ -220,6 +223,10 @@ class Boiler : public EMSdevice {
bool set_pump_delay(const char * value, const int8_t id);
bool set_reset(const char * value, const int8_t id);
bool set_maintenance(const char * value, const int8_t id);
bool set_maintenancetime(const char * value, const int8_t id);
bool set_maintenancedate(const char * value, const int8_t id);
bool set_ww_hyst_on(const char * value, const int8_t id);
bool set_ww_hyst_off(const char * value, const int8_t id);
};
} // namespace emsesp

View File

@@ -91,11 +91,8 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
TAG_NONE, &solarPumpTurnonDiff_, DeviceValueType::UINT, nullptr, FL_(solarPumpTurnonDiff), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_TurnonDiff));
register_device_value(
TAG_NONE, &solarPumpTurnoffDiff_, DeviceValueType::UINT, nullptr, FL_(solarPumpTurnoffDiff), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_TurnoffDiff));
register_device_value(
TAG_NONE, &collectorMaxTemp_, DeviceValueType::UINT, nullptr, FL_(collectorMaxTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_CollectorMaxTemp));
register_device_value(
TAG_NONE, &collectorMinTemp_, DeviceValueType::UINT, nullptr, FL_(collectorMinTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_CollectorMinTemp));
register_device_value(TAG_NONE, &collectorShutdown_, DeviceValueType::BOOL, nullptr, FL_(collectorShutdown), DeviceValueUOM::BOOLEAN);
register_device_value(TAG_NONE, &tankHeated_, DeviceValueType::BOOL, nullptr, FL_(tankHeated), DeviceValueUOM::BOOLEAN);
register_device_value(TAG_NONE, &solarPower_, DeviceValueType::ULONG, nullptr, FL_(solarPower), DeviceValueUOM::W);
register_device_value(TAG_NONE, &energyLastHour_, DeviceValueType::ULONG, FL_(div10), FL_(energyLastHour), DeviceValueUOM::WH);
register_device_value(TAG_NONE, &maxFlow_, DeviceValueType::UINT, FL_(div10), FL_(maxFlow), DeviceValueUOM::LMIN, MAKE_CF_CB(set_SM10MaxFlow));
@@ -143,7 +140,7 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
// telegram 0x035A
register_device_value(
TAG_NONE, &solarPumpMode_, DeviceValueType::ENUM, FL_(enum_solarmode), FL_(solarPumpMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_solarMode));
TAG_NONE, &solarPumpMode_, DeviceValueType::ENUM, FL_(enum_solarmode), FL_(solarPumpMode), DeviceValueUOM::LIST, MAKE_CF_CB(set_solarMode));
register_device_value(TAG_NONE,
&solarPumpKick_,
DeviceValueType::BOOL,
@@ -180,7 +177,7 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
DeviceValueType::ENUM,
FL_(enum_collectortype),
FL_(collector1Type),
DeviceValueUOM::NONE,
DeviceValueUOM::LIST,
MAKE_CF_CB(set_collector1Type)); // Type of collector field 1, 01=flat, 02=vacuum
}
}
@@ -219,12 +216,6 @@ bool Solar::publish_ha_config() {
// Solar(0x30) -> All(0x00), (0x96), data: FF 18 19 0A 02 5A 27 0A 05 2D 1E 0F 64 28 0A
void Solar::process_SM10Config(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(solarIsEnabled_, 0)); // FF on
uint8_t colmax = collectorMaxTemp_ / 10;
has_update(telegram->read_value(colmax, 3));
collectorMaxTemp_ = colmax * 10;
uint8_t colmin = collectorMinTemp_ / 10;
has_update(telegram->read_value(colmin, 4));
collectorMinTemp_ = colmin * 10;
has_update(telegram->read_value(solarPumpMinMod_, 2));
has_update(telegram->read_value(solarPumpTurnonDiff_, 7));
has_update(telegram->read_value(solarPumpTurnoffDiff_, 8));
@@ -237,7 +228,7 @@ void Solar::process_SM10Monitor(std::shared_ptr<const Telegram> telegram) {
uint8_t solarpumpmod = solarPumpModulation_;
has_update(telegram->read_bitvalue(collectorShutdown_, 0, 3));
// has_update(telegram->read_bitvalue(tankHeated_, 0, x)); // tank full, to be determined
has_update(telegram->read_bitvalue(tankHeated_, 0, 2)); // tankMaxTemp reached
has_update(telegram->read_value(collectorTemp_, 2)); // collector temp from SM10, is *10
has_update(telegram->read_value(tankBottomTemp_, 5)); // tank bottom temp from SM10, is *10
has_update(telegram->read_value(solarPumpModulation_, 4)); // modulation solar pump
@@ -315,9 +306,12 @@ void Solar::process_SM100SolarCircuitConfig(std::shared_ptr<const Telegram> tele
* e.g. B0 0B F9 00 00 02 5A 00 00 6E
*/
void Solar::process_SM100ParamCfg(std::shared_ptr<const Telegram> telegram) {
uint16_t t_id;
uint8_t of;
int32_t min, def, max, cur;
uint16_t t_id = EMS_VALUE_USHORT_NOTSET;
uint8_t of = EMS_VALUE_UINT_NOTSET;
int32_t min = EMS_VALUE_USHORT_NOTSET;
int32_t def = EMS_VALUE_USHORT_NOTSET;
int32_t max = EMS_VALUE_USHORT_NOTSET;
int32_t cur = EMS_VALUE_USHORT_NOTSET;
has_update(telegram->read_value(t_id, 1));
has_update(telegram->read_value(of, 3));
has_update(telegram->read_value(min, 5));
@@ -346,7 +340,7 @@ void Solar::process_SM100Monitor(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(heatExchangerTemp_, 20)); // is *10 - TS6: Heat exchanger temperature sensor
}
// SM100wwTemperatur - 0x07D6
// SM100wwTemperature - 0x07D6
// Solar Module(0x2A) -> (0x00), (0x7D6), data: 01 C1 00 00 02 5B 01 AF 01 AD 80 00 01 90
void Solar::process_SM100wwTemperature(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(wwTemp_1_, 0));
@@ -426,9 +420,9 @@ void Solar::process_SM100Status2(std::shared_ptr<const Telegram> telegram) {
* e.g. B0 0B FF 00 02 80 50 64 00 00 29 01 00 00 01
*/
void Solar::process_SM100CollectorConfig(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(climateZone_, 0, 1));
has_update(telegram->read_value(collector1Area_, 3, 2));
has_update(telegram->read_value(collector1Type_, 5, 1));
has_update(telegram->read_value(climateZone_, 0));
has_update(telegram->read_value(collector1Area_, 3));
has_update(telegram->read_enumvalue(collector1Type_, 5, 1));
}
/*
@@ -685,7 +679,7 @@ bool Solar::set_collector1Type(const char * value, const int8_t id) {
if (!Helpers::value2enum(value, num, FL_(enum_collectortype))) {
return false;
}
write_command(0x380, 5, num, 0x380);
write_command(0x380, 5, num + 1, 0x380);
return true;
}

View File

@@ -87,7 +87,7 @@ class Solar : public EMSdevice {
// SM10Config - 0x96
uint8_t wwMinTemp_;
uint8_t maxFlow_; // set this to caltulate power
uint8_t maxFlow_; // set this to calculate power
uint32_t solarPower_; // calculated from maxFlow
std::deque<uint16_t> energy;

View File

@@ -455,13 +455,19 @@ bool Thermostat::thermostat_ha_cmd(const char * message, uint8_t hc_num) {
}
// check for mode first, which is a string
if (!set_mode(message, hc_num)) {
if (message[0] >= 'A') {
if (set_mode(message, hc_num)) {
return true;
}
}
if ((message[0] >= '0' && message[0] <= '9') || message[0] == '-') {
// otherwise handle as a numerical temperature value and set the setpoint temp
float f = strtof((char *)message, 0);
set_temperature(f, HeatingCircuit::Mode::AUTO, hc_num);
return true;
}
return true;
return false;
}
// decodes the thermostat mode for the heating circuit based on the thermostat type
@@ -696,6 +702,8 @@ void Thermostat::process_JunkersSet(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(hc->daytemp, 17)); // is * 2
has_update(telegram->read_value(hc->nighttemp, 16)); // is * 2
has_update(telegram->read_value(hc->nofrosttemp, 15)); // is * 2
has_update(telegram->read_enumvalue(hc->mode, 14, 1)); // 0 = nofrost, 1 = eco, 2 = heat, 3 = auto
hc->hamode = hc->mode ? hc->mode - 1 : 0; // set special HA mode: off, on, auto
}
// type 0x0179, ff
@@ -708,6 +716,8 @@ void Thermostat::process_JunkersSet2(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(hc->daytemp, 7)); // is * 2
has_update(telegram->read_value(hc->nighttemp, 6)); // is * 2
has_update(telegram->read_value(hc->nofrosttemp, 5)); // is * 2
has_update(telegram->read_enumvalue(hc->mode, 4, 1)); // 0 = nofrost, 1 = eco, 2 = heat, 3 = auto
hc->hamode = hc->mode ? hc->mode - 1 : 0; // set special HA mode: off, on, auto
}
// type 0xA3 - for external temp settings from the the RC* thermostats (e.g. RC35)
@@ -773,9 +783,7 @@ void Thermostat::process_JunkersMonitor(std::shared_ptr<const Telegram> telegram
has_update(telegram->read_value(hc->curr_roomTemp, 4)); // value is * 10
has_update(telegram->read_value(hc->setpoint_roomTemp, 2)); // value is * 10
has_update(telegram->read_value(hc->modetype, 0)); // 1 = nofrost, 2 = eco, 3 = heat
has_update(telegram->read_value(hc->mode, 1)); // 1 = manual, 2 = auto
hc->hamode = hc->mode; // set special HA mode
has_update(telegram->read_enumvalue(hc->modetype, 0, 1)); // 1 = nofrost, 2 = eco, 3 = heat
}
// type 0x02A5 - data from Worchester CRF200
@@ -917,7 +925,7 @@ void Thermostat::process_RC300OutdoorTemp(std::shared_ptr<const Telegram> telegr
// 0x240 RC300 parameter
void Thermostat::process_RC300Settings(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(ibaBuildingType_, 9)); // 1=light, 2=medium, 3=heavy
has_update(telegram->read_enumvalue(ibaBuildingType_, 9, 1)); // 1=light, 2=medium, 3=heavy
has_update(telegram->read_value(ibaMinExtTemperature_, 10));
}
@@ -1096,10 +1104,10 @@ void Thermostat::process_RCErrorMessage(std::shared_ptr<const Telegram> telegram
// data: displaycode(2), errornumber(2), year, month, hour, day, minute, duration(2), src-addr
if (telegram->message_data[4] & 0x80) { // valid date
char code[3];
uint16_t codeNo;
code[0] = telegram->message_data[0];
code[1] = telegram->message_data[1];
code[2] = 0;
uint16_t codeNo = EMS_VALUE_USHORT_NOTSET;
code[0] = telegram->message_data[0];
code[1] = telegram->message_data[1];
code[2] = 0;
telegram->read_value(codeNo, 2);
uint16_t year = (telegram->message_data[4] & 0x7F) + 2000;
uint8_t month = telegram->message_data[5];
@@ -1199,11 +1207,11 @@ bool Thermostat::set_building(const char * value, const int8_t id) {
if ((model() == EMS_DEVICE_FLAG_RC300) || (model() == EMS_DEVICE_FLAG_RC100)) {
if (Helpers::value2enum(value, bd, FL_(enum_ibaBuildingType))) {
LOG_INFO(F("Setting building to %s"), value);
write_command(0x240, 9, bd, 0x240);
write_command(0x240, 9, bd + 1, 0x240);
return true;
}
} else {
if (Helpers::value2enum(value, bd, FL_(enum_ibaBuildingType2))) {
if (Helpers::value2enum(value, bd, FL_(enum_ibaBuildingType))) {
LOG_INFO(F("Setting building to %s"), value);
write_command(EMS_TYPE_IBASettings, 6, bd, EMS_TYPE_IBASettings);
return true;
@@ -1467,13 +1475,39 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) {
// sets the thermostat working mode, where mode is a string
// converts string mode to HeatingCircuit::Mode
bool Thermostat::set_mode(const char * value, const int8_t id) {
// quit if its numerical, as it could be mistaken as a temperature value
if (value[0] < 'A') {
std::string mode(20, '\0');
if (strlen(value) >= 20) {
LOG_WARNING(F("Set mode: Invalid mode"));
return false;
}
std::string mode(10, '\0');
if (!Helpers::value2string(value, mode)) {
if (value[0] >= '0' && value[0] <= '9') {
uint8_t num = value[0] - '0';
switch (model()) {
case EMSdevice::EMS_DEVICE_FLAG_RC20:
case EMSdevice::EMS_DEVICE_FLAG_RC20_N:
mode = uuid::read_flash_string(FL_(enum_mode2)[num]);
break;
case EMSdevice::EMS_DEVICE_FLAG_RC30:
case EMSdevice::EMS_DEVICE_FLAG_RC35:
case EMSdevice::EMS_DEVICE_FLAG_RC30_N:
mode = uuid::read_flash_string(FL_(enum_mode3)[num]);
break;
case EMSdevice::EMS_DEVICE_FLAG_RC300:
case EMSdevice::EMS_DEVICE_FLAG_RC100:
mode = uuid::read_flash_string(FL_(enum_mode)[num]);
break;
case EMSdevice::EMS_DEVICE_FLAG_JUNKERS:
mode = uuid::read_flash_string(FL_(enum_mode4)[num]);
break;
case EMSdevice::EMS_DEVICE_FLAG_CRF:
mode = uuid::read_flash_string(FL_(enum_mode5)[num]);
break;
default:
LOG_WARNING(F("Set mode: Invalid mode"));
return false;
}
} else if (!Helpers::value2string(value, mode)) {
LOG_WARNING(F("Set mode: Invalid mode"));
return false;
}
@@ -1583,7 +1617,7 @@ bool Thermostat::set_mode_n(const uint8_t mode, const uint8_t hc_num) {
} else {
offset = EMS_OFFSET_JunkersSetMessage_set_mode;
}
validate_typeid = monitor_typeids[hc_p];
validate_typeid = set_typeids[hc_p];
if (mode == HeatingCircuit::Mode::NOFROST) {
set_mode_value = 0x01;
} else if (mode == HeatingCircuit::Mode::ECO || (mode == HeatingCircuit::Mode::NIGHT)) {
@@ -2129,7 +2163,7 @@ void Thermostat::register_device_values() {
case EMS_DEVICE_FLAG_RC100:
case EMS_DEVICE_FLAG_RC300:
register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::TEXT, nullptr, FL_(dateTime), DeviceValueUOM::NONE, MAKE_CF_CB(set_datetime));
register_device_value(TAG_THERMOSTAT_DATA, &floordrystatus_, DeviceValueType::ENUM, FL_(enum_floordrystatus), FL_(floordrystatus), DeviceValueUOM::NONE);
register_device_value(TAG_THERMOSTAT_DATA, &floordrystatus_, DeviceValueType::ENUM, FL_(enum_floordrystatus), FL_(floordrystatus), DeviceValueUOM::LIST);
register_device_value(TAG_THERMOSTAT_DATA, &dampedoutdoortemp2_, DeviceValueType::SHORT, FL_(div10), FL_(dampedoutdoortemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_THERMOSTAT_DATA, &floordrytemp_, DeviceValueType::UINT, nullptr, FL_(floordrytemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_THERMOSTAT_DATA,
@@ -2137,14 +2171,21 @@ void Thermostat::register_device_values() {
DeviceValueType::ENUM,
FL_(enum_ibaBuildingType),
FL_(ibaBuildingType),
DeviceValueUOM::NONE,
DeviceValueUOM::LIST,
MAKE_CF_CB(set_building));
register_device_value(TAG_THERMOSTAT_DATA,
&ibaMinExtTemperature_,
DeviceValueType::INT,
nullptr,
FL_(ibaMinExtTemperature),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_minexttemp));
register_device_value(TAG_THERMOSTAT_DATA, &wwSetTemp_, DeviceValueType::UINT, nullptr, FL_(wwSetTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwtemp));
register_device_value(TAG_THERMOSTAT_DATA, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode));
register_device_value(TAG_THERMOSTAT_DATA, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode), FL_(wwMode), DeviceValueUOM::LIST, MAKE_CF_CB(set_wwmode));
register_device_value(
TAG_THERMOSTAT_DATA, &wwSetTempLow_, DeviceValueType::UINT, nullptr, FL_(wwSetTempLow), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwtemplow));
register_device_value(
TAG_THERMOSTAT_DATA, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode), FL_(wWCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwcircmode));
TAG_THERMOSTAT_DATA, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode), FL_(wWCircMode), DeviceValueUOM::LIST, MAKE_CF_CB(set_wwcircmode));
register_device_value(TAG_THERMOSTAT_DATA, &wwExtra1_, DeviceValueType::UINT, nullptr, FL_(wwExtra1), DeviceValueUOM::DEGREES);
register_device_value(TAG_THERMOSTAT_DATA, &wwExtra2_, DeviceValueType::UINT, nullptr, FL_(wwExtra2), DeviceValueUOM::DEGREES);
break;
@@ -2154,8 +2195,8 @@ void Thermostat::register_device_values() {
break;
case EMS_DEVICE_FLAG_RC30_N:
register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::TEXT, nullptr, FL_(dateTime), DeviceValueUOM::NONE); // can't set datetime
register_device_value(TAG_THERMOSTAT_DATA, &ibaMainDisplay_, DeviceValueType::ENUM, FL_(enum_ibaMainDisplay), FL_(ibaMainDisplay), DeviceValueUOM::NONE);
register_device_value(TAG_THERMOSTAT_DATA, &ibaLanguage_, DeviceValueType::ENUM, FL_(enum_ibaLanguage), FL_(ibaLanguage), DeviceValueUOM::NONE);
register_device_value(TAG_THERMOSTAT_DATA, &ibaMainDisplay_, DeviceValueType::ENUM, FL_(enum_ibaMainDisplay), FL_(ibaMainDisplay), DeviceValueUOM::LIST);
register_device_value(TAG_THERMOSTAT_DATA, &ibaLanguage_, DeviceValueType::ENUM, FL_(enum_ibaLanguage), FL_(ibaLanguage), DeviceValueUOM::LIST);
register_device_value(TAG_THERMOSTAT_DATA,
&ibaClockOffset_,
DeviceValueType::UINT,
@@ -2180,13 +2221,13 @@ void Thermostat::register_device_values() {
register_device_value(TAG_THERMOSTAT_DATA,
&ibaBuildingType_,
DeviceValueType::ENUM,
FL_(enum_ibaBuildingType2),
FL_(enum_ibaBuildingType),
FL_(ibaBuildingType),
DeviceValueUOM::NONE,
DeviceValueUOM::LIST,
MAKE_CF_CB(set_building));
register_device_value(TAG_THERMOSTAT_DATA, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode2), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode));
register_device_value(TAG_THERMOSTAT_DATA, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode2), FL_(wwMode), DeviceValueUOM::LIST, MAKE_CF_CB(set_wwmode));
register_device_value(
TAG_THERMOSTAT_DATA, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode2), FL_(wWCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwcircmode));
TAG_THERMOSTAT_DATA, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode2), FL_(wWCircMode), DeviceValueUOM::LIST, MAKE_CF_CB(set_wwcircmode));
break;
case EMS_DEVICE_FLAG_RC35:
register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::TEXT, nullptr, FL_(dateTime), DeviceValueUOM::NONE, MAKE_CF_CB(set_datetime));
@@ -2210,13 +2251,13 @@ void Thermostat::register_device_values() {
register_device_value(TAG_THERMOSTAT_DATA,
&ibaBuildingType_,
DeviceValueType::ENUM,
FL_(enum_ibaBuildingType2),
FL_(enum_ibaBuildingType),
FL_(ibaBuildingType),
DeviceValueUOM::NONE,
DeviceValueUOM::LIST,
MAKE_CF_CB(set_building));
register_device_value(TAG_THERMOSTAT_DATA, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode2), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode));
register_device_value(TAG_THERMOSTAT_DATA, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode2), FL_(wwMode), DeviceValueUOM::LIST, MAKE_CF_CB(set_wwmode));
register_device_value(
TAG_THERMOSTAT_DATA, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode2), FL_(wWCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwcircmode));
TAG_THERMOSTAT_DATA, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode2), FL_(wWCircMode), DeviceValueUOM::LIST, MAKE_CF_CB(set_wwcircmode));
break;
case EMS_DEVICE_FLAG_JUNKERS:
register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::TEXT, nullptr, FL_(dateTime), DeviceValueUOM::NONE, MAKE_CF_CB(set_datetime));
@@ -2290,8 +2331,8 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
switch (model) {
case EMS_DEVICE_FLAG_RC100:
case EMS_DEVICE_FLAG_RC300:
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode));
register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype), FL_(modetype), DeviceValueUOM::NONE);
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode), FL_(mode), DeviceValueUOM::LIST, MAKE_CF_CB(set_mode));
register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype), FL_(modetype), DeviceValueUOM::LIST);
register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), FL_(ecotemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_ecotemp));
register_device_value(tag, &hc->manualtemp, DeviceValueType::UINT, FL_(div2), FL_(manualtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_manualtemp));
register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), FL_(comforttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_comforttemp));
@@ -2303,34 +2344,34 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
register_device_value(tag, &hc->roominfluence, DeviceValueType::UINT, nullptr, FL_(roominfluence), DeviceValueUOM::NONE, MAKE_CF_CB(set_roominfluence));
register_device_value(tag, &hc->nofrosttemp, DeviceValueType::INT, nullptr, FL_(nofrosttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nofrosttemp));
register_device_value(tag, &hc->targetflowtemp, DeviceValueType::UINT, nullptr, FL_(targetflowtemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &hc->heatingtype, DeviceValueType::ENUM, FL_(enum_heatingtype), FL_(heatingtype), DeviceValueUOM::NONE);
register_device_value(tag, &hc->heatingtype, DeviceValueType::ENUM, FL_(enum_heatingtype), FL_(heatingtype), DeviceValueUOM::LIST);
register_device_value(
tag, &hc->summer_setmode, DeviceValueType::ENUM, FL_(enum_summermode), FL_(summersetmode), DeviceValueUOM::NONE, MAKE_CF_CB(set_summermode));
tag, &hc->summer_setmode, DeviceValueType::ENUM, FL_(enum_summermode), FL_(summersetmode), DeviceValueUOM::LIST, MAKE_CF_CB(set_summermode));
register_device_value(tag, &hc->summermode, DeviceValueType::BOOL, nullptr, FL_(summermode), DeviceValueUOM::BOOLEAN);
register_device_value(
tag, &hc->controlmode, DeviceValueType::ENUM, FL_(enum_controlmode), FL_(controlmode), DeviceValueUOM::NONE, MAKE_CF_CB(set_controlmode));
tag, &hc->controlmode, DeviceValueType::ENUM, FL_(enum_controlmode), FL_(controlmode), DeviceValueUOM::LIST, MAKE_CF_CB(set_controlmode));
register_device_value(tag, &hc->program, DeviceValueType::UINT, nullptr, FL_(program), DeviceValueUOM::NONE, MAKE_CF_CB(set_program));
register_device_value(tag, &hc->tempautotemp, DeviceValueType::UINT, FL_(div2), FL_(tempautotemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_tempautotemp));
break;
case EMS_DEVICE_FLAG_CRF:
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode5), FL_(mode), DeviceValueUOM::NONE);
register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype5), FL_(modetype), DeviceValueUOM::NONE);
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode5), FL_(mode), DeviceValueUOM::LIST);
register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype5), FL_(modetype), DeviceValueUOM::LIST);
register_device_value(tag, &hc->targetflowtemp, DeviceValueType::UINT, nullptr, FL_(targetflowtemp), DeviceValueUOM::DEGREES);
break;
case EMS_DEVICE_FLAG_RC20:
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode2), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode));
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode2), FL_(mode), DeviceValueUOM::LIST, MAKE_CF_CB(set_mode));
break;
case EMS_DEVICE_FLAG_RC20_N:
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode2), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode));
register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype2), FL_(modetype), DeviceValueUOM::NONE);
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode2), FL_(mode), DeviceValueUOM::LIST, MAKE_CF_CB(set_mode));
register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype2), FL_(modetype), DeviceValueUOM::LIST);
register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), FL_(daytemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daytemp));
register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), FL_(nighttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nighttemp));
register_device_value(tag, &hc->program, DeviceValueType::UINT, nullptr, FL_(program), DeviceValueUOM::NONE, MAKE_CF_CB(set_program));
break;
case EMS_DEVICE_FLAG_RC30_N:
case EMS_DEVICE_FLAG_RC35:
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode3), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode));
register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype3), FL_(modetype), DeviceValueUOM::NONE);
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode3), FL_(mode), DeviceValueUOM::LIST, MAKE_CF_CB(set_mode));
register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype3), FL_(modetype), DeviceValueUOM::LIST);
register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), FL_(daytemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daytemp));
register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), FL_(nighttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nighttemp));
register_device_value(tag, &hc->designtemp, DeviceValueType::UINT, nullptr, FL_(designtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_designtemp));
@@ -2345,22 +2386,22 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
register_device_value(tag, &hc->minflowtemp, DeviceValueType::UINT, nullptr, FL_(minflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_minflowtemp));
register_device_value(tag, &hc->maxflowtemp, DeviceValueType::UINT, nullptr, FL_(maxflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_maxflowtemp));
register_device_value(tag, &hc->flowtempoffset, DeviceValueType::UINT, nullptr, FL_(flowtempoffset), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_flowtempoffset));
register_device_value(tag, &hc->heatingtype, DeviceValueType::ENUM, FL_(enum_heatingtype), FL_(heatingtype), DeviceValueUOM::NONE);
register_device_value(tag, &hc->reducemode, DeviceValueType::ENUM, FL_(enum_reducemode), FL_(reducemode), DeviceValueUOM::NONE, MAKE_CF_CB(set_reducemode));
register_device_value(tag, &hc->heatingtype, DeviceValueType::ENUM, FL_(enum_heatingtype), FL_(heatingtype), DeviceValueUOM::LIST);
register_device_value(tag, &hc->reducemode, DeviceValueType::ENUM, FL_(enum_reducemode), FL_(reducemode), DeviceValueUOM::LIST, MAKE_CF_CB(set_reducemode));
register_device_value(
tag, &hc->controlmode, DeviceValueType::ENUM, FL_(enum_controlmode2), FL_(controlmode), DeviceValueUOM::NONE, MAKE_CF_CB(set_controlmode));
register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_control), FL_(control), DeviceValueUOM::NONE, MAKE_CF_CB(set_control));
tag, &hc->controlmode, DeviceValueType::ENUM, FL_(enum_controlmode2), FL_(controlmode), DeviceValueUOM::LIST, MAKE_CF_CB(set_controlmode));
register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_control), FL_(control), DeviceValueUOM::LIST, MAKE_CF_CB(set_control));
register_device_value(tag, &hc->program, DeviceValueType::UINT, nullptr, FL_(program), DeviceValueUOM::NONE, MAKE_CF_CB(set_program));
register_device_value(tag, &hc->pause, DeviceValueType::UINT, nullptr, FL_(pause), DeviceValueUOM::HOURS, MAKE_CF_CB(set_pause));
register_device_value(tag, &hc->party, DeviceValueType::UINT, nullptr, FL_(party), DeviceValueUOM::HOURS, MAKE_CF_CB(set_party));
register_device_value(tag, &hc->tempautotemp, DeviceValueType::UINT, FL_(div2), FL_(tempautotemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_tempautotemp));
register_device_value(tag, &hc->noreducetemp, DeviceValueType::INT, nullptr, FL_(noreducetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_noreducetemp));
register_device_value(tag, &hc->remotetemp, DeviceValueType::SHORT, FL_(div10), FL_(remotetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_remotetemp));
register_device_value(tag, &dummychar_, DeviceValueType::TEXT, nullptr, FL_(switchtime), DeviceValueUOM::NONE, MAKE_CF_CB(set_switchtime));
register_device_value(tag, &dummy_, DeviceValueType::CMD, nullptr, FL_(switchtime), DeviceValueUOM::NONE, MAKE_CF_CB(set_switchtime));
break;
case EMS_DEVICE_FLAG_JUNKERS:
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode4), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode));
register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype4), FL_(modetype), DeviceValueUOM::NONE);
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode4), FL_(mode), DeviceValueUOM::LIST, MAKE_CF_CB(set_mode));
register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype4), FL_(modetype), DeviceValueUOM::LIST);
register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), FL_(heattemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_heattemp));
register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), FL_(ecotemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_ecotemp));
register_device_value(tag, &hc->nofrosttemp, DeviceValueType::INT, FL_(div2), FL_(nofrosttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nofrosttemp));

View File

@@ -146,7 +146,7 @@ class Thermostat : public EMSdevice {
char errorCode_[15]; // code from 0xA2 as string i.e. "A22(816)"
uint16_t errorNumber_; // used internally to build error code
char lastCode_[30]; // error log
char dummychar_[5]; // for commands with no output
uint8_t dummy_; // for commands with no output
// Installation parameters
uint8_t ibaMainDisplay_; // display on Thermostat: 0 int temp, 1 int setpoint, 2 ext temp, 3 burner temp, 4 ww temp, 5 functioning mode, 6 time, 7 data, 9 smoke temp

View File

@@ -40,7 +40,8 @@ static const __FlashStringHelper * DeviceValueUOM_s[] __attribute__((__aligned__
F_(seconds),
F_(dbm),
F_(num),
F_(bool)
F_(bool),
F_(blank)
};
@@ -421,12 +422,12 @@ void EMSdevice::show_mqtt_handlers(uuid::console::Shell & shell) {
Mqtt::show_topic_handlers(shell, device_type_);
}
void EMSdevice::register_mqtt_topic(const std::string & topic, mqtt_subfunction_p f) {
void EMSdevice::register_mqtt_topic(const std::string & topic, const mqtt_sub_function_p f) {
Mqtt::subscribe(device_type_, topic, f);
}
// register a callback function for a specific telegram type
void EMSdevice::register_telegram_type(const uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, process_function_p f) {
void EMSdevice::register_telegram_type(const uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, const process_function_p f) {
telegram_functions_.emplace_back(telegram_type_id, telegram_type_name, fetch, f);
}
@@ -437,7 +438,7 @@ void EMSdevice::register_telegram_type(const uint16_t telegram_type_id, const __
// type: one of DeviceValueType
// options: options for enum or a divider for int (e.g. F("10"))
// short_name: used in Mqtt as keys
// full name: used in Web and Console unless empty (nullptr)
// full_name: used in Web and Console unless empty (nullptr)
// uom: unit of measure from DeviceValueUOM
void EMSdevice::register_device_value(uint8_t tag,
void * value_p,
@@ -479,24 +480,29 @@ void EMSdevice::register_device_value(uint8_t tag,
}
// function with min and max values
// adds a new command to the command list
void EMSdevice::register_device_value(uint8_t tag,
void * value_p,
uint8_t type,
const __FlashStringHelper * const * options,
const __FlashStringHelper * const * name,
uint8_t uom,
cmdfunction_p f,
const cmd_function_p f,
int32_t min,
uint32_t max) {
register_device_value(tag, value_p, type, options, name[0], name[1], uom, (f != nullptr), min, max);
if (f != nullptr) {
if (tag >= TAG_HC1 && tag <= TAG_HC4) {
Command::add(device_type_, name[0], f, name[1], FLAG_HC);
} else if (tag >= TAG_WWC1 && tag <= TAG_WWC4) {
Command::add(device_type_, name[0], f, name[1], FLAG_WWC);
} else {
Command::add(device_type_, name[0], f, name[1], 0);
}
// add a new command if it has a function attached
if (f == nullptr) {
return;
}
if (tag >= TAG_HC1 && tag <= TAG_HC4) {
Command::add(device_type_, name[0], f, name[1], CommandFlag::MQTT_SUB_FLAG_HC | CommandFlag::ADMIN_ONLY);
} else if (tag >= TAG_WWC1 && tag <= TAG_WWC4) {
Command::add(device_type_, name[0], f, name[1], CommandFlag::MQTT_SUB_FLAG_WWC | CommandFlag::ADMIN_ONLY);
} else {
Command::add(device_type_, name[0], f, name[1], CommandFlag::MQTT_SUB_FLAG_NORMAL | CommandFlag::ADMIN_ONLY);
}
}
@@ -507,7 +513,7 @@ void EMSdevice::register_device_value(uint8_t tag,
const __FlashStringHelper * const * options,
const __FlashStringHelper * const * name,
uint8_t uom,
cmdfunction_p f) {
const cmd_function_p f) {
register_device_value(tag, value_p, type, options, name, uom, f, 0, 0);
}
@@ -556,8 +562,8 @@ void EMSdevice::generate_values_json_web(JsonObject & json) {
JsonArray data = json.createNestedArray("data");
for (const auto & dv : devicevalues_) {
// ignore if full_name empty
if (dv.full_name != nullptr) {
// ignore if full_name empty and also commands
if ((dv.full_name != nullptr) && (dv.type != DeviceValueType::CMD)) {
JsonObject obj; // create the object, if needed
// handle Booleans (true, false)
@@ -580,6 +586,12 @@ void EMSdevice::generate_values_json_web(JsonObject & json) {
}
}
// // handle commands without value
// else if (dv.type == DeviceValueType::CMD) {
// obj = data.createNestedObject();
// obj["v"] = "";
// }
else {
// handle Integers and Floats
// If a divider is specified, do the division to 2 decimals places and send back as double/float
@@ -634,6 +646,16 @@ void EMSdevice::generate_values_json_web(JsonObject & json) {
} else {
obj["c"] = "";
}
// add enum and text option settings
if ((dv.uom == DeviceValueUOM::LIST) && dv.has_cmd) {
JsonArray l = obj.createNestedArray("l");
for (uint8_t i = 0; i < dv.options_size; i++) {
if (!uuid::read_flash_string(dv.options[i]).empty()) {
l.add(uuid::read_flash_string(dv.options[i]));
}
}
}
}
}
}
@@ -679,8 +701,8 @@ bool EMSdevice::get_value_info(JsonObject & root, const char * cmd, const int8_t
switch (dv.type) {
case DeviceValueType::ENUM: {
if (Helpers::hasValue((uint8_t)(*(uint8_t *)(dv.value_p)))) {
if (Mqtt::bool_format() == BOOL_FORMAT_10) {
if (*(uint8_t *)(dv.value_p) < dv.options_size) {
if (EMSESP::enum_format() == ENUM_FORMAT_NUMBER) {
json[value] = (uint8_t)(*(uint8_t *)(dv.value_p));
} else {
json[value] = dv.options[*(uint8_t *)(dv.value_p)]; // text
@@ -753,7 +775,16 @@ bool EMSdevice::get_value_info(JsonObject & root, const char * cmd, const int8_t
case DeviceValueType::BOOL: {
if (Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) {
json[value] = (bool)(*(uint8_t *)(dv.value_p)) ? true : false;
uint8_t bool_format = EMSESP::bool_format();
if (bool_format == BOOL_FORMAT_ONOFF) {
json[value] = (bool)(*(uint8_t *)(dv.value_p)) ? F_(on) : F_(off);
} else if (bool_format == BOOL_FORMAT_ONOFF_CAP) {
json[value] = (bool)(*(uint8_t *)(dv.value_p)) ? F_(ON) : F_(OFF);
} else if (bool_format == BOOL_FORMAT_TRUEFALSE) {
json[value] = (bool)(*(uint8_t *)(dv.value_p)) ? true : false;
} else {
json[value] = (bool)(*(uint8_t *)(dv.value_p)) ? 1 : 0;
}
}
json[type] = F("boolean");
break;
@@ -775,13 +806,17 @@ bool EMSdevice::get_value_info(JsonObject & root, const char * cmd, const int8_t
json[type] = F_(text);
break;
case DeviceValueType::CMD:
json[type] = F_(command);
break;
default:
json[type] = F_(unknown);
break;
}
// add uom if it's not a " " (single space)
if ((dv.uom != DeviceValueUOM::NONE) && (dv.uom != DeviceValueUOM::NUM) && (dv.uom != DeviceValueUOM::BOOLEAN)) {
if (!uom_to_string(dv.uom).empty() && uom_to_string(dv.uom) != " ") {
json["unit"] = EMSdevice::uom_to_string(dv.uom);
}
@@ -841,24 +876,17 @@ bool EMSdevice::generate_values_json(JsonObject & root, const uint8_t tag_filter
if ((dv.type == DeviceValueType::BOOL) && Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) {
// see how to render the value depending on the setting
// when in console mode we always use on and off
if ((Mqtt::bool_format() == BOOL_FORMAT_ONOFF) || console) {
// on or off as strings
uint8_t bool_format = EMSESP::bool_format();
if ((bool_format == BOOL_FORMAT_ONOFF) || console) {
json[name] = *(uint8_t *)(dv.value_p) ? F_(on) : F_(off);
has_value = true;
} else if (Mqtt::bool_format() == BOOL_FORMAT_ONOFF_CAP) {
// on or off as strings
} else if (bool_format == BOOL_FORMAT_ONOFF_CAP) {
json[name] = *(uint8_t *)(dv.value_p) ? F_(ON) : F_(OFF);
has_value = true;
} else if (Mqtt::bool_format() == BOOL_FORMAT_TRUEFALSE) {
// true or false values (as real booleans, not strings)
} else if (bool_format == BOOL_FORMAT_TRUEFALSE) {
json[name] = (bool)(*(uint8_t *)(dv.value_p)) ? true : false;
has_value = true;
} else {
// numerical 1 or 0
json[name] = (uint8_t)(*(uint8_t *)(dv.value_p)) ? 1 : 0;
has_value = true;
}
has_value = true;
}
// handle TEXT strings
@@ -870,7 +898,8 @@ bool EMSdevice::generate_values_json(JsonObject & root, const uint8_t tag_filter
// handle ENUMs
else if ((dv.type == DeviceValueType::ENUM) && Helpers::hasValue(*(uint8_t *)(dv.value_p))) {
if (*(uint8_t *)(dv.value_p) < dv.options_size) {
if (Mqtt::bool_format() == BOOL_FORMAT_10) {
// check for numeric enum-format, but "hamode" always as text
if ((EMSESP::enum_format() == ENUM_FORMAT_NUMBER) && (dv.short_name != FL_(hamode)[0])) {
json[name] = (uint8_t)(*(uint8_t *)(dv.value_p));
} else {
json[name] = dv.options[*(uint8_t *)(dv.value_p)];
@@ -960,7 +989,7 @@ void EMSdevice::publish_mqtt_ha_sensor() {
void EMSdevice::ha_config_clear() {
for (auto & dv : devicevalues_) {
// dv.ha &= ~DeviceValueHA::HA_DONE; // repubish all with values
// dv.ha &= ~DeviceValueHA::HA_DONE; // republish all with values
dv.ha = DeviceValueHA::HA_NONE; // also wait for new value
}
ha_config_done(false);

View File

@@ -39,7 +39,8 @@ enum DeviceValueType : uint8_t {
ULONG,
TIME, // same as ULONG (32 bits)
ENUM,
TEXT
TEXT,
CMD // special for commands only
};
@@ -64,7 +65,8 @@ enum DeviceValueUOM : uint8_t {
SECONDS, // 13
DBM, // 14
NUM, // 15
BOOLEAN // 16
BOOLEAN, // 16
LIST // 17
};
@@ -118,9 +120,6 @@ enum DeviceValueTAG : uint8_t {
};
// mqtt flags for command subscriptions
enum MqttSubFlag : uint8_t { FLAG_NORMAL = 0, FLAG_HC, FLAG_WWC, FLAG_NOSUB };
// mqtt-HA flags
enum DeviceValueHA : uint8_t { HA_NONE = 0, HA_VALUE, HA_DONE };
@@ -168,6 +167,7 @@ class EMSdevice {
return ((device_id & 0x7F) == (device_id_ & 0x7F));
}
// flags
inline void add_flags(uint8_t flags) {
flags_ |= flags;
}
@@ -239,7 +239,7 @@ class EMSdevice {
using process_function_p = std::function<void(std::shared_ptr<const Telegram>)>;
void register_telegram_type(const uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, process_function_p cb);
void register_telegram_type(const uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, const process_function_p cb);
bool handle_telegram(std::shared_ptr<const Telegram> telegram);
std::string get_value_uom(const char * key);
@@ -263,7 +263,7 @@ class EMSdevice {
const __FlashStringHelper * const * options,
const __FlashStringHelper * const * name,
uint8_t uom,
cmdfunction_p f,
const cmd_function_p f,
int32_t min,
uint32_t max);
void register_device_value(uint8_t tag,
@@ -272,22 +272,21 @@ class EMSdevice {
const __FlashStringHelper * const * options,
const __FlashStringHelper * const * name,
uint8_t uom,
cmdfunction_p f);
const cmd_function_p f);
void register_device_value(uint8_t tag,
void * value_p,
uint8_t type,
const __FlashStringHelper * const * options,
const __FlashStringHelper * const * name,
uint8_t uom);
// void register_device_value(uint8_t tag, void * value_p, uint8_t type, const __FlashStringHelper * const * options, const __FlashStringHelper * const * name, uint8_t uom, int32_t min, uint32_t max);
void write_command(const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid);
void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid);
void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value);
void read_command(const uint16_t type_id, uint8_t offset = 0, uint8_t length = 0);
void register_mqtt_topic(const std::string & topic, mqtt_subfunction_p f);
// void register_cmd(const __FlashStringHelper * cmd, cmdfunction_p f, uint8_t flag = 0);
void register_mqtt_topic(const std::string & topic, const mqtt_sub_function_p f);
void publish_mqtt_ha_sensor();
@@ -403,7 +402,7 @@ class EMSdevice {
bool fetch_; // if this type_id be queried automatically
process_function_p process_function_;
TelegramFunction(uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, process_function_p process_function)
TelegramFunction(uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, const process_function_p process_function)
: telegram_type_id_(telegram_type_id)
, telegram_type_name_(telegram_type_name)
, fetch_(fetch)

View File

@@ -37,10 +37,10 @@ ESP8266React EMSESP::esp8266React(&webServer, &LITTLEFS);
WebSettingsService EMSESP::webSettingsService = WebSettingsService(&webServer, &LITTLEFS, EMSESP::esp8266React.getSecurityManager());
#endif
WebStatusService EMSESP::webStatusService = WebStatusService(&webServer, EMSESP::esp8266React.getSecurityManager());
WebDevicesService EMSESP::webDevicesService = WebDevicesService(&webServer, EMSESP::esp8266React.getSecurityManager());
WebAPIService EMSESP::webAPIService = WebAPIService(&webServer, EMSESP::esp8266React.getSecurityManager());
WebLogService EMSESP::webLogService = WebLogService(&webServer, EMSESP::esp8266React.getSecurityManager());
WebStatusService EMSESP::webStatusService = WebStatusService(&webServer, EMSESP::esp8266React.getSecurityManager());
WebDataService EMSESP::webDataService = WebDataService(&webServer, EMSESP::esp8266React.getSecurityManager());
WebAPIService EMSESP::webAPIService = WebAPIService(&webServer, EMSESP::esp8266React.getSecurityManager());
WebLogService EMSESP::webLogService = WebLogService(&webServer, EMSESP::esp8266React.getSecurityManager());
using DeviceFlags = EMSdevice;
using DeviceType = EMSdevice::DeviceType;
@@ -75,6 +75,9 @@ uint8_t EMSESP::publish_all_idx_ = 0;
uint8_t EMSESP::unique_id_count_ = 0;
bool EMSESP::trace_raw_ = false;
uint64_t EMSESP::tx_delay_ = 0;
uint8_t EMSESP::bool_format_ = 1;
uint8_t EMSESP::enum_format_ = 1;
uint16_t EMSESP::wait_validate_ = 0;
// for a specific EMS device go and request data values
// or if device_id is 0 it will fetch from all our known and active devices
@@ -361,8 +364,13 @@ void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
shell.printfln(F("Dallas temperature sensors:"));
uint8_t i = 1;
char s[7];
char s2[7];
for (const auto & device : sensor_devices()) {
shell.printfln(F(" Sensor %d, ID: %s, Temperature: %s °C"), i++, device.to_string().c_str(), Helpers::render_value(s, device.temperature_c, 10));
shell.printfln(F(" Sensor %d, ID: %s, Temperature: %s °C (offset %s)"),
i++,
device.to_string().c_str(),
Helpers::render_value(s, device.temperature_c, 10),
Helpers::render_value(s2, device.offset(), 10));
}
shell.println();
}
@@ -548,7 +556,7 @@ void EMSESP::publish_response(std::shared_ptr<const Telegram> telegram) {
doc["dest"] = Helpers::hextoa(buffer, telegram->dest);
doc["type"] = Helpers::hextoa(buffer, telegram->type_id);
doc["offset"] = Helpers::hextoa(buffer, telegram->offset);
strcpy(buffer, Helpers::data_to_hex(telegram->message_data, telegram->message_length).c_str());
strcpy(buffer, Helpers::data_to_hex(telegram->message_data, telegram->message_length - 1).c_str()); // exclude CRC
doc["data"] = buffer;
if (telegram->message_length <= 4) {
@@ -613,10 +621,10 @@ std::string EMSESP::pretty_telegram(std::shared_ptr<const Telegram> telegram) {
uint8_t offset = telegram->offset;
// find name for src and dest by looking up known devices
std::string src_name;
std::string dest_name;
std::string type_name;
std::string direction;
std::string src_name("");
std::string dest_name("");
std::string type_name("");
std::string direction("");
for (const auto & emsdevice : emsdevices) {
if (emsdevice) {
// get src & dest
@@ -774,7 +782,7 @@ void EMSESP::process_version(std::shared_ptr<const Telegram> telegram) {
bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
// if watching or reading...
if ((telegram->type_id == read_id_) && (telegram->dest == txservice_.ems_bus_id())) {
LOG_NOTICE(pretty_telegram(telegram).c_str());
LOG_NOTICE(F("%s"), pretty_telegram(telegram).c_str());
publish_response(telegram);
if (!read_next_) {
read_id_ = WATCH_ID_NONE;
@@ -783,12 +791,12 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
} else if (watch() == WATCH_ON) {
if ((watch_id_ == WATCH_ID_NONE) || (telegram->type_id == watch_id_)
|| ((watch_id_ < 0x80) && ((telegram->src == watch_id_) || (telegram->dest == watch_id_)))) {
LOG_NOTICE(pretty_telegram(telegram).c_str());
LOG_NOTICE(F("%s"), pretty_telegram(telegram).c_str());
} else if (!trace_raw_) {
LOG_TRACE(pretty_telegram(telegram).c_str());
LOG_TRACE(F("%s"), pretty_telegram(telegram).c_str());
}
} else if (!trace_raw_) {
LOG_TRACE(pretty_telegram(telegram).c_str());
LOG_TRACE(F("%s"), pretty_telegram(telegram).c_str());
}
// only process broadcast telegrams or ones sent to us on request
@@ -832,6 +840,9 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
publish_device_values(emsdevice->device_type()); // publish to MQTT if we explicitly have too
}
}
if (wait_validate_ == telegram->type_id) {
wait_validate_ = 0;
}
break;
}
}
@@ -840,7 +851,7 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
if (!found) {
LOG_DEBUG(F("No telegram type handler found for ID 0x%02X (src 0x%02X)"), telegram->type_id, telegram->src);
if (watch() == WATCH_UNKNOWN) {
LOG_NOTICE(pretty_telegram(telegram).c_str());
LOG_NOTICE(F("%s"), pretty_telegram(telegram).c_str());
}
if (first_scan_done_ && !knowndevice && (telegram->src != EMSbus::ems_bus_id()) && (telegram->src != 0x0B) && (telegram->src != 0x0C)
&& (telegram->src != 0x0D)) {
@@ -975,18 +986,18 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::
return true;
}
Command::add_with_json(
Command::add_json(
device_type,
F_(info),
[device_type](const char * value, const int8_t id, JsonObject & json) { return command_info(device_type, json, id, true); },
F_(info_cmd));
Command::add_with_json(
Command::add_json(
device_type,
F("info_short"),
[device_type](const char * value, const int8_t id, JsonObject & json) { return command_info(device_type, json, id, false); },
nullptr,
true); // this command is hidden
Command::add_with_json(
CommandFlag::HIDDEN); // this command is hidden
Command::add_json(
device_type,
F_(commands),
[device_type](const char * value, const int8_t id, JsonObject & json) { return command_commands(device_type, json, id); },
@@ -1198,6 +1209,7 @@ void EMSESP::start() {
shower_.start(); // initialize shower timer and shower alert
dallassensor_.start(); // dallas external sensors
webServer.begin(); // start web server
webLogService.start(); // start web log service
emsdevices.reserve(5); // reserve space for initially 5 devices to avoid mem frag issues
@@ -1231,7 +1243,8 @@ void EMSESP::loop() {
console_.loop(); // telnet/serial console
// delay(1); // helps telnet catch up. don't think its needed in ESP32 3.1.0
// 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
}
} // namespace emsesp

View File

@@ -36,7 +36,7 @@
#include <ESP8266React.h>
#include "web/WebStatusService.h"
#include "web/WebDevicesService.h"
#include "web/WebDataService.h"
#include "web/WebSettingsService.h"
#include "web/WebAPIService.h"
#include "web/WebLogService.h"
@@ -65,8 +65,8 @@
#define EMSESP_JSON_SIZE_XXLARGE_DYN 8192 // for extra very very large json docs, using DynamicJsonDocument
// helpers for callback functions
#define MAKE_PF_CB(__f) [&](std::shared_ptr<const Telegram> t) { __f(t); } // for process function callbacks to register_telegram_type()
#define MAKE_CF_CB(__f) [&](const char * value, const int8_t id) { return __f(value, id); } // for command function callbacks to register_mqtt_cmd()
#define MAKE_PF_CB(__f) [&](std::shared_ptr<const Telegram> t) { __f(t); } // for Process Function callbacks to EMSDevice::process_function_p
#define MAKE_CF_CB(__f) [&](const char * value, const int8_t id) { return __f(value, id); } // for Command Function callbacks Command::cmd_function_p
namespace emsesp {
@@ -144,6 +144,22 @@ class EMSESP {
return (dallassensor_.dallas_enabled());
}
static uint8_t bool_format() {
return bool_format_;
}
static void bool_format(uint8_t format) {
bool_format_ = format;
}
static uint8_t enum_format() {
return enum_format_;
}
static void enum_format(uint8_t format) {
enum_format_ = format;
}
enum Watch : uint8_t { WATCH_OFF, WATCH_ON, WATCH_RAW, WATCH_UNKNOWN };
static void watch_id(uint16_t id);
static uint16_t watch_id() {
@@ -162,6 +178,12 @@ class EMSESP {
static void set_read_id(uint16_t id) {
read_id_ = id;
}
static bool wait_validate() {
return (wait_validate_ != 0);
}
static void wait_validate(uint16_t wait) {
wait_validate_ = wait;
}
enum Bus_status : uint8_t { BUS_STATUS_CONNECTED = 0, BUS_STATUS_TX_ERRORS, BUS_STATUS_OFFLINE };
static uint8_t bus_status();
@@ -204,7 +226,7 @@ class EMSESP {
static ESP8266React esp8266React;
static WebSettingsService webSettingsService;
static WebStatusService webStatusService;
static WebDevicesService webDevicesService;
static WebDataService webDataService;
static WebAPIService webAPIService;
static WebLogService webLogService;
@@ -246,6 +268,9 @@ class EMSESP {
static uint8_t unique_id_count_;
static bool trace_raw_;
static uint64_t tx_delay_;
static uint8_t bool_format_;
static uint8_t enum_format_;
static uint16_t wait_validate_;
};
} // namespace emsesp

View File

@@ -17,7 +17,7 @@
*/
#include "helpers.h"
#include "mqtt.h"
#include "emsesp.h"
namespace emsesp {
@@ -36,14 +36,14 @@ char * Helpers::hextoa(char * result, const uint8_t value) {
#ifdef EMSESP_STANDALONE
// special function to work outside of ESP's libraries
char * Helpers::ultostr(char * ptr, uint32_t value, const uint8_t base) {
unsigned long t = 0, res = 0;
unsigned long tmp = value;
int count = 0;
if (NULL == ptr) {
return NULL;
}
unsigned long t = 0;
unsigned long tmp = value;
int count = 0;
if (tmp == 0) {
count++;
}
@@ -57,6 +57,8 @@ char * Helpers::ultostr(char * ptr, uint32_t value, const uint8_t base) {
*ptr = '\0';
unsigned long res = 0;
do {
res = value - base * (t = value / base);
if (res < 10) {
@@ -124,7 +126,7 @@ char * Helpers::smallitoa(char * result, const uint16_t value) {
// work out how to display booleans
char * Helpers::render_boolean(char * result, bool value) {
uint8_t bool_format_ = Mqtt::bool_format();
uint8_t bool_format_ = EMSESP::bool_format();
if (bool_format_ == BOOL_FORMAT_ONOFF) {
strlcpy(result, value ? uuid::read_flash_string(F_(on)).c_str() : uuid::read_flash_string(F_(off)).c_str(), 5);
} else if (bool_format_ == BOOL_FORMAT_ONOFF_CAP) {
@@ -417,12 +419,15 @@ bool Helpers::value2number(const char * v, int & value) {
// checks if we can convert a char string to a float value
bool Helpers::value2float(const char * v, float & value) {
value = 0;
if ((v == nullptr) || (strlen(v) == 0)) {
value = 0;
return false;
}
value = atof((char *)v);
return true;
if (v[0] == '-' || v[0] == '.' || (v[0] >= '0' && v[0] <= '9')) {
value = atof((char *)v);
return true;
}
return false;
}
// https://stackoverflow.com/questions/313970/how-to-convert-stdstring-to-lower-case

View File

@@ -71,6 +71,7 @@ MAKE_PSTR_WORD(pin)
MAKE_PSTR_WORD(publish)
MAKE_PSTR_WORD(timeout)
MAKE_PSTR_WORD(board_profile)
MAKE_PSTR_WORD(sensorname)
// for commands
MAKE_PSTR_WORD(call)
@@ -124,6 +125,7 @@ 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]")
@@ -173,8 +175,9 @@ MAKE_PSTR(w, "W")
MAKE_PSTR(kb, "KB")
MAKE_PSTR(seconds, "seconds")
MAKE_PSTR(dbm, "dBm")
MAKE_PSTR(num, " ") // this is hack so HA renders numbers correctly
MAKE_PSTR(bool, " ") // this is hack so HA renders numbers correctly
MAKE_PSTR(num, " ") // this is hack so HA renders numbers correctly
MAKE_PSTR(bool, " ") // this is hack so HA renders numbers correctly
MAKE_PSTR(blank, " ") // this is hack so HA renders numbers correctly
// TAG mapping - maps to DeviceValueTAG_s in emsdevice.cpp
// use empty string if want to suppress showing tags
@@ -242,13 +245,13 @@ MAKE_PSTR_LIST(enum_charge, F_(chargepump), F_(3wayvalve))
MAKE_PSTR_LIST(enum_comfort, F_(hot), F_(eco), F_(intelligent))
MAKE_PSTR_LIST(enum_flow, F_(off), F_(flow), F_(bufferedflow), F_(buffer), F_(layeredbuffer))
MAKE_PSTR_LIST(enum_reset, F_(maintenance), F_(error))
MAKE_PSTR_LIST(enum_bool, F_(off), F_(on))
// thermostat
MAKE_PSTR_WORD(light)
MAKE_PSTR_WORD(medium)
MAKE_PSTR_WORD(heavy)
MAKE_PSTR_WORD(own_prog)
MAKE_PSTR(blank, "")
MAKE_PSTR_WORD(start)
MAKE_PSTR_WORD(heat)
MAKE_PSTR_WORD(hold)
@@ -309,25 +312,24 @@ MAKE_PSTR_LIST(enum_ibaMainDisplay,
F_(smoke_temperature))
MAKE_PSTR_LIST(enum_ibaLanguage, F_(german), F_(dutch), F_(french), F_(italian))
MAKE_PSTR_LIST(enum_floordrystatus, F_(off), F_(start), F_(heat), F_(hold), F_(cool), F_(end))
MAKE_PSTR_LIST(enum_ibaBuildingType, F_(blank), F_(light), F_(medium), F_(heavy)) // RC300
MAKE_PSTR_LIST(enum_ibaBuildingType, F_(light), F_(medium), F_(heavy)) // RC300
MAKE_PSTR_LIST(enum_wwMode, F_(off), F_(low), F_(high), F_(auto), F_(own_prog))
MAKE_PSTR_LIST(enum_wwCircMode, F_(off), F_(on), F_(auto), F_(own_prog))
MAKE_PSTR_LIST(enum_ibaBuildingType2, F_(light), F_(medium), F_(heavy)) // RC30, RC35
MAKE_PSTR_LIST(enum_wwMode2, F_(off), F_(on), F_(auto))
MAKE_PSTR_LIST(enum_wwCircMode2, F_(off), F_(on), F_(auto))
MAKE_PSTR_LIST(enum_heatingtype, F_(off), F_(radiator), F_(convector), F_(floor))
MAKE_PSTR_LIST(enum_summermode, F_(summer), F_(auto), F_(winter))
MAKE_PSTR_LIST(enum_mode, F_(manual), F_(auto)) // RC100, RC300, RC310
MAKE_PSTR_LIST(enum_mode2, F_(off), F_(manual), F_(auto)) // RC20
MAKE_PSTR_LIST(enum_mode3, F_(night), F_(day), F_(auto)) // RC35, RC30
MAKE_PSTR_LIST(enum_mode4, F_(blank), F_(manual), F_(auto), F_(holiday)) // JUNKERS
MAKE_PSTR_LIST(enum_mode5, F_(auto), F_(off)) // CRF
MAKE_PSTR_LIST(enum_mode, F_(manual), F_(auto)) // RC100, RC300, RC310
MAKE_PSTR_LIST(enum_mode2, F_(off), F_(manual), F_(auto)) // RC20
MAKE_PSTR_LIST(enum_mode3, F_(night), F_(day), F_(auto)) // RC35, RC30
MAKE_PSTR_LIST(enum_mode4, F_(nofrost), F_(eco), F_(heat), F_(auto)) // JUNKERS
MAKE_PSTR_LIST(enum_mode5, F_(auto), F_(off)) // CRF
MAKE_PSTR_LIST(enum_modetype, F_(eco), F_(comfort))
MAKE_PSTR_LIST(enum_modetype2, F_(day))
MAKE_PSTR_LIST(enum_modetype3, F_(night), F_(day))
MAKE_PSTR_LIST(enum_modetype4, F_(blank), F_(nofrost), F_(eco), F_(heat))
MAKE_PSTR_LIST(enum_modetype4, F_(nofrost), F_(eco), F_(heat))
MAKE_PSTR_LIST(enum_modetype5, F_(off), F_(on))
MAKE_PSTR_LIST(enum_reducemode, F_(nofrost), F_(reduce), F_(room), F_(outdoor))
@@ -341,7 +343,7 @@ MAKE_PSTR_LIST(enum_hamode, F_(off), F_(heat), F_(auto), F_(heat), F_(off), F_(h
// solar list
MAKE_PSTR_LIST(enum_solarmode, F_(constant), F("pwm"), F("analog"))
MAKE_PSTR_LIST(enum_collectortype, F_(blank), F("flat"), F("vacuum"))
MAKE_PSTR_LIST(enum_collectortype, F("flat"), F("vacuum"))
// MQTT topics and full text for values and commands
MAKE_PSTR(homeassistant, "homeassistant/")
@@ -465,10 +467,13 @@ MAKE_PSTR_LIST(tankMiddleTemp, F("tankmiddletemp"), F("ww tank middle temperatur
MAKE_PSTR_LIST(wWStarts, F("wwstarts"), F("ww # starts"))
MAKE_PSTR_LIST(wWStarts2, F("wwstarts2"), F("ww # control starts"))
MAKE_PSTR_LIST(wWWorkM, F("wwworkm"), F("ww active time"))
MAKE_PSTR_LIST(wWHystOn, F("wwhyston"), F("ww hysteresis on temperature"))
MAKE_PSTR_LIST(wWHystOff, F("wwhystoff"), F("ww hysteresis off temperature"))
// thermostat
// extra commands
MAKE_PSTR_LIST(switchtime, F("switchtime"), F("single program switchtime"))
// extra commands, with no long name so they don't show up in WebUI
MAKE_PSTR_LIST(switchtime, F("switchtime"))
MAKE_PSTR_LIST(temp, F("temp"))
MAKE_PSTR_LIST(hatemp, F("hatemp"))
MAKE_PSTR_LIST(hamode, F("hamode"))

View File

@@ -35,8 +35,6 @@ uint32_t Mqtt::publish_time_mixer_;
uint32_t Mqtt::publish_time_sensor_;
uint32_t Mqtt::publish_time_other_;
bool Mqtt::mqtt_enabled_;
uint8_t Mqtt::dallas_format_;
uint8_t Mqtt::bool_format_;
uint8_t Mqtt::ha_climate_format_;
bool Mqtt::ha_enabled_;
uint8_t Mqtt::nested_format_;
@@ -56,11 +54,7 @@ uuid::log::Logger Mqtt::logger_{F_(mqtt), uuid::log::Facility::DAEMON};
// subscribe to an MQTT topic, and store the associated callback function
// only if it already hasn't been added
void Mqtt::subscribe(const uint8_t device_type, const std::string & topic, mqtt_subfunction_p cb) {
if (!enabled()) {
return;
}
void Mqtt::subscribe(const uint8_t device_type, const std::string & topic, mqtt_sub_function_p cb) {
// check if we already have the topic subscribed, if so don't add it again
if (!mqtt_subfunctions_.empty()) {
for (auto & mqtt_subfunction : mqtt_subfunctions_) {
@@ -74,22 +68,22 @@ void Mqtt::subscribe(const uint8_t device_type, const std::string & topic, mqtt_
}
}
LOG_DEBUG(F("Subscribing MQTT topic %s for device type %s"), topic.c_str(), EMSdevice::device_type_2_device_name(device_type).c_str());
// add to MQTT queue as a subscribe operation
auto message = queue_subscribe_message(topic);
if (message == nullptr) {
return;
}
// register in our libary with the callback function.
// We store the original topic without base
mqtt_subfunctions_.emplace_back(device_type, std::move(topic), std::move(cb));
if (!enabled()) {
return;
}
LOG_DEBUG(F("Subscribing MQTT topic %s for device type %s"), topic.c_str(), EMSdevice::device_type_2_device_name(device_type).c_str());
// add to MQTT queue as a subscribe operation
queue_subscribe_message(topic);
}
// subscribe to the command topic if it doesn't exist yet
void Mqtt::register_command(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb, uint8_t flag) {
void Mqtt::register_command(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb, uint8_t flags) {
std::string cmd_topic = EMSdevice::device_type_2_device_name(device_type); // thermostat, boiler, etc...
// see if we have already a handler for the device type (boiler, thermostat). If not add it
@@ -107,10 +101,14 @@ void Mqtt::register_command(const uint8_t device_type, const __FlashStringHelper
LOG_DEBUG(F("Registering MQTT cmd %s with topic %s"), uuid::read_flash_string(cmd).c_str(), EMSdevice::device_type_2_device_name(device_type).c_str());
}
if (!enabled()) {
return;
}
// register the individual commands too (e.g. ems-esp/boiler/wwonetime)
// https://github.com/emsesp/EMS-ESP32/issues/31
std::string topic(MQTT_TOPIC_MAX_SIZE, '\0');
if (subscribe_format_ == 2 && flag == MqttSubFlag::FLAG_HC) {
if (subscribe_format_ == Subscribe_Format::INDIVIDUAL_ALL_HC && ((flags & CommandFlag::MQTT_SUB_FLAG_HC) == CommandFlag::MQTT_SUB_FLAG_HC)) {
std::string topic(MQTT_TOPIC_MAX_SIZE, '\0');
topic = cmd_topic + "/hc1/" + uuid::read_flash_string(cmd);
queue_subscribe_message(topic);
topic = cmd_topic + "/hc2/" + uuid::read_flash_string(cmd);
@@ -119,7 +117,8 @@ void Mqtt::register_command(const uint8_t device_type, const __FlashStringHelper
queue_subscribe_message(topic);
topic = cmd_topic + "/hc4/" + uuid::read_flash_string(cmd);
queue_subscribe_message(topic);
} else if (subscribe_format_ && flag != MqttSubFlag::FLAG_NOSUB) {
} else if (subscribe_format_ != Subscribe_Format::GENERAL && ((flags & CommandFlag::MQTT_SUB_FLAG_NOSUB) == CommandFlag::MQTT_SUB_FLAG_NOSUB)) {
std::string topic(MQTT_TOPIC_MAX_SIZE, '\0');
topic = cmd_topic + "/" + uuid::read_flash_string(cmd);
queue_subscribe_message(topic);
}
@@ -127,7 +126,7 @@ void Mqtt::register_command(const uint8_t device_type, const __FlashStringHelper
// subscribe to an MQTT topic, and store the associated callback function
// For generic functions not tied to a specific device
void Mqtt::subscribe(const std::string & topic, mqtt_subfunction_p cb) {
void Mqtt::subscribe(const std::string & topic, mqtt_sub_function_p cb) {
subscribe(0, topic, cb); // no device_id needed if generic to EMS-ESP
}
@@ -142,7 +141,7 @@ void Mqtt::resubscribe() {
}
for (const auto & cf : Command::commands()) {
std::string topic(MQTT_TOPIC_MAX_SIZE, '\0');
if (subscribe_format_ == 2 && cf.flag_ == MqttSubFlag::FLAG_HC) {
if (subscribe_format_ == Subscribe_Format::INDIVIDUAL_ALL_HC && cf.has_flags(CommandFlag::MQTT_SUB_FLAG_HC)) {
topic = EMSdevice::device_type_2_device_name(cf.device_type_) + "/hc1/" + uuid::read_flash_string(cf.cmd_);
queue_subscribe_message(topic);
topic = EMSdevice::device_type_2_device_name(cf.device_type_) + "/hc2/" + uuid::read_flash_string(cf.cmd_);
@@ -151,7 +150,7 @@ void Mqtt::resubscribe() {
queue_subscribe_message(topic);
topic = EMSdevice::device_type_2_device_name(cf.device_type_) + "/hc4/" + uuid::read_flash_string(cf.cmd_);
queue_subscribe_message(topic);
} else if (subscribe_format_ && cf.flag_ != MqttSubFlag::FLAG_NOSUB) {
} else if (subscribe_format_ != Subscribe_Format::GENERAL && !cf.has_flags(CommandFlag::MQTT_SUB_FLAG_NOSUB)) {
topic = EMSdevice::device_type_2_device_name(cf.device_type_) + "/" + uuid::read_flash_string(cf.cmd_);
queue_subscribe_message(topic);
}
@@ -227,7 +226,7 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) {
shell.printfln(F(" %s/%s"), mqtt_base_.c_str(), mqtt_subfunction.topic_.c_str());
}
for (const auto & cf : Command::commands()) {
if (subscribe_format_ == 2 && cf.flag_ == MqttSubFlag::FLAG_HC) {
if (subscribe_format_ == 2 && cf.has_flags(CommandFlag::MQTT_SUB_FLAG_HC)) {
shell.printfln(F(" %s/%s/hc1/%s"),
mqtt_base_.c_str(),
EMSdevice::device_type_2_device_name(cf.device_type_).c_str(),
@@ -244,7 +243,7 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) {
mqtt_base_.c_str(),
EMSdevice::device_type_2_device_name(cf.device_type_).c_str(),
uuid::read_flash_string(cf.cmd_).c_str());
} else if (subscribe_format_ && cf.flag_ != MqttSubFlag::FLAG_NOSUB) {
} else if (subscribe_format_ == 1 && !cf.has_flags(CommandFlag::MQTT_SUB_FLAG_NOSUB)) {
shell.printfln(F(" %s/%s/%s"),
mqtt_base_.c_str(),
EMSdevice::device_type_2_device_name(cf.device_type_).c_str(),
@@ -348,8 +347,13 @@ void Mqtt::on_message(const char * fulltopic, const char * payload, size_t len)
}
cmd_only++; // skip the /
// LOG_INFO(F("devicetype= %d, topic = %s, cmd = %s, message = %s), mf.device_type_, topic, cmd_only, message);
if (!Command::call(mf.device_type_, cmd_only, message)) {
LOG_ERROR(F("No matching cmd (%s) in topic %s, or invalid data"), cmd_only, topic);
// call command, assume admin authentication is allowed
uint8_t cmd_return = Command::call(mf.device_type_, cmd_only, message, true);
if (cmd_return == CommandRet::NOT_FOUND) {
LOG_ERROR(F("No matching cmd (%s) in topic %s"), cmd_only, topic);
Mqtt::publish(F_(response), "unknown");
} else if (cmd_return != CommandRet::OK) {
LOG_ERROR(F("Invalid data with cmd (%s) in topic %s"), cmd_only, topic);
Mqtt::publish(F_(response), "unknown");
}
return;
@@ -378,29 +382,32 @@ void Mqtt::on_message(const char * fulltopic, const char * payload, size_t len)
n = doc["id"];
}
bool cmd_known = false;
JsonVariant data = doc["data"];
uint8_t cmd_return = CommandRet::OK;
JsonVariant data = doc["data"];
if (data.is<const char *>()) {
cmd_known = Command::call(mf.device_type_, command, data.as<const char *>(), n);
cmd_return = Command::call(mf.device_type_, command, data.as<const char *>(), true, n);
} else if (data.is<int>()) {
char data_str[10];
cmd_known = Command::call(mf.device_type_, command, Helpers::itoa(data_str, (int16_t)data.as<int>()), n);
cmd_return = Command::call(mf.device_type_, command, Helpers::itoa(data_str, (int16_t)data.as<int>()), true, n);
} else if (data.is<float>()) {
char data_str[10];
cmd_known = Command::call(mf.device_type_, command, Helpers::render_value(data_str, (float)data.as<float>(), 2), n);
cmd_return = Command::call(mf.device_type_, command, Helpers::render_value(data_str, (float)data.as<float>(), 2), true, n);
} else if (data.isNull()) {
DynamicJsonDocument resp(EMSESP_JSON_SIZE_XLARGE_DYN);
JsonObject json = resp.to<JsonObject>();
cmd_known = Command::call(mf.device_type_, command, "", n, json);
if (cmd_known && json.size()) {
cmd_return = Command::call(mf.device_type_, command, "", true, n, json);
if (json.size()) {
Mqtt::publish(F_(response), resp.as<JsonObject>());
return;
}
}
if (!cmd_known) {
LOG_ERROR(F("No matching cmd (%s) or invalid data"), command);
if (cmd_return == CommandRet::NOT_FOUND) {
LOG_ERROR(F("No matching cmd (%s)"), command);
Mqtt::publish(F_(response), "unknown");
} else if (cmd_return != CommandRet::OK) {
LOG_ERROR(F("Invalid data for cmd (%s)"), command);
Mqtt::publish(F_(response), "unknown");
}
@@ -484,8 +491,6 @@ void Mqtt::load_settings() {
mqtt_enabled_ = mqttSettings.enabled;
ha_enabled_ = mqttSettings.ha_enabled;
ha_climate_format_ = mqttSettings.ha_climate_format;
dallas_format_ = mqttSettings.dallas_format;
bool_format_ = mqttSettings.bool_format;
nested_format_ = mqttSettings.nested_format;
subscribe_format_ = mqttSettings.subscribe_format;
@@ -639,8 +644,14 @@ void Mqtt::on_connect() {
#ifndef EMSESP_STANDALONE
if (EMSESP::system_.ethernet_connected()) {
doc["ip"] = ETH.localIP().toString();
if (ETH.localIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000") {
doc["ipv6"] = ETH.localIPv6().toString();
}
} else {
doc["ip"] = WiFi.localIP().toString();
if (WiFi.localIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000") {
doc["ipv6"] = WiFi.localIPv6().toString();
}
}
#endif
publish(F_(info), doc.as<JsonObject>()); // topic called "info"
@@ -654,12 +665,9 @@ void Mqtt::on_connect() {
EMSESP::shower_.send_mqtt_stat(false); // Send shower_activated as false
EMSESP::system_.send_heartbeat(); // send heatbeat
if (connectcount_ > 1) {
// we doing a re-connect from a TCP break
// only re-subscribe again to all MQTT topics
resubscribe();
EMSESP::reset_mqtt_ha(); // re-create all HA devices if there are any
}
// re-subscribe to all MQTT topics
resubscribe();
EMSESP::reset_mqtt_ha(); // re-create all HA devices if there are any
publish_retain(F("status"), "online", true); // say we're alive to the Last Will topic, with retain on
@@ -887,7 +895,7 @@ void Mqtt::process_queue() {
// else try and publish it
uint16_t packet_id = mqttClient_->publish(topic, mqtt_qos_, message->retain, message->payload.c_str(), message->payload.size(), false, mqtt_message.id_);
LOG_DEBUG(F("Publishing topic %s (#%02d, retain=%d, try#%d, size %d, pid %d)"),
LOG_DEBUG(F("Publishing topic %s (#%02d, retain=%d, retry=%d, size=%d, pid=%d)"),
topic,
mqtt_message.id_,
message->retain,

View File

@@ -44,12 +44,10 @@ using uuid::console::Shell;
// size of queue
#define MAX_MQTT_MESSAGES 200
enum { BOOL_FORMAT_ONOFF = 1, BOOL_FORMAT_TRUEFALSE, BOOL_FORMAT_10, BOOL_FORMAT_ONOFF_CAP }; // matches Web UI settings
namespace emsesp {
using mqtt_subfunction_p = std::function<bool(const char * message)>;
using cmdfunction_p = std::function<bool(const char * data, const int8_t id)>;
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;
@@ -83,15 +81,27 @@ class Mqtt {
enum Operation { PUBLISH, SUBSCRIBE };
enum Dallas_Format : uint8_t { SENSORID = 1, NUMBER };
enum HA_Climate_Format : uint8_t { CURRENT = 1, SETPOINT, ZERO };
enum HA_Climate_Format : uint8_t {
CURRENT = 1, // 1
SETPOINT, // 2
ZERO // 3
};
// subscribe_format
enum Subscribe_Format : uint8_t {
GENERAL = 0, // 0
INDIVIDUAL_MAIN_HC, // 1
INDIVIDUAL_ALL_HC // 2
};
static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 128; // note this should really match the user setting in mqttSettings.maxTopicLength
static void on_connect();
static void subscribe(const uint8_t device_type, const std::string & topic, mqtt_subfunction_p cb);
static void subscribe(const std::string & topic, mqtt_subfunction_p cb);
static void subscribe(const uint8_t device_type, const std::string & topic, mqtt_sub_function_p cb);
static void subscribe(const std::string & topic, mqtt_sub_function_p cb);
static void resubscribe();
static void publish(const std::string & topic, const std::string & payload);
@@ -112,7 +122,7 @@ class Mqtt {
const uint8_t device_type,
const __FlashStringHelper * entity,
const uint8_t uom = 0);
static void register_command(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb, uint8_t tag = 0);
static void register_command(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb, uint8_t flags = 0);
static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type);
static void show_mqtt(uuid::console::Shell & shell);
@@ -159,14 +169,6 @@ class Mqtt {
return ha_climate_format_;
}
static uint8_t dallas_format() {
return dallas_format_;
}
static uint8_t bool_format() {
return bool_format_;
}
static uint8_t nested_format() {
return nested_format_;
}
@@ -183,10 +185,6 @@ class Mqtt {
ha_climate_format_ = ha_climate_format;
}
static void dallas_format(uint8_t dallas_format) {
dallas_format_ = dallas_format;
}
static void ha_enabled(bool ha_enabled) {
ha_enabled_ = ha_enabled;
}
@@ -241,11 +239,11 @@ class Mqtt {
// function handlers for MQTT subscriptions
struct MQTTSubFunction {
uint8_t device_type_; // which device type, from DeviceType::
const std::string topic_; // short topic name
mqtt_subfunction_p mqtt_subfunction_; // can be empty
uint8_t device_type_; // which device type, from DeviceType::
const std::string topic_; // short topic name
mqtt_sub_function_p mqtt_subfunction_; // can be empty
MQTTSubFunction(uint8_t device_type, const std::string && topic, mqtt_subfunction_p mqtt_subfunction)
MQTTSubFunction(uint8_t device_type, const std::string && topic, mqtt_sub_function_p mqtt_subfunction)
: device_type_(device_type)
, topic_(topic)
, mqtt_subfunction_(mqtt_subfunction) {
@@ -279,8 +277,6 @@ class Mqtt {
static uint32_t publish_time_other_;
static uint32_t publish_time_sensor_;
static bool mqtt_enabled_;
static uint8_t dallas_format_;
static uint8_t bool_format_;
static uint8_t ha_climate_format_;
static bool ha_enabled_;
static uint8_t nested_format_;

View File

@@ -118,8 +118,11 @@ void Shower::send_mqtt_stat(bool state, bool force) {
doc["uniq_id"] = FJSON("shower_active");
doc["~"] = Mqtt::base(); // default ems-esp
doc["stat_t"] = FJSON("~/shower_active");
JsonObject dev = doc.createNestedObject("dev");
JsonArray ids = dev.createNestedArray("ids");
char result[10];
doc[F("payload_on")] = Helpers::render_boolean(result, true);
doc[F("payload_off")] = Helpers::render_boolean(result, false);
JsonObject dev = doc.createNestedObject("dev");
JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp");
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
@@ -132,7 +135,7 @@ void Shower::send_mqtt_stat(bool state, bool force) {
void Shower::shower_alert_stop() {
if (doing_cold_shot_) {
LOG_DEBUG(F("Shower Alert stopped"));
Command::call(EMSdevice::DeviceType::BOILER, "wwtapactivated", "true");
(void)Command::call(EMSdevice::DeviceType::BOILER, "wwtapactivated", "true", true); // no need to check authentication
doing_cold_shot_ = false;
}
}
@@ -140,7 +143,7 @@ void Shower::shower_alert_stop() {
void Shower::shower_alert_start() {
if (shower_alert_) {
LOG_DEBUG(F("Shower Alert started"));
Command::call(EMSdevice::DeviceType::BOILER, "wwtapactivated", "false");
(void)Command::call(EMSdevice::DeviceType::BOILER, "wwtapactivated", "false", true); // no need to check authentication
doing_cold_shot_ = true;
alert_timer_start_ = uuid::get_uptime(); // timer starts now
}
@@ -151,19 +154,9 @@ void Shower::shower_alert_start() {
void Shower::publish_values() {
StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> doc;
if (Mqtt::bool_format() == BOOL_FORMAT_ONOFF) {
doc["shower_timer"] = shower_timer_ ? "on" : "off";
doc["shower_alert"] = shower_alert_ ? "on" : "off";
} else if (Mqtt::bool_format() == BOOL_FORMAT_ONOFF_CAP) {
doc["shower_timer"] = shower_timer_ ? "ON" : "OFF";
doc["shower_alert"] = shower_alert_ ? "ON" : "OFF";
} else if (Mqtt::bool_format() == BOOL_FORMAT_TRUEFALSE) {
doc["shower_timer"] = shower_timer_;
doc["shower_alert"] = shower_alert_;
} else {
doc["shower_timer"] = shower_timer_ ? 1 : 0;
doc["shower_alert"] = shower_alert_ ? 1 : 0;
}
char result[10];
doc["shower_timer"] = Helpers::render_boolean(result, shower_timer_);
doc["shower_alert"] = Helpers::render_boolean(result, shower_alert_);
// only publish shower duration if there is a value
if (duration_ > SHOWER_MIN_DURATION) {

View File

@@ -159,45 +159,37 @@ void System::format(uuid::console::Shell & shell) {
}
void System::syslog_start() {
bool was_enabled = syslog_enabled_;
EMSESP::webSettingsService.read([&](WebSettings & settings) {
syslog_enabled_ = settings.syslog_enabled;
syslog_level_ = settings.syslog_level;
syslog_mark_interval_ = settings.syslog_mark_interval;
syslog_host_ = settings.syslog_host;
syslog_port_ = settings.syslog_port;
});
#ifndef EMSESP_STANDALONE
if (syslog_enabled_) {
#ifndef EMSESP_STANDALONE
syslog_.start();
// start & configure syslog
if (!was_enabled) {
syslog_.start();
EMSESP::logger().info(F("Starting Syslog"));
}
syslog_.log_level((uuid::log::Level)syslog_level_);
#endif
EMSESP::logger().info(F("Starting Syslog"));
}
}
void System::syslog_init(bool refresh) {
if (refresh) {
get_settings();
}
#ifndef EMSESP_STANDALONE
// check for empty or invalid hostname
IPAddress addr;
if (!addr.fromString(syslog_host_.c_str())) {
syslog_enabled_ = false;
}
// in case service is still running, this flushes the queue
// https://github.com/emsesp/EMS-ESP/issues/496
if (!syslog_enabled_) {
syslog_.mark_interval(syslog_mark_interval_);
syslog_.destination(syslog_host_.c_str(), syslog_port_);
syslog_.hostname(hostname().c_str());
} else if (was_enabled) {
// in case service is still running, this flushes the queue
// https://github.com/emsesp/EMS-ESP/issues/496
EMSESP::logger().info(F("Stopping Syslog"));
syslog_.log_level((uuid::log::Level)-1);
syslog_.mark_interval(0);
syslog_.destination((IPAddress)((uint32_t)0));
return;
syslog_.destination("");
}
// start & configure syslog
syslog_.log_level((uuid::log::Level)syslog_level_);
syslog_.mark_interval(syslog_mark_interval_);
syslog_.destination(addr, syslog_port_);
syslog_.hostname(hostname().c_str());
#endif
}
// read all the settings from the config files and store locally
// read all the settings except syslog from the config files and store locally
void System::get_settings() {
EMSESP::webSettingsService.read([&](WebSettings & settings) {
// Button
@@ -206,12 +198,8 @@ void System::get_settings() {
// ADC
analog_enabled_ = settings.analog_enabled;
// Syslog
syslog_enabled_ = settings.syslog_enabled;
syslog_level_ = settings.syslog_level;
syslog_mark_interval_ = settings.syslog_mark_interval;
syslog_host_ = settings.syslog_host;
syslog_port_ = settings.syslog_port;
// Sysclock
low_clock_ = settings.low_clock;
// LED
hide_led_ = settings.hide_led;
@@ -240,12 +228,12 @@ void System::wifi_tweak() {
// WIFI_POWER_2dBm = 8,// 2dBm
// WIFI_POWER_MINUS_1dBm = -4// -1dBm
wifi_power_t p1 = WiFi.getTxPower();
(void)WiFi.setTxPower(WIFI_POWER_19_5dBm);
(void)WiFi.setTxPower(WIFI_POWER_17dBm);
wifi_power_t p2 = WiFi.getTxPower();
bool s1 = WiFi.getSleep();
WiFi.setSleep(false); // turn off sleep - WIFI_PS_NONE
bool s2 = WiFi.getSleep();
LOG_DEBUG(F("Adjusting WiFi - Tx power %d->%d, Sleep %d->%d"), p1, p2, s1, s2);
LOG_DEBUG(F("[DEBUG] Adjusting WiFi - Tx power %d->%d, Sleep %d->%d"), p1, p2, s1, s2);
#endif
}
@@ -275,6 +263,14 @@ void System::start(uint32_t heap_start) {
// load in all the settings first
get_settings();
#ifndef EMSESP_STANDALONE
// disable bluetooth module
periph_module_disable(PERIPH_BT_MODULE);
if (low_clock_) {
setCpuFrequencyMhz(160);
}
#endif
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) {
hostname(networkSettings.hostname.c_str()); // sets the hostname
LOG_INFO(F("System name: %s"), hostname().c_str());
@@ -285,7 +281,7 @@ void System::start(uint32_t heap_start) {
adc_init(false); // analog ADC
button_init(false); // the special button
network_init(false); // network
syslog_init(false); // init SysLog
syslog_start(); // start Syslog
EMSESP::init_uart(); // start UART
}
@@ -296,12 +292,8 @@ void System::adc_init(bool refresh) {
get_settings();
}
#ifndef EMSESP_STANDALONE
// setCpuFrequencyMhz(160); // default is 240
// disable bluetooth & ADC
// disable ADC
/*
btStop();
esp_bt_controller_disable();
if (!analog_enabled_) {
adc_power_release(); // turn off ADC to save power if not needed
}
@@ -415,11 +407,13 @@ void System::loop() {
#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
@@ -501,10 +495,10 @@ void System::measure_analog() {
if (!measure_last_ || (uint32_t)(uuid::get_uptime() - measure_last_) >= SYSTEM_MEASURE_ANALOG_INTERVAL) {
measure_last_ = uuid::get_uptime();
#if defined(ESP32)
uint16_t a = analogRead(36);
#if defined(EMSESP_STANDALONE)
uint16_t a = 0;
#else
uint16_t a = 0; // standalone
uint16_t a = analogReadMilliVolts(ADC1_CHANNEL_0_GPIO_NUM);
#endif
static uint32_t sum_ = 0;
@@ -615,13 +609,14 @@ void System::system_check() {
// these commands respond to the topic "system" and take a payload like {cmd:"", data:"", id:""}
// no individual subscribe for pin command because id is needed
void System::commands_init() {
Command::add(EMSdevice::DeviceType::SYSTEM, F_(pin), System::command_pin, F("set GPIO"), MqttSubFlag::FLAG_NOSUB);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(send), System::command_send, F("send a telegram"));
Command::add(EMSdevice::DeviceType::SYSTEM, F_(publish), System::command_publish, F("force a MQTT publish"));
Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), System::command_fetch, F("refresh all EMS values"));
Command::add_with_json(EMSdevice::DeviceType::SYSTEM, F_(info), System::command_info, F("system status"));
Command::add_with_json(EMSdevice::DeviceType::SYSTEM, F_(settings), System::command_settings, F("list system settings"));
Command::add_with_json(EMSdevice::DeviceType::SYSTEM, F_(commands), System::command_commands, F("list system commands"));
Command::add(EMSdevice::DeviceType::SYSTEM, F_(pin), System::command_pin, F("set GPIO"), CommandFlag::MQTT_SUB_FLAG_NOSUB | CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(send), System::command_send, F("send a telegram"), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(publish), System::command_publish, F("force a MQTT publish"), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), System::command_fetch, F("refresh all EMS values"), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(restart), System::command_restart, F("restarts EMS-ESP"), CommandFlag::ADMIN_ONLY);
Command::add_json(EMSdevice::DeviceType::SYSTEM, F_(info), System::command_info, F("system status"));
Command::add_json(EMSdevice::DeviceType::SYSTEM, F_(settings), System::command_settings, F("list system settings"));
Command::add_json(EMSdevice::DeviceType::SYSTEM, F_(commands), System::command_commands, F("list system commands"));
#if defined(EMSESP_DEBUG)
Command::add(EMSdevice::DeviceType::SYSTEM, F("test"), System::command_test, F("run tests"));
#endif
@@ -667,7 +662,7 @@ void System::show_users(uuid::console::Shell & shell) {
#ifndef EMSESP_STANDALONE
EMSESP::esp8266React.getSecuritySettingsService()->read([&](SecuritySettings & securitySettings) {
for (User user : securitySettings.users) {
for (const User & user : securitySettings.users) {
shell.printfln(F(" username: %s, password: %s, is_admin: %s"), user.username.c_str(), user.password.c_str(), user.admin ? F("yes") : F("no"));
}
});
@@ -708,6 +703,9 @@ void System::show_system(uuid::console::Shell & shell) {
shell.printfln(F("IPv4 address: %s/%s"), uuid::printable_to_string(WiFi.localIP()).c_str(), uuid::printable_to_string(WiFi.subnetMask()).c_str());
shell.printfln(F("IPv4 gateway: %s"), uuid::printable_to_string(WiFi.gatewayIP()).c_str());
shell.printfln(F("IPv4 nameserver: %s"), uuid::printable_to_string(WiFi.dnsIP()).c_str());
if (WiFi.localIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000") {
shell.printfln(F("IPv6 address: %s"), uuid::printable_to_string(WiFi.localIPv6()).c_str());
}
break;
case WL_CONNECT_FAILED:
@@ -738,6 +736,9 @@ void System::show_system(uuid::console::Shell & shell) {
shell.printfln(F("IPv4 address: %s/%s"), uuid::printable_to_string(ETH.localIP()).c_str(), uuid::printable_to_string(ETH.subnetMask()).c_str());
shell.printfln(F("IPv4 gateway: %s"), uuid::printable_to_string(ETH.gatewayIP()).c_str());
shell.printfln(F("IPv4 nameserver: %s"), uuid::printable_to_string(ETH.dnsIP()).c_str());
if (ETH.localIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000") {
shell.printfln(F("IPv6 address: %s"), uuid::printable_to_string(ETH.localIPv6()).c_str());
}
} else {
shell.printfln(F("Ethernet: disconnected"));
}
@@ -746,15 +747,17 @@ void System::show_system(uuid::console::Shell & shell) {
if (!syslog_enabled_) {
shell.printfln(F("Syslog: disabled"));
} else {
shell.printfln(F("Syslog:"));
shell.printfln(F("Syslog: %s"), syslog_.started() ? "started" : "stopped");
shell.print(F(" "));
shell.printfln(F_(host_fmt), !syslog_host_.isEmpty() ? syslog_host_.c_str() : uuid::read_flash_string(F_(unset)).c_str());
shell.printfln(F(" IP: %s"), uuid::printable_to_string(syslog_.ip()).c_str());
shell.print(F(" "));
shell.printfln(F_(port_fmt), syslog_port_);
shell.print(F(" "));
shell.printfln(F_(log_level_fmt), uuid::log::format_level_lowercase(static_cast<uuid::log::Level>(syslog_level_)));
shell.print(F(" "));
shell.printfln(F_(mark_interval_fmt), syslog_mark_interval_);
shell.printfln(F(" Queued: %d"), syslog_.queued());
}
#endif
@@ -772,7 +775,7 @@ bool System::command_commands(const char * value, const int8_t id, JsonObject &
}
// export all settings to JSON text
// e.g. http://ems-esp/api?device=system&cmd=settings
// http://ems-esp/api/system/settings
// value and id are ignored
bool System::command_settings(const char * value, const int8_t id, JsonObject & json) {
JsonObject node;
@@ -780,11 +783,12 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject &
node = json.createNestedObject("System");
node["version"] = EMSESP_APP_VERSION;
// hide ssid from this list
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & settings) {
node = json.createNestedObject("WIFI");
// node["ssid"] = settings.ssid; // commented out - people don't like others to see this
node = json.createNestedObject("Network");
node["hostname"] = settings.hostname;
node["static_ip_config"] = settings.staticIPConfig;
node["enableIPv6"] = settings.enableIPv6;
JsonUtils::writeIP(node, "local_ip", settings.localIP);
JsonUtils::writeIP(node, "gateway_ip", settings.gatewayIP);
JsonUtils::writeIP(node, "subnet_mask", settings.subnetMask);
@@ -820,12 +824,11 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject &
node["publish_time_mixer"] = settings.publish_time_mixer;
node["publish_time_other"] = settings.publish_time_other;
node["publish_time_sensor"] = settings.publish_time_sensor;
node["dallas_format"] = settings.dallas_format;
node["bool_format"] = settings.bool_format;
node["ha_climate_format"] = settings.ha_climate_format;
node["ha_enabled"] = settings.ha_enabled;
node["mqtt_qos"] = settings.mqtt_qos;
node["mqtt_retain"] = settings.mqtt_retain;
node["subscribe_format"] = settings.subscribe_format;
});
#ifndef EMSESP_STANDALONE
@@ -864,6 +867,9 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject &
node["led_gpio"] = settings.led_gpio;
node["hide_led"] = settings.hide_led;
node["notoken_api"] = settings.notoken_api;
node["dallas_format"] = settings.dallas_format;
node["bool_format"] = settings.bool_format;
node["enum_format"] = settings.enum_format;
node["analog_enabled"] = settings.analog_enabled;
node["pbutton_gpio"] = settings.pbutton_gpio;
node["board_profile"] = settings.board_profile;
@@ -872,15 +878,17 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject &
return true;
}
// export status information including some basic settings
// e.g. http://ems-esp/api?device=system&cmd=info
// export status information including the device information
// http://ems-esp/api/system/info
bool System::command_info(const char * value, const int8_t id, JsonObject & json) {
JsonObject node;
node = json.createNestedObject("System");
node["version"] = EMSESP_APP_VERSION;
node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
node["version"] = EMSESP_APP_VERSION;
node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
node["uptime_sec"] = uuid::get_uptime_sec();
#ifndef EMSESP_STANDALONE
node["freemem"] = ESP.getFreeHeap() / 1000L; // kilobytes
#endif
@@ -901,31 +909,37 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & json
}
if (EMSESP::bus_status() != EMSESP::BUS_STATUS_OFFLINE) {
node["bus protocol"] = EMSbus::is_ht3() ? F("HT3") : F("Buderus");
node["#telegrams received"] = EMSESP::rxservice_.telegram_count();
node["#read requests sent"] = EMSESP::txservice_.telegram_read_count();
node["#write requests sent"] = EMSESP::txservice_.telegram_write_count();
node["#incomplete telegrams"] = EMSESP::rxservice_.telegram_error_count();
node["#tx fails"] = EMSESP::txservice_.telegram_fail_count();
node["rx line quality"] = EMSESP::rxservice_.quality();
node["tx line quality"] = EMSESP::txservice_.quality();
node["bus protocol"] = EMSbus::is_ht3() ? F("HT3") : F("Buderus");
node["telegrams received"] = EMSESP::rxservice_.telegram_count();
node["read requests sent"] = EMSESP::txservice_.telegram_read_count();
node["write requests sent"] = EMSESP::txservice_.telegram_write_count();
node["incomplete telegrams"] = EMSESP::rxservice_.telegram_error_count();
node["tx fails"] = EMSESP::txservice_.telegram_fail_count();
node["rx line quality"] = EMSESP::rxservice_.quality();
node["tx line quality"] = EMSESP::txservice_.quality();
if (Mqtt::enabled()) {
node["#MQTT publishes"] = Mqtt::publish_count();
node["#MQTT publish fails"] = Mqtt::publish_fails();
node["MQTT publishes"] = Mqtt::publish_count();
node["MQTT publish fails"] = Mqtt::publish_fails();
}
if (EMSESP::dallas_enabled()) {
node["#dallas sensors"] = EMSESP::sensor_devices().size();
node["#dallas reads"] = EMSESP::sensor_reads();
node["#dallas fails"] = EMSESP::sensor_fails();
node["dallas sensors"] = EMSESP::sensor_devices().size();
node["dallas reads"] = EMSESP::sensor_reads();
node["dallas fails"] = EMSESP::sensor_fails();
}
#ifndef EMSESP_STANDALONE
if (EMSESP::system_.syslog_enabled_) {
node["syslog_ip"] = syslog_.ip();
node["syslog_started"] = syslog_.started();
}
#endif
}
JsonArray devices2 = json.createNestedArray("Devices");
// show EMS devices
JsonArray devices = json.createNestedArray("Devices");
for (const auto & device_class : EMSFactory::device_handlers()) {
for (const auto & emsdevice : EMSESP::emsdevices) {
if ((emsdevice) && (emsdevice->device_type() == device_class.first)) {
JsonObject obj = devices2.createNestedObject();
JsonObject obj = devices.createNestedObject();
obj["type"] = emsdevice->device_type_name();
obj["name"] = emsdevice->to_string();
char result[200];
@@ -933,11 +947,6 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & json
}
}
}
if (EMSESP::sensor_devices().size()) {
JsonObject obj = devices2.createNestedObject();
obj["type"] = F_(Dallassensor);
obj["name"] = F_(Dallassensor);
}
return true;
}
@@ -978,4 +987,12 @@ bool System::load_board_profile(std::vector<uint8_t> & data, const std::string &
return true;
}
// restart command - perform a hard reset
bool System::command_restart(const char * value, const int8_t id) {
#ifndef EMSESP_STANDALONE
ESP.restart();
#endif
return true;
}
} // namespace emsesp

View File

@@ -52,13 +52,15 @@ class System {
static bool command_send(const char * value, const int8_t id);
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_info(const char * value, const int8_t id, JsonObject & json);
static bool command_settings(const char * value, const int8_t id, JsonObject & json);
static bool command_commands(const char * value, const int8_t id, JsonObject & json);
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_info(const char * value, const int8_t id, JsonObject & json);
static bool command_settings(const char * value, const int8_t id, JsonObject & json);
static bool command_commands(const char * value, const int8_t id, JsonObject & json);
void restart();
void format(uuid::console::Shell & shell);
void upload_status(bool in_progress);
@@ -72,7 +74,6 @@ class System {
void send_heartbeat();
void led_init(bool refresh);
void syslog_init(bool refresh);
void adc_init(bool refresh);
void network_init(bool refresh);
void button_init(bool refresh);
@@ -81,6 +82,14 @@ class System {
static bool is_valid_gpio(uint8_t pin);
static bool load_board_profile(std::vector<uint8_t> & data, const std::string & board_profile);
bool analog_enabled() {
return analog_enabled_;
}
uint16_t analog() {
return analog_;
}
std::string hostname() {
return hostname_;
}
@@ -97,9 +106,13 @@ class System {
ethernet_connected_ = b;
}
void network_connected(bool b) {
network_connected_ = b;
}
bool network_connected() {
#ifndef EMSESP_STANDALONE
return (ethernet_connected_ || WiFi.isConnected());
return network_connected_;
#else
return true;
#endif
@@ -147,14 +160,16 @@ class System {
uint32_t last_system_check_ = 0;
bool upload_status_ = false; // true if we're in the middle of a OTA firmware upload
bool ethernet_connected_ = false;
bool network_connected_ = false;
uint16_t analog_;
// settings
std::string hostname_ = "ems-esp";
bool hide_led_;
uint8_t led_gpio_;
bool syslog_enabled_;
bool syslog_enabled_ = false;
bool analog_enabled_;
bool low_clock_;
String board_profile_;
uint8_t pbutton_gpio_;
int8_t syslog_level_;

View File

@@ -126,14 +126,6 @@ std::string Telegram::to_string_message() const {
// checks if we have an Rx telegram that needs processing
void RxService::loop() {
/*
while (!rx_telegrams_.empty()) {
auto telegram = rx_telegrams_.pop().telegram_;
(void)EMSESP::process_telegram(telegram); // further process the telegram
increment_telegram_count(); // increase rx count
}
*/
while (!rx_telegrams_.empty()) {
auto telegram = rx_telegrams_.front().telegram_;
(void)EMSESP::process_telegram(telegram); // further process the telegram
@@ -154,8 +146,12 @@ void RxService::add(uint8_t * data, uint8_t length) {
// validate the CRC. if it fails then increment the number of corrupt/incomplete telegrams and only report to console/syslog
uint8_t crc = calculate_crc(data, length - 1);
if (data[length - 1] != crc) {
telegram_error_count_++;
LOG_ERROR(F("Rx: %s (CRC %02X != %02X)"), Helpers::data_to_hex(data, length).c_str(), data[length - 1], crc);
if ((data[0] & 0x7F) != ems_bus_id()) { // do not count echos as errors
telegram_error_count_++;
LOG_WARNING(F("Incomplete Rx: %s"), Helpers::data_to_hex(data, length - 1).c_str()); // exclude CRC
} else {
LOG_TRACE(F("Incomplete Rx: %s"), Helpers::data_to_hex(data, length - 1).c_str()); // exclude CRC
}
return;
}
@@ -200,6 +196,7 @@ void RxService::add(uint8_t * data, uint8_t length) {
}
// if we're watching and "raw" print out actual telegram as bytes to the console
// including the CRC at the end
if (EMSESP::watch() == EMSESP::Watch::WATCH_RAW) {
uint16_t trace_watch_id = EMSESP::watch_id();
if ((trace_watch_id == WATCH_ID_NONE) || (type_id == trace_watch_id)
@@ -363,7 +360,7 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
LOG_DEBUG(F("Sending %s Tx [#%d], telegram: %s"),
(telegram->operation == Telegram::Operation::TX_WRITE) ? F("write") : F("read"),
tx_telegram.id_,
Helpers::data_to_hex(telegram_raw, length).c_str());
Helpers::data_to_hex(telegram_raw, length - 1).c_str()); // exclude the last CRC byte
set_post_send_query(tx_telegram.validateid_);
// send the telegram to the UART Tx
@@ -427,6 +424,9 @@ void TxService::add(const uint8_t operation,
} else {
tx_telegrams_.emplace_back(tx_telegram_id_++, std::move(telegram), false, validateid); // add to back of queue
}
if (validateid != 0) {
EMSESP::wait_validate(validateid);
}
}
// builds a Tx telegram and adds to queue
@@ -503,6 +503,9 @@ void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t lengt
// tx_telegrams_.push_back(qtxt); // add to back of queue
tx_telegrams_.emplace_back(tx_telegram_id_++, std::move(telegram), false, validate_id); // add to back of queue
}
if (validate_id != 0) {
EMSESP::wait_validate(validate_id);
}
}
// send a Tx telegram to request data from an EMS device
@@ -566,11 +569,12 @@ void TxService::retry_tx(const uint8_t operation, const uint8_t * data, const ui
if (++retry_count_ > MAXIMUM_TX_RETRIES) {
reset_retry_count(); // give up
increment_telegram_fail_count(); // another Tx fail
EMSESP::wait_validate(0); // do not wait for validation
LOG_ERROR(F("Last Tx %s operation failed after %d retries. Ignoring request: %s"),
(operation == Telegram::Operation::TX_WRITE) ? F("Write") : F("Read"),
MAXIMUM_TX_RETRIES),
telegram_last_->to_string().c_str();
MAXIMUM_TX_RETRIES,
telegram_last_->to_string().c_str());
return;
}
@@ -579,7 +583,7 @@ void TxService::retry_tx(const uint8_t operation, const uint8_t * data, const ui
(operation == Telegram::Operation::TX_WRITE) ? F("Write") : F("Read"),
retry_count_,
telegram_last_->to_string().c_str(),
Helpers::data_to_hex(data, length).c_str());
Helpers::data_to_hex(data, length - 1).c_str());
#endif
// add to the top of the queue

View File

@@ -92,10 +92,7 @@ class Telegram {
}
uint8_t val = value;
value = (uint8_t)(((this->message_data[abs_index]) >> (bit)) & 0x01);
if (val != value) {
return true;
}
return false;
return (val != value);
}
// read a value from a telegram. We always store the value, regardless if its garbage
@@ -116,10 +113,16 @@ class Telegram {
for (uint8_t i = 0; i < num_bytes; i++) {
value = (value << 8) + this->message_data[index - this->offset + i]; // shift by byte
}
if (val != value) {
return true;
return (val != value);
}
bool read_enumvalue(uint8_t & value, const uint8_t index, uint8_t start = 0) const {
if ((index < this->offset) || ((index - this->offset) >= this->message_length)) {
return false;
}
return false;
uint8_t val = value;
value = this->message_data[index - this->offset] - start;
return (val != value);
}
private:

View File

@@ -194,7 +194,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
// init stuff
Mqtt::ha_enabled(true);
Mqtt::dallas_format(1);
EMSESP::dallassensor_.dallas_format(1);
Mqtt::ha_climate_format(1);
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
EMSESP::watch(EMSESP::Watch::WATCH_RAW); // raw
@@ -352,7 +352,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
if (emsdevice) {
doc.clear();
JsonObject json = doc.to<JsonObject>();
Command::call(emsdevice->device_type(), "info", nullptr, -1, json);
Command::call(emsdevice->device_type(), "info", nullptr, true, -1, json);
Serial.print(COLOR_YELLOW);
if (json.size() != 0) {
@@ -424,7 +424,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
run_test("boiler");
// device type, command, data
Command::call(EMSdevice::DeviceType::BOILER, "wwtapactivated", "false");
Command::call(EMSdevice::DeviceType::BOILER, "wwtapactivated", "false", true);
}
if (command == "fr120") {
@@ -934,17 +934,41 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
shell.printfln(F("Testing RESTful API..."));
Mqtt::ha_enabled(false);
run_test("general");
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XXLARGE_DYN);
JsonObject json = doc.to<JsonObject>();
AsyncWebServerRequest request;
// GET
request.method(HTTP_GET);
request.url("/api/thermostat/seltemp");
EMSESP::webAPIService.webAPIService_get(&request);
request.url("/api/boiler/syspress");
EMSESP::webAPIService.webAPIService_get(&request);
request.url("/api/system/commands");
EMSESP::webAPIService.webAPIService_get(&request);
request.url("/api/boiler/info");
EMSESP::webAPIService.webAPIService_get(&request);
// POST
request.method(HTTP_POST);
request.url("/api/system/commands");
EMSESP::webAPIService.webAPIService_get(&request);
#endif
}
if (command == "crash") {
shell.printfln(F("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(F("Testing %s"), a);
#pragma GCC diagnostic pop
}
}
// simulates a telegram in the Rx queue, but without the CRC which is added automatically

View File

@@ -39,6 +39,7 @@ namespace emsesp {
// #define EMSESP_DEBUG_DEFAULT "shower_alert"
// #define EMSESP_DEBUG_DEFAULT "310"
#define EMSESP_DEBUG_DEFAULT "api"
// #define EMSESP_DEBUG_DEFAULT "crash"
class Test {
public:

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.1.1"
#define EMSESP_APP_VERSION "3.2.0"

View File

@@ -26,7 +26,7 @@ namespace emsesp {
WebAPIService::WebAPIService(AsyncWebServer * server, SecurityManager * securityManager)
: _securityManager(securityManager)
, _apiHandler("/api", std::bind(&WebAPIService::webAPIService_post, this, _1, _2), 256) { // for POSTS
, _apiHandler("/api", std::bind(&WebAPIService::webAPIService_post, this, _1, _2), 256) { // for POSTS, must use 'Content-Type: application/json' in header
server->on("/api", HTTP_GET, std::bind(&WebAPIService::webAPIService_get, this, _1)); // for GETS
server->addHandler(&_apiHandler);
}
@@ -47,7 +47,7 @@ void WebAPIService::webAPIService_get(AsyncWebServerRequest * request) {
// HTTP_POST | HTTP_PUT | HTTP_PATCH
// POST/PUT /{device}[/{hc}][/{name}]
void WebAPIService::webAPIService_post(AsyncWebServerRequest * request, JsonVariant & json) {
// extra the params from the json body
// if no body then treat it as a secure GET
if (not json.is<JsonObject>()) {
webAPIService_get(request);
return;
@@ -126,7 +126,7 @@ void WebAPIService::parse(AsyncWebServerRequest * request, std::string & device_
id_n = Helpers::atoint(request->getParam("hc")->value().c_str());
}
} else {
// parse paths and json data
// parse paths and json data from the OpenAPI standard
// /{device}[/{hc}][/{name}]
// first param must be a valid device, which includes "system"
device_s = p.paths().front();
@@ -158,35 +158,46 @@ void WebAPIService::parse(AsyncWebServerRequest * request, std::string & device_
// check that we have permissions first. We require authenticating on 1 or more of these conditions:
// 1. any HTTP POSTs or PUTs
// 2. a HTTP GET which has a 'data' parameter which is not empty (to keep v2 compatibility)
auto method = request->method();
bool have_data = !value_s.empty();
bool admin_allowed;
// 2. an HTTP GET which has a 'data' parameter which is not empty (to keep v2 compatibility)
auto method = request->method();
bool have_data = !value_s.empty();
bool authenticated = false;
EMSESP::webSettingsService.read([&](WebSettings & settings) {
Authentication authentication = _securityManager->authenticateRequest(request);
admin_allowed = settings.notoken_api | AuthenticationPredicates::IS_ADMIN(authentication);
authenticated = settings.notoken_api | AuthenticationPredicates::IS_ADMIN(authentication);
});
if ((method != HTTP_GET) || ((method == HTTP_GET) && have_data)) {
if (!admin_allowed) {
if (!authenticated) {
send_message_response(request, 401, "Bad credentials"); // Unauthorized
return;
}
}
// now we have all the parameters go and execute the command
PrettyAsyncJsonResponse * response = new PrettyAsyncJsonResponse(false, EMSESP_JSON_SIZE_XLARGE_DYN);
JsonObject json = response->getRoot();
bool ok = Command::call(device_type, cmd_s.c_str(), (have_data ? value_s.c_str() : nullptr), id_n, json);
// now we have all the parameters go and execute the command
// the function will also determine if authentication is needed to execute its command
uint8_t cmd_reply = Command::call(device_type, cmd_s.c_str(), (have_data ? value_s.c_str() : nullptr), authenticated, id_n, json);
// check for errors
if (!ok) {
if (cmd_reply == CommandRet::NOT_FOUND) {
delete response;
send_message_response(request, 400, "Command not found"); // Bad Request
return;
} else if (cmd_reply == CommandRet::NOT_ALLOWED) {
delete response;
send_message_response(request, 401, "Bad credentials"); // Unauthorized
return;
} else if (cmd_reply != CommandRet::OK) {
delete response;
send_message_response(request, 400, "Problems parsing elements"); // Bad Request
return;
}
if (!json.size()) {
delete response;
send_message_response(request, 200, "OK"); // OK
return;
}

View File

@@ -22,17 +22,19 @@ namespace emsesp {
using namespace std::placeholders; // for `_1` etc
WebDevicesService::WebDevicesService(AsyncWebServer * server, SecurityManager * securityManager)
WebDataService::WebDataService(AsyncWebServer * server, SecurityManager * securityManager)
: _device_dataHandler(DEVICE_DATA_SERVICE_PATH,
securityManager->wrapCallback(std::bind(&WebDevicesService::device_data, this, _1, _2), AuthenticationPredicates::IS_AUTHENTICATED))
securityManager->wrapCallback(std::bind(&WebDataService::device_data, this, _1, _2), AuthenticationPredicates::IS_AUTHENTICATED))
, _writevalue_dataHandler(WRITE_VALUE_SERVICE_PATH,
securityManager->wrapCallback(std::bind(&WebDevicesService::write_value, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) {
server->on(EMSESP_DEVICES_SERVICE_PATH,
securityManager->wrapCallback(std::bind(&WebDataService::write_value, this, _1, _2), AuthenticationPredicates::IS_ADMIN))
, _writesensor_dataHandler(WRITE_SENSOR_SERVICE_PATH,
securityManager->wrapCallback(std::bind(&WebDataService::write_sensor, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) {
server->on(EMSESP_DATA_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WebDevicesService::all_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
securityManager->wrapRequest(std::bind(&WebDataService::all_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
server->on(SCAN_DEVICES_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WebDevicesService::scan_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
securityManager->wrapRequest(std::bind(&WebDataService::scan_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
_device_dataHandler.setMethod(HTTP_POST);
_device_dataHandler.setMaxContentLength(256);
@@ -41,14 +43,18 @@ WebDevicesService::WebDevicesService(AsyncWebServer * server, SecurityManager *
_writevalue_dataHandler.setMethod(HTTP_POST);
_writevalue_dataHandler.setMaxContentLength(256);
server->addHandler(&_writevalue_dataHandler);
_writesensor_dataHandler.setMethod(HTTP_POST);
_writesensor_dataHandler.setMaxContentLength(256);
server->addHandler(&_writesensor_dataHandler);
}
void WebDevicesService::scan_devices(AsyncWebServerRequest * request) {
void WebDataService::scan_devices(AsyncWebServerRequest * request) {
EMSESP::scan_devices();
request->send(200);
}
void WebDevicesService::all_devices(AsyncWebServerRequest * request) {
void WebDataService::all_devices(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_LARGE_DYN);
JsonObject root = response->getRoot();
@@ -69,27 +75,36 @@ void WebDevicesService::all_devices(AsyncWebServerRequest * request) {
JsonArray sensors = root.createNestedArray("sensors");
if (EMSESP::have_sensors()) {
uint8_t i = 1;
char s[8];
for (const auto & sensor : EMSESP::sensor_devices()) {
JsonObject obj = sensors.createNestedObject();
obj["no"] = i++;
obj["id"] = sensor.to_string();
obj["temp"] = Helpers::render_value(s, sensor.temperature_c, 10);
obj["id"] = sensor.to_string(true);
obj["temp"] = (float)(sensor.temperature_c) / 10;
obj["offset"] = (float)(sensor.offset()) / 10;
}
}
if (EMSESP::system_.analog_enabled()) {
root["analog"] = EMSESP::system_.analog();
}
response->setLength();
request->send(response);
}
// The unique_id is the unique record ID from the Web table to identify which device to load
// Compresses the JSON using MsgPack https://msgpack.org/index.html
void WebDevicesService::device_data(AsyncWebServerRequest * request, JsonVariant & json) {
void WebDataService::device_data(AsyncWebServerRequest * request, JsonVariant & json) {
if (json.is<JsonObject>()) {
MsgpackAsyncJsonResponse * response = new MsgpackAsyncJsonResponse(false, EMSESP_JSON_SIZE_XXLARGE_DYN);
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice) {
if (emsdevice->unique_id() == json["id"]) {
// wait max 2.5 sec for updated data (post_send_delay is 2 sec)
for (uint16_t i = 0; i < 2500 && EMSESP::wait_validate(); i++) {
delay(1);
}
EMSESP::wait_validate(0); // reset in case of timeout
#ifndef EMSESP_STANDALONE
JsonObject root = response->getRoot();
emsdevice->generate_values_json_web(root);
@@ -108,7 +123,8 @@ void WebDevicesService::device_data(AsyncWebServerRequest * request, JsonVariant
}
// takes a command and its data value from a specific Device, from the Web
void WebDevicesService::write_value(AsyncWebServerRequest * request, JsonVariant & json) {
// assumes the service has been checked for admin authentication
void WebDataService::write_value(AsyncWebServerRequest * request, JsonVariant & json) {
if (json.is<JsonObject>()) {
JsonObject dv = json["devicevalue"];
uint8_t id = json["id"];
@@ -119,22 +135,22 @@ void WebDevicesService::write_value(AsyncWebServerRequest * request, JsonVariant
if (emsdevice->unique_id() == id) {
const char * cmd = dv["c"];
uint8_t device_type = emsdevice->device_type();
bool ok = false;
uint8_t cmd_return = CommandRet::OK;
char s[10];
// the data could be in any format, but we need string
JsonVariant data = dv["v"];
if (data.is<const char *>()) {
ok = Command::call(device_type, cmd, data.as<const char *>());
cmd_return = Command::call(device_type, cmd, data.as<const char *>(), true);
} else if (data.is<int>()) {
ok = Command::call(device_type, cmd, Helpers::render_value(s, data.as<int16_t>(), 0));
cmd_return = Command::call(device_type, cmd, Helpers::render_value(s, data.as<int16_t>(), 0), true);
} else if (data.is<float>()) {
ok = Command::call(device_type, cmd, Helpers::render_value(s, (float)data.as<float>(), 1));
cmd_return = Command::call(device_type, cmd, Helpers::render_value(s, (float)data.as<float>(), 1), true);
} else if (data.is<bool>()) {
ok = Command::call(device_type, cmd, data.as<bool>() ? "true" : "false");
cmd_return = Command::call(device_type, cmd, data.as<bool>() ? "true" : "false", true);
}
// send "Write command sent to device" or "Write command failed"
AsyncWebServerResponse * response = request->beginResponse(ok ? 200 : 204);
AsyncWebServerResponse * response = request->beginResponse((cmd_return == CommandRet::OK) ? 200 : 204);
request->send(response);
return;
}
@@ -146,4 +162,27 @@ void WebDevicesService::write_value(AsyncWebServerRequest * request, JsonVariant
request->send(response);
}
// takes a sensorname and optional offset from the Web
void WebDataService::write_sensor(AsyncWebServerRequest * request, JsonVariant & json) {
bool ok = false;
if (json.is<JsonObject>()) {
JsonObject sensor = json["sensor"];
// if valid add.
uint8_t no = sensor["no"];
if (no > 0 && no < 100) {
char name[20];
std::string id = sensor["id"];
strlcpy(name, id.c_str(), sizeof(name));
float offset = sensor["offset"]; // this will be a float value. We'll convert it to int and * 10 it
int16_t offset10 = offset * 10;
char idstr[3];
ok = EMSESP::dallassensor_.update(Helpers::itoa(idstr, no, 10), name, offset10);
}
}
AsyncWebServerResponse * response = request->beginResponse(ok ? 200 : 204);
request->send(response);
}
} // namespace emsesp

View File

@@ -16,24 +16,25 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef WebDevicesService_h
#define WebDevicesService_h
#ifndef WebDataService_h
#define WebDataService_h
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#define EMSESP_DEVICES_SERVICE_PATH "/rest/allDevices"
#define EMSESP_DATA_SERVICE_PATH "/rest/data"
#define SCAN_DEVICES_SERVICE_PATH "/rest/scanDevices"
#define DEVICE_DATA_SERVICE_PATH "/rest/deviceData"
#define WRITE_VALUE_SERVICE_PATH "/rest/writeValue"
#define WRITE_SENSOR_SERVICE_PATH "/rest/writeSensor"
namespace emsesp {
class WebDevicesService {
class WebDataService {
public:
WebDevicesService(AsyncWebServer * server, SecurityManager * securityManager);
WebDataService(AsyncWebServer * server, SecurityManager * securityManager);
private:
// GET
@@ -43,8 +44,9 @@ class WebDevicesService {
// POST
void device_data(AsyncWebServerRequest * request, JsonVariant & json);
void write_value(AsyncWebServerRequest * request, JsonVariant & json);
void write_sensor(AsyncWebServerRequest * request, JsonVariant & json);
AsyncCallbackJsonWebHandler _device_dataHandler, _writevalue_dataHandler;
AsyncCallbackJsonWebHandler _device_dataHandler, _writevalue_dataHandler, _writesensor_dataHandler;
};
} // namespace emsesp

View File

@@ -23,30 +23,28 @@ using namespace std::placeholders;
namespace emsesp {
WebLogService::WebLogService(AsyncWebServer * server, SecurityManager * securityManager)
: _events(EVENT_SOURCE_LOG_PATH)
, _setLevel(LOG_SETTINGS_PATH, std::bind(&WebLogService::setLevel, this, _1, _2), 256) { // for POSTS
: events_(EVENT_SOURCE_LOG_PATH)
, setValues_(LOG_SETTINGS_PATH, std::bind(&WebLogService::setValues, this, _1, _2), 256) { // for POSTS
_events.setFilter(securityManager->filterRequest(AuthenticationPredicates::IS_ADMIN));
server->addHandler(&_events);
events_.setFilter(securityManager->filterRequest(AuthenticationPredicates::IS_ADMIN));
server->addHandler(&events_);
server->on(EVENT_SOURCE_LOG_PATH, HTTP_GET, std::bind(&WebLogService::forbidden, this, _1));
// for bring back the whole log
server->on(FETCH_LOG_PATH, HTTP_GET, std::bind(&WebLogService::fetchLog, this, _1));
// get when page is loaded
server->on(LOG_SETTINGS_PATH, HTTP_GET, std::bind(&WebLogService::getLevel, this, _1));
server->on(LOG_SETTINGS_PATH, HTTP_GET, std::bind(&WebLogService::getValues, this, _1));
// for setting a level
server->addHandler(&_setLevel);
// start event source service
start();
server->addHandler(&setValues_);
}
void WebLogService::forbidden(AsyncWebServerRequest * request) {
request->send(403);
}
// start event source service
void WebLogService::start() {
uuid::log::Logger::register_handler(this, uuid::log::Level::INFO); // default is INFO
}
@@ -79,60 +77,96 @@ void WebLogService::operator<<(std::shared_ptr<uuid::log::Message> message) {
if (log_messages_.size() >= maximum_log_messages_) {
log_messages_.pop_front();
}
log_messages_.emplace_back(log_message_id_++, std::move(message));
EMSESP::esp8266React.getNTPSettingsService()->read([&](NTPSettings & settings) {
if (!settings.enabled || (time(nullptr) < 1500000000L)) {
time_offset_ = 0;
} else if (!time_offset_) {
time_offset_ = time(nullptr) - uuid::get_uptime_sec();
}
});
}
void WebLogService::loop() {
if (!_events.count() || log_messages_.empty()) {
if (!events_.count() || log_messages_.empty()) {
return;
}
// put a small delay in
const uint64_t now = uuid::get_uptime_ms();
if (now < last_transmit_ || now - last_transmit_ < 100) {
if (now < last_transmit_ || now - last_transmit_ < REFRESH_SYNC) {
return;
}
// see if we've advanced
if (log_messages_.back().id_ > log_message_id_tail_) {
transmit(log_messages_.back());
log_message_id_tail_ = log_messages_.back().id_;
last_transmit_ = uuid::get_uptime_ms();
if (log_messages_.back().id_ <= log_message_id_tail_) {
return;
}
// flush
for (auto it = log_messages_.begin(); it != log_messages_.end(); it++) {
if (it->id_ > log_message_id_tail_) {
transmit(*it);
}
}
log_message_id_tail_ = log_messages_.back().id_;
last_transmit_ = uuid::get_uptime_ms();
}
// convert time to real offset
char * WebLogService::messagetime(char * out, const uint64_t t) {
if (!time_offset_) {
strcpy(out, uuid::log::format_timestamp_ms(t, 3).c_str());
} else {
time_t t1 = time_offset_ + t / 1000ULL;
strftime(out, 25, "%F %T", localtime(&t1));
snprintf_P(out, 25, "%s.%03d", out, (uint16_t)(t % 1000));
}
return out;
}
// send to web eventsource
void WebLogService::transmit(const QueuedLogMessage & message) {
DynamicJsonDocument jsonDocument = DynamicJsonDocument(EMSESP_JSON_SIZE_SMALL);
JsonObject logEvent = jsonDocument.to<JsonObject>();
logEvent["t"] = uuid::log::format_timestamp_ms(message.content_->uptime_ms, 3);
logEvent["l"] = message.content_->level;
logEvent["n"] = message.content_->name;
logEvent["m"] = message.content_->text;
char time_string[25];
logEvent["t"] = messagetime(time_string, message.content_->uptime_ms);
logEvent["l"] = message.content_->level;
logEvent["i"] = message.id_;
logEvent["n"] = message.content_->name;
logEvent["m"] = message.content_->text;
size_t len = measureJson(jsonDocument);
char * buffer = new char[len + 1];
if (buffer) {
serializeJson(jsonDocument, buffer, len + 1);
_events.send(buffer, "message", millis());
events_.send(buffer, "message", millis());
}
delete[] buffer;
}
// send the current log buffer to the API
// send the complete log buffer to the API, filtering on log level
void WebLogService::fetchLog(AsyncWebServerRequest * request) {
MsgpackAsyncJsonResponse * response = new MsgpackAsyncJsonResponse(false, EMSESP_JSON_SIZE_XXLARGE_DYN); // 8kb buffer
JsonObject root = response->getRoot();
JsonArray log = root.createNestedArray("events");
for (const auto & msg : log_messages_) {
JsonObject logEvent = log.createNestedObject();
auto message = std::move(msg);
logEvent["t"] = uuid::log::format_timestamp_ms(message.content_->uptime_ms, 3);
logEvent["l"] = message.content_->level;
logEvent["n"] = message.content_->name;
logEvent["m"] = message.content_->text;
for (const auto & message : log_messages_) {
if (message.content_->level <= log_level()) {
JsonObject logEvent = log.createNestedObject();
char time_string[25];
logEvent["t"] = messagetime(time_string, message.content_->uptime_ms);
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_;
@@ -141,25 +175,25 @@ void WebLogService::fetchLog(AsyncWebServerRequest * request) {
request->send(response);
}
// sets the level after a POST
void WebLogService::setLevel(AsyncWebServerRequest * request, JsonVariant & json) {
// sets the values like level after a POST
void WebLogService::setValues(AsyncWebServerRequest * request, JsonVariant & json) {
if (not json.is<JsonObject>()) {
return;
}
auto && body = json.as<JsonObject>();
auto && body = json.as<JsonObject>();
uuid::log::Level level = body["level"];
log_level(level);
// send the value back
AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_SMALL);
JsonObject root = response->getRoot();
root["level"] = log_level();
response->setLength();
request->send(response);
uint8_t max_messages = body["max_messages"];
maximum_log_messages(max_messages);
request->send(200); // OK
}
// return the current log level after a GET
void WebLogService::getLevel(AsyncWebServerRequest * request) {
// return the current value settings after a GET
void WebLogService::getValues(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_SMALL);
JsonObject root = response->getRoot();
root["level"] = log_level();

View File

@@ -34,7 +34,8 @@ namespace emsesp {
class WebLogService : public uuid::log::Handler {
public:
static constexpr size_t MAX_LOG_MESSAGES = 30;
static constexpr size_t MAX_LOG_MESSAGES = 50;
static constexpr size_t REFRESH_SYNC = 200;
WebLogService(AsyncWebServer * server, SecurityManager * securityManager);
@@ -48,7 +49,7 @@ class WebLogService : public uuid::log::Handler {
virtual void operator<<(std::shared_ptr<uuid::log::Message> message);
private:
AsyncEventSource _events;
AsyncEventSource events_;
class QueuedLogMessage {
public:
@@ -63,16 +64,19 @@ class WebLogService : public uuid::log::Handler {
void forbidden(AsyncWebServerRequest * request);
void transmit(const QueuedLogMessage & message);
void fetchLog(AsyncWebServerRequest * request);
void getLevel(AsyncWebServerRequest * request);
void getValues(AsyncWebServerRequest * request);
void setLevel(AsyncWebServerRequest * request, JsonVariant & json);
AsyncCallbackJsonWebHandler _setLevel; // for POSTs
char * messagetime(char * out, const uint64_t t);
void setValues(AsyncWebServerRequest * request, JsonVariant & json);
AsyncCallbackJsonWebHandler setValues_; // for POSTs
uint64_t last_transmit_ = 0; // Last transmit time
size_t maximum_log_messages_ = MAX_LOG_MESSAGES; // Maximum number of log messages to buffer before they are output
unsigned long log_message_id_ = 0; // The next identifier to use for queued log messages
unsigned long log_message_id_tail_ = 0; // last event shown on the screen after fetch
std::list<QueuedLogMessage> log_messages_; // Queued log messages, in the order they were received
time_t time_offset_ = 0;
};
} // namespace emsesp

View File

@@ -20,7 +20,7 @@
namespace emsesp {
uint8_t WebSettings::flags_;
uint8_t WebSettings::flags_ = 0;
using namespace std::placeholders; // for `_1` etc
@@ -55,18 +55,34 @@ void WebSettings::read(WebSettings & settings, JsonObject & root) {
root["dallas_parasite"] = settings.dallas_parasite;
root["led_gpio"] = settings.led_gpio;
root["hide_led"] = settings.hide_led;
root["low_clock"] = settings.low_clock;
root["notoken_api"] = settings.notoken_api;
root["analog_enabled"] = settings.analog_enabled;
root["pbutton_gpio"] = settings.pbutton_gpio;
root["solar_maxflow"] = settings.solar_maxflow;
root["board_profile"] = settings.board_profile;
root["dallas_format"] = settings.dallas_format;
root["bool_format"] = settings.bool_format;
root["enum_format"] = settings.enum_format;
for (uint8_t i = 0; i < NUM_SENSOR_NAMES; i++) {
char buf[20];
snprintf_P(buf, sizeof(buf), PSTR("sensor_id%d"), i);
root[buf] = settings.sensor[i].id;
snprintf_P(buf, sizeof(buf), PSTR("sensor_name%d"), i);
root[buf] = settings.sensor[i].name;
snprintf_P(buf, sizeof(buf), PSTR("sensor_offset%d"), i);
root[buf] = settings.sensor[i].offset;
}
}
// call on initialization and also when settings are updated via web or console
StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings) {
// load default GPIO configuration based on board profile
std::vector<uint8_t> data; // led, dallas, rx, tx, button
settings.board_profile = root["board_profile"] | EMSESP_DEFAULT_BOARD_PROFILE;
String old_board_profile = settings.board_profile;
settings.board_profile = root["board_profile"] | EMSESP_DEFAULT_BOARD_PROFILE;
if (!System::load_board_profile(data, settings.board_profile.c_str())) {
settings.board_profile = EMSESP_DEFAULT_BOARD_PROFILE; // invalid board configuration, override the default in case it has been misspelled
}
@@ -77,13 +93,15 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings)
uint8_t default_tx_gpio = data[3];
uint8_t default_pbutton_gpio = data[4];
EMSESP::logger().info(F("EMS-ESP version %s"), EMSESP_APP_VERSION);
if (old_board_profile != settings.board_profile) {
EMSESP::logger().info(F("EMS-ESP version %s"), EMSESP_APP_VERSION);
// check to see if we have a settings file, if not it's a fresh install
if (!root.size()) {
EMSESP::logger().info(F("Initializing configuration with board profile %s"), settings.board_profile.c_str());
} else {
EMSESP::logger().info(F("Using configuration from board profile %s"), settings.board_profile.c_str());
// check to see if we have a settings file, if not it's a fresh install
if (!root.size()) {
EMSESP::logger().info(F("Initializing configuration with board profile %s"), settings.board_profile.c_str());
} else {
EMSESP::logger().info(F("Using configuration from board profile %s"), settings.board_profile.c_str());
}
}
int prev;
@@ -116,19 +134,19 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings)
settings.syslog_mark_interval = root["syslog_mark_interval"] | EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL;
check_flag(prev, settings.syslog_mark_interval, ChangeFlags::SYSLOG);
#ifndef EMSESP_STANDALONE
String old_syslog_host = settings.syslog_host;
settings.syslog_host = root["syslog_host"] | EMSESP_DEFAULT_SYSLOG_HOST;
if (old_syslog_host.equals(settings.syslog_host.c_str())) {
if (!old_syslog_host.equals(settings.syslog_host)) {
add_flags(ChangeFlags::SYSLOG);
}
#endif
prev = settings.syslog_port;
settings.syslog_port = root["syslog_port"] | EMSESP_DEFAULT_SYSLOG_PORT;
check_flag(prev, settings.syslog_port, ChangeFlags::SYSLOG);
prev = settings.trace_raw;
settings.trace_raw = root["trace_raw"] | EMSESP_DEFAULT_TRACELOG_RAW;
check_flag(prev, settings.trace_raw, ChangeFlags::SYSLOG);
EMSESP::trace_raw(settings.trace_raw);
// adc
@@ -165,14 +183,35 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings)
settings.hide_led = root["hide_led"] | EMSESP_DEFAULT_HIDE_LED;
check_flag(prev, settings.hide_led, ChangeFlags::LED);
// these both need reboots to be applied
// these need reboots to be applied
settings.ems_bus_id = root["ems_bus_id"] | EMSESP_DEFAULT_EMS_BUS_ID;
settings.master_thermostat = root["master_thermostat"] | EMSESP_DEFAULT_MASTER_THERMOSTAT;
settings.low_clock = root["low_clock"] | false;
;
// doesn't need any follow-up actions
settings.notoken_api = root["notoken_api"] | EMSESP_DEFAULT_NOTOKEN_API;
settings.solar_maxflow = root["solar_maxflow"] | EMSESP_DEFAULT_SOLAR_MAXFLOW;
settings.dallas_format = root["dallas_format"] | EMSESP_DEFAULT_DALLAS_FORMAT;
EMSESP::dallassensor_.dallas_format(settings.dallas_format);
settings.bool_format = root["bool_format"] | EMSESP_DEFAULT_BOOL_FORMAT;
EMSESP::bool_format(settings.bool_format);
settings.enum_format = root["enum_format"] | EMSESP_DEFAULT_ENUM_FORMAT;
EMSESP::enum_format(settings.enum_format);
for (uint8_t i = 0; i < NUM_SENSOR_NAMES; i++) {
char buf[20];
snprintf_P(buf, sizeof(buf), PSTR("sensor_id%d"), i);
settings.sensor[i].id = root[buf] | EMSESP_DEFAULT_SENSOR_NAME;
snprintf_P(buf, sizeof(buf), PSTR("sensor_name%d"), i);
settings.sensor[i].name = root[buf] | EMSESP_DEFAULT_SENSOR_NAME;
snprintf_P(buf, sizeof(buf), PSTR("sensor_offset%d"), i);
settings.sensor[i].offset = root[buf] | 0;
}
return StateUpdateResult::CHANGED;
}
@@ -192,8 +231,7 @@ void WebSettingsService::onUpdate() {
}
if (WebSettings::has_flags(WebSettings::ChangeFlags::SYSLOG)) {
EMSESP::system_.syslog_init(true); // reload settings
EMSESP::system_.syslog_start(); // re-start (or stop)
EMSESP::system_.syslog_start(); // re-start (or stop)
}
if (WebSettings::has_flags(WebSettings::ChangeFlags::ADC)) {
@@ -207,6 +245,8 @@ void WebSettingsService::onUpdate() {
if (WebSettings::has_flags(WebSettings::ChangeFlags::LED)) {
EMSESP::system_.led_init(true); // reload settings
}
WebSettings::reset_flags();
}
void WebSettingsService::begin() {
@@ -233,6 +273,7 @@ void WebSettingsService::board_profile(AsyncWebServerRequest * request, JsonVari
root["tx_gpio"] = data[3];
root["pbutton_gpio"] = data[4];
} else {
delete response;
AsyncWebServerResponse * response = request->beginResponse(200);
request->send(response);
return;

View File

@@ -28,8 +28,13 @@
#define EMSESP_SETTINGS_SERVICE_PATH "/rest/emsespSettings"
#define EMSESP_BOARD_PROFILE_SERVICE_PATH "/rest/boardProfile"
#define NUM_SENSOR_NAMES 10
namespace emsesp {
enum { BOOL_FORMAT_ONOFF = 1, BOOL_FORMAT_ONOFF_CAP, BOOL_FORMAT_TRUEFALSE, BOOL_FORMAT_10 }; // matches Web UI settings
enum { ENUM_FORMAT_TEXT = 1, ENUM_FORMAT_NUMBER }; // matches Web UI settings
class WebSettings {
public:
uint8_t tx_mode;
@@ -50,11 +55,21 @@ class WebSettings {
bool dallas_parasite;
uint8_t led_gpio;
bool hide_led;
bool low_clock;
bool notoken_api;
bool analog_enabled;
uint8_t pbutton_gpio;
uint8_t solar_maxflow;
String board_profile;
uint8_t dallas_format;
uint8_t bool_format;
uint8_t enum_format;
struct {
String id;
String name;
int16_t offset;
} sensor[NUM_SENSOR_NAMES];
static void read(WebSettings & settings, JsonObject & root);
static StateUpdateResult update(JsonObject & root, WebSettings & settings);

View File

@@ -35,15 +35,20 @@ void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
switch (event) {
case SYSTEM_EVENT_STA_DISCONNECTED:
EMSESP::logger().info(F("WiFi Disconnected. Reason code=%d"), info.disconnected.reason);
EMSESP::system_.network_connected(false);
break;
case SYSTEM_EVENT_STA_GOT_IP:
#ifndef EMSESP_STANDALONE
EMSESP::logger().info(F("WiFi Connected with IP=%s, hostname=%s"), WiFi.localIP().toString().c_str(), WiFi.getHostname());
#endif
EMSESP::system_.wifi_tweak();
EMSESP::system_.send_heartbeat();
EMSESP::system_.syslog_start();
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) {
if (!networkSettings.enableIPv6) {
EMSESP::system_.network_connected(true);
EMSESP::system_.send_heartbeat();
EMSESP::system_.syslog_start();
}
});
break;
case SYSTEM_EVENT_ETH_START:
@@ -57,8 +62,13 @@ void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
#ifndef EMSESP_STANDALONE
EMSESP::logger().info(F("Ethernet Connected with IP=%s, speed %d Mbps"), ETH.localIP().toString().c_str(), ETH.linkSpeed());
#endif
EMSESP::system_.send_heartbeat();
EMSESP::system_.syslog_start();
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) {
if (!networkSettings.enableIPv6) {
EMSESP::system_.network_connected(true);
EMSESP::system_.send_heartbeat();
EMSESP::system_.syslog_start();
}
});
EMSESP::system_.ethernet_connected(true);
}
break;
@@ -66,13 +76,44 @@ void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
case SYSTEM_EVENT_ETH_DISCONNECTED:
EMSESP::logger().info(F("Ethernet Disconnected"));
EMSESP::system_.ethernet_connected(false);
EMSESP::system_.network_connected(false);
break;
case SYSTEM_EVENT_ETH_STOP:
EMSESP::logger().info(F("Ethernet Stopped"));
EMSESP::system_.ethernet_connected(false);
EMSESP::system_.network_connected(false);
break;
#ifndef EMSESP_STANDALONE
case SYSTEM_EVENT_STA_CONNECTED:
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) {
if (networkSettings.enableIPv6) {
WiFi.enableIpV6();
}
});
break;
case SYSTEM_EVENT_ETH_CONNECTED:
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) {
if (networkSettings.enableIPv6) {
ETH.enableIpV6();
}
});
break;
case SYSTEM_EVENT_GOT_IP6:
if (EMSESP::system_.ethernet_connected()) {
EMSESP::logger().info(F("Ethernet Connected with IP=%s, speed %d Mbps"), ETH.localIPv6().toString().c_str(), ETH.linkSpeed());
} else {
EMSESP::logger().info(F("WiFi Connected with IP=%s, hostname=%s"), WiFi.localIPv6().toString().c_str(), WiFi.getHostname());
}
EMSESP::system_.network_connected(true);
EMSESP::system_.send_heartbeat();
EMSESP::system_.syslog_start();
break;
#endif
default:
break;
}