Merge remote-tracking branch 'origin/dev'

This commit is contained in:
proddy
2021-06-26 11:02:55 +02:00
353 changed files with 10069 additions and 10252 deletions

View File

@@ -1,91 +0,0 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "emsesp.h"
using namespace std::placeholders; // for `_1` etc
namespace emsesp {
WebAPIService::WebAPIService(AsyncWebServer * server) {
server->on(EMSESP_API_SERVICE_PATH, HTTP_GET, std::bind(&WebAPIService::webAPIService, this, _1));
}
// e.g. http://ems-esp/api?device=boiler&cmd=wwtemp&data=20&id=1
void WebAPIService::webAPIService(AsyncWebServerRequest * request) {
// must have device and cmd parameters
if ((!request->hasParam(F_(device))) || (!request->hasParam(F_(cmd)))) {
request->send(400, "text/plain", F("Invalid syntax"));
return;
}
// get device
String device = request->getParam(F_(device))->value();
uint8_t device_type = EMSdevice::device_name_2_device_type(device.c_str());
if (device_type == emsesp::EMSdevice::DeviceType::UNKNOWN) {
request->send(400, "text/plain", F("Invalid device"));
return;
}
// get cmd, we know we have one
String cmd = request->getParam(F_(cmd))->value();
String data;
if (request->hasParam(F_(data))) {
data = request->getParam(F_(data))->value();
}
String id;
if (request->hasParam(F_(id))) {
id = request->getParam(F_(id))->value();
}
if (id.isEmpty()) {
id = "-1";
}
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN);
JsonObject json = doc.to<JsonObject>();
bool ok = false;
// execute the command
if (data.isEmpty()) {
ok = Command::call(device_type, cmd.c_str(), nullptr, id.toInt(), json); // command only
} else {
// we only allow commands with parameters if the API is enabled
bool api_enabled;
EMSESP::webSettingsService.read([&](WebSettings & settings) { api_enabled = settings.api_enabled; });
if (api_enabled) {
ok = Command::call(device_type, cmd.c_str(), data.c_str(), id.toInt(), json); // has cmd, data and id
} else {
request->send(401, "text/plain", F("Unauthorized"));
return;
}
}
if (ok && json.size()) {
// send json output back to web
doc.shrinkToFit();
std::string buffer;
serializeJsonPretty(doc, buffer);
request->send(200, "text/plain;charset=utf-8", buffer.c_str());
return;
}
request->send(200, "text/plain", ok ? F("OK") : F("Invalid"));
}
} // namespace emsesp

View File

@@ -1,40 +0,0 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef WebAPIService_h
#define WebAPIService_h
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
#define EMSESP_API_SERVICE_PATH "/api"
namespace emsesp {
class WebAPIService {
public:
WebAPIService(AsyncWebServer * server);
private:
void webAPIService(AsyncWebServerRequest * request);
};
} // namespace emsesp
#endif

View File

@@ -30,24 +30,24 @@ std::vector<Command::CmdFunction> Command::cmdfunctions_;
// 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) {
std::string dname = EMSdevice::device_type_2_device_name(device_type);
int8_t id_new = id;
char cmd_new[20];
int8_t id_new = id;
char cmd_new[20] = {'\0'};
strlcpy(cmd_new, cmd, 20);
check_command(cmd_new, cmd, id_new);
auto cf = find_command(device_type, cmd_new);
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, dname.c_str());
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
}
#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_DEBUG(F("[DEBUG] 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_DEBUG(F("[DEBUG] 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_DEBUG(F("[DEBUG] Calling %s command '%s', value %s, id is %d"), dname.c_str(), cmd, value, id);
}
#endif
@@ -58,20 +58,20 @@ bool Command::call(const uint8_t device_type, const char * cmd, const char * val
// 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) {
int8_t id_new = id;
char cmd_new[20];
int8_t id_new = id;
char cmd_new[20] = {'\0'};
strlcpy(cmd_new, cmd, 20);
check_command(cmd_new, cmd, id_new);
auto cf = find_command(device_type, cmd_new);
auto cf = find_command(device_type, cmd_new, id_new);
#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_DEBUG(F("[DEBUG] 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_DEBUG(F("[DEBUG] 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_DEBUG(F("[DEBUG] Calling %s command '%s', value %s, id is %d"), dname.c_str(), cmd, value, id);
}
#endif
@@ -81,6 +81,7 @@ bool Command::call(const uint8_t device_type, const char * cmd, const char * val
return false;
}
// 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);
}
@@ -88,50 +89,78 @@ bool Command::call(const uint8_t device_type, const char * cmd, const char * val
if (cf->cmdfunction_json_) {
return ((cf->cmdfunction_json_)(value, id_new, json));
} else {
if (value == nullptr || strlen(value) == 0 || strcmp(value, "?") == 0 || strcmp(value, "*") == 0) {
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 ((cf->cmdfunction_)(value, id_new));
}
}
char * Command::check_command(char * out, const char * cmd, int8_t & id) {
// strip prefixes, check, and find command
Command::CmdFunction * Command::find_command(const uint8_t device_type, char * cmd, int8_t & id) {
// 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
strlcpy(out, cmd, 20);
for (char * p = out; *p; p++) {
for (char * p = cmd; *p; p++) {
*p = tolower(*p);
}
//scan for prefix hc.
// scan for prefix hc.
for (uint8_t i = DeviceValueTAG::TAG_HC1; i <= DeviceValueTAG::TAG_HC4; i++) {
if ((strncmp(out, EMSdevice::tag_to_string(i).c_str(), 3) == 0) && (strlen(out) > 4)) {
strcpy(out, &out[4]);
const char * tag = EMSdevice::tag_to_string(i).c_str();
uint8_t len = strlen(tag);
if (strncmp(cmd, tag, len) == 0) {
if (cmd[len] != '\0') {
strcpy(cmd, &cmd[len + 1]);
} else {
strcpy(cmd, &cmd[len]);
}
id = 1 + i - DeviceValueTAG::TAG_HC1;
break;
}
}
//scan for prefix wwc.
// scan for prefix wwc.
for (uint8_t i = DeviceValueTAG::TAG_WWC1; i <= DeviceValueTAG::TAG_WWC4; i++) {
if ((strncmp(out, EMSdevice::tag_to_string(i).c_str(), 4) == 0) && (strlen(out) > 5)) {
strcpy(out, &out[5]);
const char * tag = EMSdevice::tag_to_string(i).c_str();
uint8_t len = strlen(tag);
if (strncmp(cmd, tag, len) == 0) {
if (cmd[len] != '\0') {
strcpy(cmd, &cmd[len + 1]);
} else {
strcpy(cmd, &cmd[len]);
}
id = 8 + i - DeviceValueTAG::TAG_WWC1;
break;
}
}
return out;
// empty command after processing prefix is info
if (cmd[0] == '\0') {
strlcpy(cmd, "info", 20);
}
return find_command(device_type, cmd);
}
// 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, uint8_t flag) {
void Command::add(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb, const __FlashStringHelper * description, uint8_t flag) {
// 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, flag, cmd, cb, nullptr);
// 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);
cmdfunctions_.emplace_back(device_type, flag, cmd, cb, nullptr, description, hidden); // callback for json is nullptr
// see if we need to subscribe
if (Mqtt::enabled()) {
@@ -140,13 +169,15 @@ void Command::add(const uint8_t device_type, const __FlashStringHelper * cmd, cm
}
// add a command to the list, which does return json object as output
void Command::add_with_json(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_json_p cb) {
// 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) {
// 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); // add command
cmdfunctions_.emplace_back(device_type, MqttSubFlag::FLAG_NOSUB, cmd, nullptr, cb, description, hidden); // callback for json is included
}
// see if a command exists for that device type
@@ -172,17 +203,87 @@ Command::CmdFunction * Command::find_command(const uint8_t device_type, const ch
return nullptr; // command not found
}
// output list of all commands to console for a specific DeviceType
void Command::show(uuid::console::Shell & shell, uint8_t device_type) {
// list all commands for a specific device, output as json
bool Command::list(const uint8_t device_type, JsonObject & json) {
if (cmdfunctions_.empty()) {
shell.println(F("No commands"));
json["message"] = "no commands available";
return false;
}
// create a list of commands, sort them
std::list<std::string> sorted_cmds;
for (const auto & cf : cmdfunctions_) {
if (cf.device_type_ == device_type) {
shell.printf("%s ", uuid::read_flash_string(cf.cmd_).c_str());
if ((cf.device_type_ == device_type) && !cf.hidden_) {
sorted_cmds.push_back(uuid::read_flash_string(cf.cmd_));
}
}
sorted_cmds.sort();
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_))) {
json[cl] = cf.description_;
}
}
}
return true;
}
// output list of all commands to console for a specific DeviceType
void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbose) {
if (cmdfunctions_.empty()) {
shell.println(F("No commands available"));
return;
}
// 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_) {
sorted_cmds.push_back(uuid::read_flash_string(cf.cmd_));
}
}
sorted_cmds.sort();
// if not in verbose mode, just print them on a single line
if (!verbose) {
for (auto & cl : sorted_cmds) {
shell.print(cl);
shell.print(" ");
}
shell.println();
return;
}
// verbose mode
shell.println();
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_))) {
uint8_t i = cl.length();
shell.print(" ");
if (cf.flag_ == FLAG_HC) {
shell.print("[hc] ");
i += 5;
} else if (cf.flag_ == FLAG_WWC) {
shell.print("[wwc] ");
i += 6;
}
shell.print(cl);
// pad with spaces
while (i++ < 22) {
shell.print(' ');
}
shell.print(COLOR_BRIGHT_CYAN);
shell.print(uuid::read_flash_string(cf.description_));
shell.print(COLOR_RESET);
}
}
shell.println();
}
shell.println();
}
@@ -234,24 +335,31 @@ 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: "));
// show system first
shell.print(COLOR_BOLD_ON);
shell.printf(" %s: ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::SYSTEM).c_str());
show(shell, EMSdevice::DeviceType::SYSTEM);
shell.print(COLOR_RESET);
show(shell, EMSdevice::DeviceType::SYSTEM, true);
// show sensor
if (EMSESP::have_sensors()) {
shell.print(COLOR_BOLD_ON);
shell.printf(" %s: ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::DALLASSENSOR).c_str());
show(shell, EMSdevice::DeviceType::DALLASSENSOR);
shell.print(COLOR_RESET);
show(shell, EMSdevice::DeviceType::DALLASSENSOR, true);
}
// do this in the order of factory classes to keep a consistent order when displaying
for (const auto & device_class : EMSFactory::device_handlers()) {
if (Command::device_has_commands(device_class.first)) {
shell.print(COLOR_BOLD_ON);
shell.printf(" %s: ", EMSdevice::device_type_2_device_name(device_class.first).c_str());
show(shell, device_class.first);
shell.print(COLOR_RESET);
show(shell, device_class.first, true);
}
}
}

View File

@@ -45,13 +45,23 @@ class Command {
const __FlashStringHelper * cmd_;
cmdfunction_p cmdfunction_;
cmdfunction_json_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 __FlashStringHelper * cmd, cmdfunction_p cmdfunction, cmdfunction_json_p cmdfunction_json)
CmdFunction(const uint8_t device_type,
const uint8_t flag,
const __FlashStringHelper * cmd,
cmdfunction_p cmdfunction,
cmdfunction_json_p cmdfunction_json,
const __FlashStringHelper * description,
bool hidden = false)
: device_type_(device_type)
, flag_(flag)
, cmd_(cmd)
, cmdfunction_(cmdfunction)
, cmdfunction_json_(cmdfunction_json) {
, cmdfunction_json_(cmdfunction_json)
, description_(description)
, hidden_(hidden) {
}
};
@@ -59,18 +69,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 = 0);
static void add(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb, uint8_t flag = 0);
static void add_with_json(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_json_p cb);
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 void show_all(uuid::console::Shell & shell);
static Command::CmdFunction * find_command(const uint8_t device_type, const char * cmd);
static char * check_command(char * out, const char * cmd, int8_t & id);
static Command::CmdFunction * find_command(const uint8_t device_type, char * cmd, int8_t & id);
static void show(uuid::console::Shell & shell, uint8_t device_type);
static void show(uuid::console::Shell & shell, uint8_t device_type, bool verbose);
static void show_devices(uuid::console::Shell & shell);
static bool device_has_commands(const uint8_t device_type);
static bool list(const uint8_t device_type, JsonObject & json);
private:
static uuid::log::Logger logger_;

View File

@@ -36,7 +36,9 @@ static std::shared_ptr<EMSESPShell> shell;
std::vector<bool> EMSESPStreamConsole::ptys_;
#ifndef EMSESP_STANDALONE
uuid::telnet::TelnetService telnet_([](Stream & stream, const IPAddress & addr, uint16_t port) -> std::shared_ptr<uuid::console::Shell> { return std::make_shared<EMSESPStreamConsole>(stream, addr, port); });
uuid::telnet::TelnetService telnet_([](Stream & stream, const IPAddress & addr, uint16_t port) -> std::shared_ptr<uuid::console::Shell> {
return std::make_shared<EMSESPStreamConsole>(stream, addr, port);
});
#endif
EMSESPShell::EMSESPShell()
@@ -102,30 +104,42 @@ void EMSESPShell::add_console_commands() {
// just in case, remove everything
commands->remove_all_commands();
commands->add_command(ShellContext::MAIN, CommandFlags::USER, flash_string_vector{F_(show)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.printfln(F("%s%sEMS-ESP version %s%s"), COLOR_BRIGHT_GREEN, COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_RESET);
shell.println();
EMSESP::show_device_values(shell);
EMSESP::show_sensor_values(shell);
});
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(show)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.printfln(F("%s%sEMS-ESP version %s%s"), COLOR_BRIGHT_GREEN, COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_RESET);
shell.println();
EMSESP::show_device_values(shell);
EMSESP::show_sensor_values(shell);
});
commands->add_command(ShellContext::MAIN, CommandFlags::USER, flash_string_vector{F_(show), F_(devices)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::show_devices(shell);
});
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(show), F_(devices)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::show_devices(shell); });
commands->add_command(ShellContext::MAIN, CommandFlags::USER, flash_string_vector{F_(show), F_(ems)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::show_ems(shell); });
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(show), F_(ems)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::show_ems(shell); });
commands->add_command(ShellContext::MAIN, CommandFlags::USER, flash_string_vector{F_(show), F_(values)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::show_device_values(shell);
});
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(show), F_(values)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::show_device_values(shell); });
commands->add_command(ShellContext::MAIN, CommandFlags::USER, flash_string_vector{F_(show), F_(mqtt)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { Mqtt::show_mqtt(shell); });
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(show), F_(mqtt)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { Mqtt::show_mqtt(shell); });
commands->add_command(ShellContext::MAIN, CommandFlags::USER, flash_string_vector{F_(show), F_(commands)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
Command::show_all(shell);
});
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(show), F_(commands)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { Command::show_all(shell); });
commands->add_command(
ShellContext::MAIN,
@@ -157,62 +171,75 @@ void EMSESPShell::add_console_commands() {
};
});
commands->add_command(ShellContext::MAIN, CommandFlags::ADMIN, flash_string_vector{F_(set), F_(tx_mode)}, flash_string_vector{F_(n_mandatory)}, [](Shell & shell, const std::vector<std::string> & arguments) {
uint8_t tx_mode = std::strtol(arguments[0].c_str(), nullptr, 10);
// save the tx_mode
EMSESP::webSettingsService.update(
[&](WebSettings & settings) {
settings.tx_mode = tx_mode;
shell.printfln(F_(tx_mode_fmt), settings.tx_mode);
return StateUpdateResult::CHANGED;
},
"local");
});
commands->add_command(ShellContext::MAIN,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(tx_mode)},
flash_string_vector{F_(n_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
uint8_t tx_mode = std::strtol(arguments[0].c_str(), nullptr, 10);
// save the tx_mode
EMSESP::webSettingsService.update(
[&](WebSettings & settings) {
settings.tx_mode = tx_mode;
shell.printfln(F_(tx_mode_fmt), settings.tx_mode);
return StateUpdateResult::CHANGED;
},
"local");
});
commands->add_command(ShellContext::MAIN, CommandFlags::ADMIN, flash_string_vector{F_(scan), F_(devices)}, flash_string_vector{F_(deep_optional)}, [](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.size() == 0) {
EMSESP::scan_devices();
} else {
shell.printfln(F("Performing a deep scan..."));
EMSESP::clear_all_devices();
std::vector<uint8_t> Device_Ids;
commands->add_command(ShellContext::MAIN,
CommandFlags::ADMIN,
flash_string_vector{F_(scan), F_(devices)},
flash_string_vector{F_(deep_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.size() == 0) {
EMSESP::scan_devices();
} else {
shell.printfln(F("Performing a deep scan..."));
EMSESP::clear_all_devices();
std::vector<uint8_t> Device_Ids;
Device_Ids.push_back(0x08); // Boilers - 0x08
Device_Ids.push_back(0x38); // HeatPump - 0x38
Device_Ids.push_back(0x30); // Solar Module - 0x30
Device_Ids.push_back(0x09); // Controllers - 0x09
Device_Ids.push_back(0x02); // Connect - 0x02
Device_Ids.push_back(0x48); // Gateway - 0x48
Device_Ids.push_back(0x20); // Mixer Devices - 0x20
Device_Ids.push_back(0x21); // Mixer Devices - 0x21
Device_Ids.push_back(0x22); // Mixer Devices - 0x22
Device_Ids.push_back(0x23); // Mixer Devices - 0x23
Device_Ids.push_back(0x28); // Mixer Devices WW- 0x28
Device_Ids.push_back(0x29); // Mixer Devices WW- 0x29
Device_Ids.push_back(0x10); // Thermostats - 0x10
Device_Ids.push_back(0x17); // Thermostats - 0x17
Device_Ids.push_back(0x18); // Thermostat remote - 0x18
Device_Ids.push_back(0x19); // Thermostat remote - 0x19
Device_Ids.push_back(0x1A); // Thermostat remote - 0x1A
Device_Ids.push_back(0x1B); // Thermostat remote - 0x1B
Device_Ids.push_back(0x11); // Switches - 0x11
Device_Ids.push_back(0x08); // Boilers - 0x08
Device_Ids.push_back(0x38); // HeatPump - 0x38
Device_Ids.push_back(0x30); // Solar Module - 0x30
Device_Ids.push_back(0x09); // Controllers - 0x09
Device_Ids.push_back(0x02); // Connect - 0x02
Device_Ids.push_back(0x48); // Gateway - 0x48
Device_Ids.push_back(0x20); // Mixer Devices - 0x20
Device_Ids.push_back(0x21); // Mixer Devices - 0x21
Device_Ids.push_back(0x22); // Mixer Devices - 0x22
Device_Ids.push_back(0x23); // Mixer Devices - 0x23
Device_Ids.push_back(0x28); // Mixer Devices WW- 0x28
Device_Ids.push_back(0x29); // Mixer Devices WW- 0x29
Device_Ids.push_back(0x10); // Thermostats - 0x10
Device_Ids.push_back(0x17); // Thermostats - 0x17
Device_Ids.push_back(0x18); // Thermostat remote - 0x18
Device_Ids.push_back(0x19); // Thermostat remote - 0x19
Device_Ids.push_back(0x1A); // Thermostat remote - 0x1A
Device_Ids.push_back(0x1B); // Thermostat remote - 0x1B
Device_Ids.push_back(0x11); // Switches - 0x11
// send the read command with Version command
for (const uint8_t device_id : Device_Ids) {
EMSESP::send_read_request(EMSdevice::EMS_TYPE_VERSION, device_id);
}
}
});
// send the read command with Version command
for (const uint8_t device_id : Device_Ids) {
EMSESP::send_read_request(EMSdevice::EMS_TYPE_VERSION, device_id);
}
}
});
commands->add_command(ShellContext::MAIN, CommandFlags::USER, flash_string_vector{F_(set)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::webSettingsService.read([&](WebSettings & settings) {
shell.printfln(F_(tx_mode_fmt), settings.tx_mode);
shell.printfln(F_(bus_id_fmt), settings.ems_bus_id);
char buffer[4];
shell.printfln(F_(master_thermostat_fmt), settings.master_thermostat == 0 ? uuid::read_flash_string(F_(auto)).c_str() : Helpers::hextoa(buffer, settings.master_thermostat));
shell.printfln(F_(board_profile_fmt), settings.board_profile.c_str());
});
});
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(set)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::webSettingsService.read([&](WebSettings & settings) {
shell.printfln(F_(tx_mode_fmt), settings.tx_mode);
shell.printfln(F_(bus_id_fmt), settings.ems_bus_id);
char buffer[4];
shell.printfln(F_(master_thermostat_fmt),
settings.master_thermostat == 0 ? uuid::read_flash_string(F_(auto)).c_str()
: Helpers::hextoa(buffer, settings.master_thermostat));
shell.printfln(F_(board_profile_fmt), settings.board_profile.c_str());
});
});
commands->add_command(ShellContext::MAIN,
CommandFlags::ADMIN,
@@ -229,7 +256,7 @@ void EMSESPShell::add_console_commands() {
uint16_t offset = Helpers::hextoint(arguments.back().c_str());
EMSESP::send_read_request(type_id, device_id, offset, EMS_MAX_TELEGRAM_LENGTH);
} else {
// send with length to send immediatly and trigger publish read_id
// send with length to send immediately and trigger publish read_id
EMSESP::send_read_request(type_id, device_id, 0, EMS_MAX_TELEGRAM_LENGTH);
}
});
@@ -245,18 +272,23 @@ void EMSESPShell::add_console_commands() {
settings.master_thermostat = value;
EMSESP::actual_master_thermostat(value); // set the internal value too
char buffer[5];
shell.printfln(F_(master_thermostat_fmt), !value ? uuid::read_flash_string(F_(auto)).c_str() : Helpers::hextoa(buffer, value));
shell.printfln(F_(master_thermostat_fmt),
!value ? uuid::read_flash_string(F_(auto)).c_str() : Helpers::hextoa(buffer, value));
return StateUpdateResult::CHANGED;
},
"local");
});
#ifndef EMSESP_STANDALONE
commands->add_command(ShellContext::MAIN, CommandFlags::USER, flash_string_vector{F_(set), F_(timeout)}, flash_string_vector{F_(n_mandatory)}, [](Shell & shell, const std::vector<std::string> & arguments) {
uint16_t value = Helpers::atoint(arguments.front().c_str());
telnet_.initial_idle_timeout(value * 60);
shell.printfln(F("Telnet timeout set to %d minutes"), value);
});
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(set), F_(timeout)},
flash_string_vector{F_(n_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
uint16_t value = Helpers::atoint(arguments.front().c_str());
telnet_.initial_idle_timeout(value * 60);
shell.printfln(F("Telnet timeout set to %d minutes"), value);
});
#endif
commands->add_command(ShellContext::MAIN,
@@ -341,19 +373,25 @@ void EMSESPShell::add_console_commands() {
return;
}
// validate the command
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) {
shell.print(F("Available commands are: "));
Command::show(shell, device_type);
// // 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;
}
const char * cmd = arguments[1].c_str();
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN);
JsonObject json = doc.to<JsonObject>();
bool ok = false;
if (arguments.size() == 2) {
// no value specified, just the cmd
ok = Command::call(device_type, cmd, nullptr, -1, json);
@@ -371,14 +409,13 @@ void EMSESPShell::add_console_commands() {
}
if (ok && json.size()) {
doc.shrinkToFit();
serializeJsonPretty(doc, shell);
shell.println();
return;
} else if (!ok) {
shell.println(F("Unknown command, value, or id."));
shell.print(F("Available commands are: "));
Command::show(shell, device_type);
Command::show(shell, device_type, false); // non-verbose mode
}
},
[&](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) -> std::vector<std::string> {
@@ -444,27 +481,37 @@ void Console::enter_custom_context(Shell & shell, unsigned int context) {
// each custom context has the common commands like log, help, exit, su etc
void Console::load_standard_commands(unsigned int context) {
#if defined(EMSESP_DEBUG)
EMSESPShell::commands->add_command(context, CommandFlags::USER, flash_string_vector{F("test")}, flash_string_vector{F_(name_optional)}, [](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.size() == 0) {
Test::run_test(shell, "default");
} else {
Test::run_test(shell, arguments.front());
}
});
EMSESPShell::commands->add_command(context,
CommandFlags::USER,
flash_string_vector{F("test")},
flash_string_vector{F_(name_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.size() == 0) {
Test::run_test(shell, "default");
} else {
Test::run_test(shell, arguments.front());
}
});
#endif
#if defined(EMSESP_STANDALONE)
EMSESPShell::commands->add_command(context, CommandFlags::USER, flash_string_vector{F("t")}, [](Shell & shell, const std::vector<std::string> & arguments) { Test::run_test(shell, "default"); });
EMSESPShell::commands->add_command(context, CommandFlags::USER, flash_string_vector{F("t")}, [](Shell & shell, const std::vector<std::string> & arguments) {
Test::run_test(shell, "default");
});
#endif
#if defined(EMSESP_DEBUG)
EMSESPShell::commands->add_command(context, CommandFlags::USER, flash_string_vector{F_(debug)}, flash_string_vector{F_(name_optional)}, [](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.size() == 0) {
Test::debug(shell, "default");
} else {
Test::debug(shell, arguments.front());
}
});
EMSESPShell::commands->add_command(context,
CommandFlags::USER,
flash_string_vector{F_(debug)},
flash_string_vector{F_(name_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.size() == 0) {
Test::debug(shell, "default");
} else {
Test::debug(shell, arguments.front());
}
});
#endif
EMSESPShell::commands->add_command(
@@ -493,44 +540,55 @@ void Console::load_standard_commands(unsigned int context) {
}
shell.printfln(F_(log_level_fmt), uuid::log::format_level_lowercase(shell.log_level()));
},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> std::vector<std::string> { return uuid::log::levels_lowercase(); });
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> std::vector<std::string> {
return uuid::log::levels_lowercase();
});
EMSESPShell::commands->add_command(context, CommandFlags::USER, flash_string_vector{F_(help)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.print_all_available_commands();
});
EMSESPShell::commands->add_command(context,
CommandFlags::USER,
flash_string_vector{F_(help)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.print_all_available_commands();
});
EMSESPShell::commands->add_command(context, CommandFlags::USER, flash_string_vector{F_(exit)}, [=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.stop();
// shell.exit_context();
});
EMSESPShell::commands->add_command(context,
CommandFlags::USER,
flash_string_vector{F_(exit)},
[=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.stop();
// shell.exit_context();
});
EMSESPShell::commands->add_command(context, CommandFlags::USER, flash_string_vector{F_(su)}, [=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
auto become_admin = [](Shell & shell) {
shell.logger().log(LogLevel::NOTICE, LogFacility::AUTH, F("su session opened on console"));
shell.add_flags(CommandFlags::ADMIN);
};
EMSESPShell::commands->add_command(context,
CommandFlags::USER,
flash_string_vector{F_(su)},
[=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
auto become_admin = [](Shell & shell) {
shell.logger().log(LogLevel::NOTICE, LogFacility::AUTH, F("su session opened on console"));
shell.add_flags(CommandFlags::ADMIN);
};
if (shell.has_flags(CommandFlags::LOCAL)) {
become_admin(shell);
} else {
shell.enter_password(F_(password_prompt), [=](Shell & shell, bool completed, const std::string & password) {
if (completed) {
uint64_t now = uuid::get_uptime_ms();
if (shell.has_flags(CommandFlags::LOCAL)) {
become_admin(shell);
} else {
shell.enter_password(F_(password_prompt), [=](Shell & shell, bool completed, const std::string & password) {
if (completed) {
uint64_t now = uuid::get_uptime_ms();
EMSESP::esp8266React.getSecuritySettingsService()->read([&](SecuritySettings & securitySettings) {
if (securitySettings.jwtSecret.equals(password.c_str())) {
become_admin(shell);
} else {
shell.delay_until(now + INVALID_PASSWORD_DELAY_MS, [](Shell & shell) {
shell.logger().log(LogLevel::NOTICE, LogFacility::AUTH, F("Invalid su password on console"));
shell.println(F("su: incorrect password"));
});
}
});
}
});
}
});
EMSESP::esp8266React.getSecuritySettingsService()->read([&](SecuritySettings & securitySettings) {
if (securitySettings.jwtSecret.equals(password.c_str())) {
become_admin(shell);
} else {
shell.delay_until(now + INVALID_PASSWORD_DELAY_MS, [](Shell & shell) {
shell.logger().log(LogLevel::NOTICE, LogFacility::AUTH, F("Invalid su password on console"));
shell.println(F("su: incorrect password"));
});
}
});
}
});
}
});
}
// console commands to add
@@ -538,53 +596,67 @@ void Console::load_system_commands(unsigned int context) {
EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN,
flash_string_vector{F_(restart)},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::system_.restart(); });
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::system_.restart();
});
EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN,
flash_string_vector{F_(wifi), F_(reconnect)},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::system_.wifi_reconnect(); });
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::system_.wifi_reconnect();
});
EMSESPShell::commands->add_command(ShellContext::MAIN, CommandFlags::ADMIN, flash_string_vector{F_(format)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.enter_password(F_(password_prompt), [=](Shell & shell, bool completed, const std::string & password) {
if (completed) {
EMSESP::esp8266React.getSecuritySettingsService()->read([&](SecuritySettings & securitySettings) {
if (securitySettings.jwtSecret.equals(password.c_str())) {
EMSESP::system_.format(shell);
} else {
shell.println(F("incorrect password"));
}
});
}
});
});
EMSESPShell::commands->add_command(ShellContext::MAIN,
CommandFlags::ADMIN,
flash_string_vector{F_(format)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.enter_password(F_(password_prompt), [=](Shell & shell, bool completed, const std::string & password) {
if (completed) {
EMSESP::esp8266React.getSecuritySettingsService()->read([&](SecuritySettings & securitySettings) {
if (securitySettings.jwtSecret.equals(password.c_str())) {
EMSESP::system_.format(shell);
} else {
shell.println(F("incorrect password"));
}
});
}
});
});
EMSESPShell::commands->add_command(context, CommandFlags::ADMIN, flash_string_vector{F_(passwd)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.enter_password(F_(new_password_prompt1), [](Shell & shell, bool completed, const std::string & password1) {
if (completed) {
shell.enter_password(F_(new_password_prompt2), [password1](Shell & shell, bool completed, const std::string & password2) {
if (completed) {
if (password1 == password2) {
EMSESP::esp8266React.getSecuritySettingsService()->update(
[&](SecuritySettings & securitySettings) {
securitySettings.jwtSecret = password2.c_str();
return StateUpdateResult::CHANGED;
},
"local");
shell.println(F("su password updated"));
} else {
shell.println(F("Passwords do not match"));
}
}
});
}
});
});
EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN,
flash_string_vector{F_(passwd)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.enter_password(F_(new_password_prompt1), [](Shell & shell, bool completed, const std::string & password1) {
if (completed) {
shell.enter_password(F_(new_password_prompt2),
[password1](Shell & shell, bool completed, const std::string & password2) {
if (completed) {
if (password1 == password2) {
EMSESP::esp8266React.getSecuritySettingsService()->update(
[&](SecuritySettings & securitySettings) {
securitySettings.jwtSecret = password2.c_str();
return StateUpdateResult::CHANGED;
},
"local");
shell.println(F("su password updated"));
} else {
shell.println(F("Passwords do not match"));
}
}
});
}
});
});
EMSESPShell::commands->add_command(context, CommandFlags::USER, flash_string_vector{F_(show), F_(system)}, [=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::system_.show_system(shell);
shell.println();
});
EMSESPShell::commands->add_command(context,
CommandFlags::USER,
flash_string_vector{F_(show), F_(system)},
[=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::system_.show_system(shell);
shell.println();
});
EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN,
@@ -614,55 +686,63 @@ void Console::load_system_commands(unsigned int context) {
shell.println("Use `wifi reconnect` to save and apply the new settings");
});
EMSESPShell::commands->add_command(context, CommandFlags::ADMIN, flash_string_vector{F_(set), F_(wifi), F_(password)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.enter_password(F_(new_password_prompt1), [](Shell & shell, bool completed, const std::string & password1) {
if (completed) {
shell.enter_password(F_(new_password_prompt2), [password1](Shell & shell, bool completed, const std::string & password2) {
if (completed) {
if (password1 == password2) {
EMSESP::esp8266React.getNetworkSettingsService()->updateWithoutPropagation([&](NetworkSettings & networkSettings) {
networkSettings.password = password2.c_str();
return StateUpdateResult::CHANGED;
});
shell.println("Use `wifi reconnect` to save and apply the new settings");
} else {
shell.println(F("Passwords do not match"));
}
}
});
}
});
});
EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(board_profile)},
flash_string_vector{F_(name_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
std::vector<uint8_t> data; // led, dallas, rx, tx, button
std::string board_profile = Helpers::toUpper(arguments.front());
if (!EMSESP::system_.load_board_profile(data, board_profile)) {
shell.println(F("Invalid board profile (S32, E32, MH-ET, NODEMCU, OLIMEX, TLK110, LAN8720, CUSTOM)"));
return;
}
EMSESP::webSettingsService.update(
[&](WebSettings & settings) {
settings.board_profile = board_profile.c_str();
settings.led_gpio = data[0];
settings.dallas_gpio = data[1];
settings.rx_gpio = data[2];
settings.tx_gpio = data[3];
settings.pbutton_gpio = data[4];
return StateUpdateResult::CHANGED;
},
"local");
shell.printfln("Loaded board profile %s (%d,%d,%d,%d,%d)", board_profile.c_str(), data[0], data[1], data[2], data[3], data[4]);
EMSESP::system_.network_init(true);
flash_string_vector{F_(set), F_(wifi), F_(password)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.enter_password(F_(new_password_prompt1), [](Shell & shell, bool completed, const std::string & password1) {
if (completed) {
shell.enter_password(F_(new_password_prompt2),
[password1](Shell & shell, bool completed, const std::string & password2) {
if (completed) {
if (password1 == password2) {
EMSESP::esp8266React.getNetworkSettingsService()->updateWithoutPropagation(
[&](NetworkSettings & networkSettings) {
networkSettings.password = password2.c_str();
return StateUpdateResult::CHANGED;
});
shell.println("Use `wifi reconnect` to save and apply the new settings");
} else {
shell.println(F("Passwords do not match"));
}
}
});
}
});
});
EMSESPShell::commands->add_command(context, CommandFlags::ADMIN, flash_string_vector{F_(show), F_(users)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::system_.show_users(shell);
});
EMSESPShell::commands
->add_command(context,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(board_profile)},
flash_string_vector{F_(name_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
std::vector<uint8_t> data; // led, dallas, rx, tx, button
std::string board_profile = Helpers::toUpper(arguments.front());
if (!EMSESP::system_.load_board_profile(data, board_profile)) {
shell.println(F("Invalid board profile (S32, E32, MH-ET, NODEMCU, OLIMEX, TLK110, LAN8720, CUSTOM)"));
return;
}
EMSESP::webSettingsService.update(
[&](WebSettings & settings) {
settings.board_profile = board_profile.c_str();
settings.led_gpio = data[0];
settings.dallas_gpio = data[1];
settings.rx_gpio = data[2];
settings.tx_gpio = data[3];
settings.pbutton_gpio = data[4];
return StateUpdateResult::CHANGED;
},
"local");
shell.printfln("Loaded board profile %s (%d,%d,%d,%d,%d)", board_profile.c_str(), data[0], data[1], data[2], data[3], data[4]);
EMSESP::system_.network_init(true);
});
EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN,
flash_string_vector{F_(show), F_(users)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::system_.show_users(shell);
});
}
/*

View File

@@ -44,8 +44,6 @@ using uuid::log::Level;
#define LOG_WARNING(...) logger_.warning(__VA_ARGS__)
#define LOG_ERROR(...) logger_.err(__VA_ARGS__)
#define MQTT_TOPIC(list_name) (__pstr__##list_name[0])
// clang-format off
// strings stored 32 bit aligned on ESP8266/ESP32
#define MAKE_PSTR(string_name, string_literal) static const char __pstr__##string_name[] __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = string_literal;

View File

@@ -40,8 +40,17 @@ void DallasSensor::start() {
#ifndef EMSESP_STANDALONE
bus_.begin(dallas_gpio_);
#endif
// API call
Command::add_with_json(EMSdevice::DeviceType::DALLASSENSOR, F_(info), [&](const char * value, const int8_t id, JsonObject & json) { return command_info(value, id, json); });
// API calls
Command::add_with_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(
EMSdevice::DeviceType::DALLASSENSOR,
F_(commands),
[&](const char * value, const int8_t id, JsonObject & json) { return command_commands(value, id, json); },
F_(commands_cmd));
}
}
@@ -125,6 +134,7 @@ void DallasSensor::loop() {
int16_t t;
t = get_temperature_c(addr);
if ((t >= -550) && (t <= 1250)) {
sensorreads_++;
// check if we have this sensor already
bool found = false;
for (auto & sensor : sensors_) {
@@ -270,11 +280,13 @@ const std::vector<DallasSensor::Sensor> DallasSensor::sensors() const {
// skip crc from id.
DallasSensor::Sensor::Sensor(const uint8_t addr[])
: id_(((uint64_t)addr[0] << 48) | ((uint64_t)addr[1] << 40) | ((uint64_t)addr[2] << 32) | ((uint64_t)addr[3] << 24) | ((uint64_t)addr[4] << 16) | ((uint64_t)addr[5] << 8) | ((uint64_t)addr[6])) {
: id_(((uint64_t)addr[0] << 48) | ((uint64_t)addr[1] << 40) | ((uint64_t)addr[2] << 32) | ((uint64_t)addr[3] << 24) | ((uint64_t)addr[4] << 16)
| ((uint64_t)addr[5] << 8) | ((uint64_t)addr[6])) {
}
uint64_t DallasSensor::get_id(const uint8_t addr[]) {
return (((uint64_t)addr[0] << 48) | ((uint64_t)addr[1] << 40) | ((uint64_t)addr[2] << 32) | ((uint64_t)addr[3] << 24) | ((uint64_t)addr[4] << 16) | ((uint64_t)addr[5] << 8) | ((uint64_t)addr[6]));
return (((uint64_t)addr[0] << 48) | ((uint64_t)addr[1] << 40) | ((uint64_t)addr[2] << 32) | ((uint64_t)addr[3] << 24) | ((uint64_t)addr[4] << 16)
| ((uint64_t)addr[5] << 8) | ((uint64_t)addr[6]));
}
uint64_t DallasSensor::Sensor::id() const {
@@ -283,7 +295,13 @@ uint64_t DallasSensor::Sensor::id() const {
std::string DallasSensor::Sensor::to_string() const {
std::string str(20, '\0');
snprintf_P(&str[0], str.capacity() + 1, PSTR("%02X-%04X-%04X-%04X"), (unsigned int)(id_ >> 48) & 0xFF, (unsigned int)(id_ >> 32) & 0xFFFF, (unsigned int)(id_ >> 16) & 0xFFFF, (unsigned int)(id_)&0xFFFF);
snprintf_P(&str[0],
str.capacity() + 1,
PSTR("%02X-%04X-%04X-%04X"),
(unsigned int)(id_ >> 48) & 0xFF,
(unsigned int)(id_ >> 32) & 0xFFFF,
(unsigned int)(id_ >> 16) & 0xFFFF,
(unsigned int)(id_)&0xFFFF);
return str;
}
@@ -296,6 +314,11 @@ bool DallasSensor::updated_values() {
return false;
}
// list commands
bool DallasSensor::command_commands(const char * value, const int8_t id, JsonObject & json) {
return Command::list(EMSdevice::DeviceType::DALLASSENSOR, json);
}
// creates JSON doc from values
// returns false if empty
// e.g. dallassensor_data = {"sensor1":{"id":"28-EA41-9497-0E03-5F","temp":23.30},"sensor2":{"id":"28-233D-9497-0C03-8B","temp":24.0}}
@@ -308,22 +331,22 @@ bool DallasSensor::command_info(const char * value, const int8_t id, JsonObject
for (const auto & sensor : sensors_) {
char sensorID[10]; // sensor{1-n}
snprintf_P(sensorID, 10, PSTR("sensor%d"), i++);
if (id == 0) {
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)) {
json[sensorID] = (float)(sensor.temperature_c) / 10;
}
} else {
if (id == -1) { // show number and id
JsonObject dataSensor = json.createNestedObject(sensorID);
dataSensor["id"] = sensor.to_string();
if (Helpers::hasValue(sensor.temperature_c)) {
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)) {
json[sensorID] = (float)(sensor.temperature_c) / 10;
}
}
}
return true;
return (json.size() > 0);
}
// send all dallas sensor values as a JSON package to MQTT
@@ -414,7 +437,6 @@ void DallasSensor::publish_values(const bool force) {
sensor_no++; // increment sensor count
}
// doc.shrinkToFit();
Mqtt::publish(F("dallassensor_data"), doc.as<JsonObject>());
}

View File

@@ -64,6 +64,10 @@ class DallasSensor {
const std::vector<Sensor> sensors() const;
uint32_t reads() {
return sensorreads_;
}
uint32_t fails() {
return sensorfails_;
}
@@ -113,6 +117,7 @@ class DallasSensor {
uint64_t get_id(const uint8_t addr[]);
bool command_info(const char * value, const int8_t id, JsonObject & json);
bool command_commands(const char * value, const int8_t id, JsonObject & json);
uint32_t last_activity_ = uuid::get_uptime();
uint32_t last_publish_ = uuid::get_uptime();
@@ -127,6 +132,7 @@ class DallasSensor {
bool parasite_ = false;
bool changed_ = false;
uint32_t sensorfails_ = 0;
uint32_t sensorreads_ = 0;
int8_t scanretry_ = 0;
};

View File

@@ -76,8 +76,8 @@
#define EMSESP_DEFAULT_DALLAS_PARASITE false
#endif
#ifndef EMSESP_DEFAULT_API_ENABLED
#define EMSESP_DEFAULT_API_ENABLED false // turn off, because its insecure
#ifndef EMSESP_DEFAULT_NOTOKEN_API
#define EMSESP_DEFAULT_NOTOKEN_API false
#endif
#ifndef EMSESP_DEFAULT_BOOL_FORMAT
@@ -152,4 +152,8 @@
#define EMSESP_DEFAULT_SUBSCRIBE_FORMAT 0
#endif
#ifndef EMSESP_DEFAULT_SOLAR_MAXFLOW
#define EMSESP_DEFAULT_SOLAR_MAXFLOW 30
#endif
#endif

View File

@@ -85,7 +85,8 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
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::NONE, MAKE_CF_CB(set_tapwarmwater_activated));
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
@@ -95,8 +96,8 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_device_value(TAG_BOILER_DATA, &id_, DeviceValueType::UINT, nullptr, FL_(ID), DeviceValueUOM::NONE);
id_ = product_id;
register_device_value(TAG_BOILER_DATA, &heatingActive_, DeviceValueType::BOOL, nullptr, FL_(heatingActive), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA, &tapwaterActive_, DeviceValueType::BOOL, nullptr, FL_(tapwaterActive), DeviceValueUOM::NONE);
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);
register_device_value(TAG_BOILER_DATA, &selFlowTemp_, DeviceValueType::UINT, nullptr, FL_(selFlowTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_flow_temp));
register_device_value(TAG_BOILER_DATA, &selBurnPow_, DeviceValueType::UINT, nullptr, FL_(selBurnPow), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_burn_power));
register_device_value(TAG_BOILER_DATA, &heatingPumpMod_, DeviceValueType::UINT, nullptr, FL_(heatingPumpMod), DeviceValueUOM::PERCENT);
@@ -108,12 +109,13 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_device_value(TAG_BOILER_DATA, &sysPress_, DeviceValueType::UINT, FL_(div10), FL_(sysPress), DeviceValueUOM::BAR);
register_device_value(TAG_BOILER_DATA, &boilTemp_, DeviceValueType::USHORT, FL_(div10), FL_(boilTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA, &exhaustTemp_, DeviceValueType::USHORT, FL_(div10), FL_(exhaustTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA, &burnGas_, DeviceValueType::BOOL, nullptr, FL_(burnGas), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA, &burnGas_, DeviceValueType::BOOL, nullptr, FL_(burnGas), DeviceValueUOM::BOOLEAN);
register_device_value(TAG_BOILER_DATA, &flameCurr_, DeviceValueType::USHORT, FL_(div10), FL_(flameCurr), DeviceValueUOM::UA);
register_device_value(TAG_BOILER_DATA, &heatingPump_, DeviceValueType::BOOL, nullptr, FL_(heatingPump), DeviceValueUOM::PUMP);
register_device_value(TAG_BOILER_DATA, &fanWork_, DeviceValueType::BOOL, nullptr, FL_(fanWork), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA, &ignWork_, DeviceValueType::BOOL, nullptr, FL_(ignWork), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA, &heatingActivated_, DeviceValueType::BOOL, nullptr, FL_(heatingActivated), DeviceValueUOM::NONE, MAKE_CF_CB(set_heating_activated));
register_device_value(TAG_BOILER_DATA, &heatingPump_, DeviceValueType::BOOL, nullptr, FL_(heatingPump), DeviceValueUOM::BOOLEAN);
register_device_value(TAG_BOILER_DATA, &fanWork_, DeviceValueType::BOOL, nullptr, FL_(fanWork), DeviceValueUOM::BOOLEAN);
register_device_value(TAG_BOILER_DATA, &ignWork_, DeviceValueType::BOOL, nullptr, FL_(ignWork), DeviceValueUOM::BOOLEAN);
register_device_value(
TAG_BOILER_DATA, &heatingActivated_, DeviceValueType::BOOL, nullptr, FL_(heatingActivated), DeviceValueUOM::BOOLEAN, MAKE_CF_CB(set_heating_activated));
register_device_value(TAG_BOILER_DATA, &heatingTemp_, DeviceValueType::UINT, nullptr, FL_(heatingTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_heating_temp));
register_device_value(TAG_BOILER_DATA, &pumpModMax_, DeviceValueType::UINT, nullptr, FL_(pumpModMax), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_max_pump));
register_device_value(TAG_BOILER_DATA, &pumpModMin_, DeviceValueType::UINT, nullptr, FL_(pumpModMin), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_min_pump));
@@ -126,7 +128,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_device_value(TAG_BOILER_DATA, &setFlowTemp_, DeviceValueType::UINT, nullptr, FL_(setFlowTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA, &setBurnPow_, DeviceValueType::UINT, nullptr, FL_(setBurnPow), DeviceValueUOM::PERCENT);
register_device_value(TAG_BOILER_DATA, &curBurnPow_, DeviceValueType::UINT, nullptr, FL_(curBurnPow), DeviceValueUOM::PERCENT);
register_device_value(TAG_BOILER_DATA, &burnStarts_, DeviceValueType::ULONG, nullptr, FL_(burnStarts), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA, &burnStarts_, DeviceValueType::ULONG, nullptr, FL_(burnStarts), DeviceValueUOM::NUM);
register_device_value(TAG_BOILER_DATA, &burnWorkMin_, DeviceValueType::TIME, nullptr, FL_(burnWorkMin), DeviceValueUOM::MINUTES);
register_device_value(TAG_BOILER_DATA, &heatWorkMin_, DeviceValueType::TIME, nullptr, FL_(heatWorkMin), DeviceValueUOM::MINUTES);
register_device_value(TAG_BOILER_DATA, &UBAuptime_, DeviceValueType::TIME, nullptr, FL_(UBAuptime), DeviceValueUOM::MINUTES);
@@ -135,7 +137,13 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
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, MAKE_CF_CB(set_maintenance));
register_device_value(TAG_BOILER_DATA,
&maintenanceType_,
DeviceValueType::ENUM,
FL_(enum_off_time_date),
FL_(maintenanceType),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_maintenance));
register_device_value(TAG_BOILER_DATA, &maintenanceTime_, DeviceValueType::USHORT, nullptr, FL_(maintenanceTime), DeviceValueUOM::HOURS);
// heatpump info
if (model() == EMS_DEVICE_FLAG_HEATPUMP) {
@@ -143,8 +151,8 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_device_value(TAG_BOILER_DATA, &upTimeCompHeating_, DeviceValueType::TIME, FL_(div60), FL_(upTimeCompHeating), DeviceValueUOM::MINUTES);
register_device_value(TAG_BOILER_DATA, &upTimeCompCooling_, DeviceValueType::TIME, FL_(div60), FL_(upTimeCompCooling), DeviceValueUOM::MINUTES);
register_device_value(TAG_BOILER_DATA, &upTimeCompWw_, DeviceValueType::TIME, FL_(div60), FL_(upTimeCompWw), DeviceValueUOM::MINUTES);
register_device_value(TAG_BOILER_DATA, &heatingStarts_, DeviceValueType::ULONG, nullptr, FL_(heatingStarts), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA, &coolingStarts_, DeviceValueType::ULONG, nullptr, FL_(coolingStarts), DeviceValueUOM::NONE);
register_device_value(TAG_BOILER_DATA, &heatingStarts_, DeviceValueType::ULONG, nullptr, FL_(heatingStarts), DeviceValueUOM::NUM);
register_device_value(TAG_BOILER_DATA, &coolingStarts_, DeviceValueType::ULONG, nullptr, FL_(coolingStarts), DeviceValueUOM::NUM);
register_device_value(TAG_BOILER_DATA, &nrgConsTotal_, DeviceValueType::ULONG, nullptr, FL_(nrgConsTotal), DeviceValueUOM::KWH);
register_device_value(TAG_BOILER_DATA, &nrgConsCompTotal_, DeviceValueType::ULONG, nullptr, FL_(nrgConsCompTotal), DeviceValueUOM::KWH);
register_device_value(TAG_BOILER_DATA, &nrgConsCompHeating_, DeviceValueType::ULONG, nullptr, FL_(nrgConsCompHeating), DeviceValueUOM::KWH);
@@ -172,54 +180,79 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
}
// warm water - boiler_data_ww topic
register_device_value(TAG_DEVICE_DATA_WW, &wWSelTemp_, DeviceValueType::UINT, nullptr, FL_(wWSelTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_DEVICE_DATA_WW, &wWSetTemp_, DeviceValueType::UINT, nullptr, FL_(wWSetTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_warmwater_temp));
register_device_value(TAG_DEVICE_DATA_WW, &wWType_, DeviceValueType::ENUM, FL_(enum_flow), FL_(wWType), DeviceValueUOM::NONE);
register_device_value(TAG_DEVICE_DATA_WW, &wWComfort_, DeviceValueType::ENUM, FL_(enum_comfort), FL_(wWComfort), DeviceValueUOM::NONE, MAKE_CF_CB(set_warmwater_mode));
register_device_value(TAG_DEVICE_DATA_WW, &wWFlowTempOffset_, DeviceValueType::UINT, nullptr, FL_(wWFlowTempOffset), DeviceValueUOM::NONE, MAKE_CF_CB(set_wWFlowTempOffset));
register_device_value(TAG_DEVICE_DATA_WW, &wWMaxPower_, DeviceValueType::UINT, nullptr, FL_(wWMaxPower), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_warmwater_maxpower));
register_device_value(TAG_DEVICE_DATA_WW, &wWCircPump_, DeviceValueType::BOOL, nullptr, FL_(wWCircPump), DeviceValueUOM::NONE, MAKE_CF_CB(set_warmwater_circulation_pump));
register_device_value(TAG_DEVICE_DATA_WW, &wWChargeType_, DeviceValueType::BOOL, FL_(enum_charge), FL_(wWChargeType), DeviceValueUOM::NONE);
register_device_value(TAG_DEVICE_DATA_WW, &wWDisinfectionTemp_, DeviceValueType::UINT, nullptr, FL_(wWDisinfectionTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_disinfect_temp));
register_device_value(TAG_DEVICE_DATA_WW, &wWCircMode_, DeviceValueType::ENUM, FL_(enum_freq), FL_(wWCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_warmwater_circulation_mode));
register_device_value(TAG_DEVICE_DATA_WW, &wWCirc_, DeviceValueType::BOOL, nullptr, FL_(wWCirc), DeviceValueUOM::NONE, MAKE_CF_CB(set_warmwater_circulation));
register_device_value(TAG_DEVICE_DATA_WW, &wWCurTemp_, DeviceValueType::USHORT, FL_(div10), FL_(wWCurTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_DEVICE_DATA_WW, &wWCurTemp2_, DeviceValueType::USHORT, FL_(div10), FL_(wWCurTemp2), DeviceValueUOM::DEGREES);
register_device_value(TAG_DEVICE_DATA_WW, &wWCurFlow_, DeviceValueType::UINT, FL_(div10), FL_(wWCurFlow), DeviceValueUOM::LMIN);
register_device_value(TAG_DEVICE_DATA_WW, &wWStorageTemp1_, DeviceValueType::USHORT, FL_(div10), FL_(wWStorageTemp1), DeviceValueUOM::DEGREES);
register_device_value(TAG_DEVICE_DATA_WW, &wWStorageTemp2_, DeviceValueType::USHORT, FL_(div10), FL_(wWStorageTemp2), DeviceValueUOM::DEGREES);
register_device_value(TAG_DEVICE_DATA_WW, &wWActivated_, DeviceValueType::BOOL, nullptr, FL_(wWActivated), DeviceValueUOM::NONE, MAKE_CF_CB(set_warmwater_activated));
register_device_value(TAG_DEVICE_DATA_WW, &wWOneTime_, DeviceValueType::BOOL, nullptr, FL_(wWOneTime), DeviceValueUOM::NONE, MAKE_CF_CB(set_warmwater_onetime));
register_device_value(TAG_DEVICE_DATA_WW, &wWDisinfecting_, DeviceValueType::BOOL, nullptr, FL_(wWDisinfecting), DeviceValueUOM::NONE);
register_device_value(TAG_DEVICE_DATA_WW, &wWCharging_, DeviceValueType::BOOL, nullptr, FL_(wWCharging), DeviceValueUOM::NONE);
register_device_value(TAG_DEVICE_DATA_WW, &wWRecharging_, DeviceValueType::BOOL, nullptr, FL_(wWRecharging), DeviceValueUOM::NONE);
register_device_value(TAG_DEVICE_DATA_WW, &wWTempOK_, DeviceValueType::BOOL, nullptr, FL_(wWTempOK), DeviceValueUOM::NONE);
register_device_value(TAG_DEVICE_DATA_WW, &wWActive_, DeviceValueType::BOOL, nullptr, FL_(wWActive), DeviceValueUOM::NONE);
register_device_value(TAG_DEVICE_DATA_WW, &wWHeat_, DeviceValueType::BOOL, nullptr, FL_(wWHeat), DeviceValueUOM::NONE);
register_device_value(TAG_DEVICE_DATA_WW, &wWSetPumpPower_, DeviceValueType::UINT, nullptr, FL_(wWSetPumpPower), DeviceValueUOM::PERCENT);
register_device_value(TAG_DEVICE_DATA_WW, &mixerTemp_, DeviceValueType::USHORT, FL_(div10), FL_(mixerTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_DEVICE_DATA_WW, &tankMiddleTemp_, DeviceValueType::USHORT, FL_(div10), FL_(tankMiddleTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_DEVICE_DATA_WW, &wWStarts_, DeviceValueType::ULONG, nullptr, FL_(wWStarts), DeviceValueUOM::NONE);
register_device_value(TAG_DEVICE_DATA_WW, &wWStarts2_, DeviceValueType::ULONG, nullptr, FL_(wWStarts2), DeviceValueUOM::NONE);
register_device_value(TAG_DEVICE_DATA_WW, &wWWorkM_, DeviceValueType::TIME, nullptr, FL_(wWWorkM), DeviceValueUOM::MINUTES);
register_device_value(TAG_BOILER_DATA_WW, &wWSelTemp_, DeviceValueType::UINT, nullptr, FL_(wWSelTemp), DeviceValueUOM::DEGREES);
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));
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,
&wWDisinfectionTemp_,
DeviceValueType::UINT,
nullptr,
FL_(wWDisinfectionTemp),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_disinfect_temp));
register_device_value(TAG_BOILER_DATA_WW,
&wWCircMode_,
DeviceValueType::ENUM,
FL_(enum_freq),
FL_(wWCircMode),
DeviceValueUOM::NONE,
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);
register_device_value(TAG_BOILER_DATA_WW, &wWCurTemp2_, DeviceValueType::USHORT, FL_(div10), FL_(wWCurTemp2), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA_WW, &wWCurFlow_, DeviceValueType::UINT, FL_(div10), FL_(wWCurFlow), DeviceValueUOM::LMIN);
register_device_value(TAG_BOILER_DATA_WW, &wWStorageTemp1_, DeviceValueType::USHORT, FL_(div10), FL_(wWStorageTemp1), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA_WW, &wWStorageTemp2_, DeviceValueType::USHORT, FL_(div10), FL_(wWStorageTemp2), DeviceValueUOM::DEGREES);
register_device_value(
TAG_BOILER_DATA_WW, &wWActivated_, DeviceValueType::BOOL, nullptr, FL_(wWActivated), DeviceValueUOM::BOOLEAN, MAKE_CF_CB(set_warmwater_activated));
register_device_value(TAG_BOILER_DATA_WW, &wWOneTime_, DeviceValueType::BOOL, nullptr, FL_(wWOneTime), DeviceValueUOM::BOOLEAN, MAKE_CF_CB(set_warmwater_onetime));
register_device_value(TAG_BOILER_DATA_WW, &wWDisinfecting_, DeviceValueType::BOOL, nullptr, FL_(wWDisinfecting), DeviceValueUOM::BOOLEAN);
register_device_value(TAG_BOILER_DATA_WW, &wWCharging_, DeviceValueType::BOOL, nullptr, FL_(wWCharging), DeviceValueUOM::BOOLEAN);
register_device_value(TAG_BOILER_DATA_WW, &wWRecharging_, DeviceValueType::BOOL, nullptr, FL_(wWRecharging), DeviceValueUOM::BOOLEAN);
register_device_value(TAG_BOILER_DATA_WW, &wWTempOK_, DeviceValueType::BOOL, nullptr, FL_(wWTempOK), DeviceValueUOM::BOOLEAN);
register_device_value(TAG_BOILER_DATA_WW, &wWActive_, DeviceValueType::BOOL, nullptr, FL_(wWActive), DeviceValueUOM::BOOLEAN);
register_device_value(TAG_BOILER_DATA_WW, &wWHeat_, DeviceValueType::BOOL, nullptr, FL_(wWHeat), DeviceValueUOM::BOOLEAN);
register_device_value(TAG_BOILER_DATA_WW, &wWSetPumpPower_, DeviceValueType::UINT, nullptr, FL_(wWSetPumpPower), DeviceValueUOM::PERCENT);
register_device_value(TAG_BOILER_DATA_WW, &mixerTemp_, DeviceValueType::USHORT, FL_(div10), FL_(mixerTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA_WW, &tankMiddleTemp_, DeviceValueType::USHORT, FL_(div10), FL_(tankMiddleTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_BOILER_DATA_WW, &wWStarts_, DeviceValueType::ULONG, nullptr, FL_(wWStarts), DeviceValueUOM::NUM);
register_device_value(TAG_BOILER_DATA_WW, &wWStarts2_, DeviceValueType::ULONG, nullptr, FL_(wWStarts2), DeviceValueUOM::NUM);
register_device_value(TAG_BOILER_DATA_WW, &wWWorkM_, DeviceValueType::TIME, nullptr, FL_(wWWorkM), DeviceValueUOM::MINUTES);
// fetch some initial data
EMSESP::send_read_request(0x10, device_id); // read last errorcode on start (only published on errors)
EMSESP::send_read_request(0x11, device_id); // read last errorcode on start (only published on errors)
EMSESP::send_read_request(0x15, device_id); // read maintenace data on start (only published on change)
EMSESP::send_read_request(0x1C, device_id); // read maintenace status on start (only published on change)
EMSESP::send_read_request(0x10,
device_id); // read last errorcode on start (only published on errors)
EMSESP::send_read_request(0x11,
device_id); // read last errorcode on start (only published on errors)
EMSESP::send_read_request(0x15,
device_id); // read maintenace data on start (only published on change)
EMSESP::send_read_request(0x1C,
device_id); // read maintenace status on start (only published on change)
}
// publish HA config
bool Boiler::publish_ha_config() {
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
doc["uniq_id"] = F_(boiler);
doc["ic"] = F_(icondevice);
char stat_t[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s"), Mqtt::base().c_str(), Mqtt::tag_to_topic(device_type(), DeviceValueTAG::TAG_NONE).c_str());
doc["stat_t"] = stat_t;
doc["name"] = FJSON("ID");
char name_s[40];
snprintf_P(name_s, sizeof(name_s), PSTR("* %s Product ID"), device_type_name().c_str());
doc["name"] = name_s;
doc["val_tpl"] = FJSON("{{value_json.id}}");
JsonObject dev = doc.createNestedObject("dev");
dev["name"] = FJSON("EMS-ESP Boiler");
@@ -231,7 +264,8 @@ bool Boiler::publish_ha_config() {
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf_P(topic, sizeof(topic), PSTR("sensor/%s/boiler/config"), Mqtt::base().c_str());
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
Mqtt::publish_ha(topic,
doc.as<JsonObject>()); // publish the config payload with retain flag
return true;
}
@@ -277,7 +311,6 @@ void Boiler::check_active(const bool force) {
}
// 0x33
// Boiler(0x08) -> Me(0x0B), UBAParameterWW(0x33), data: 08 FF 30 FB FF 28 FF 07 46 00 00
void Boiler::process_UBAParameterWW(std::shared_ptr<const Telegram> telegram) {
// has_update(telegram->read_bitvalue(wwEquipt_,0,3)); // 8=boiler has ww
has_update(telegram->read_value(wWActivated_, 1)); // 0xFF means on
@@ -288,7 +321,7 @@ void Boiler::process_UBAParameterWW(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(wWCircPump_, 6)); // 0xFF means on
has_update(telegram->read_value(wWCircMode_, 7)); // 1=1x3min 6=6x3min 7=continuous
has_update(telegram->read_value(wWDisinfectionTemp_, 8));
has_update(telegram->read_value(wWChargeType_, 10)); // 0 = charge pump, 0xff = 3-way valve
has_update(telegram->read_bitvalue(wWChargeType_, 10, 0)); // 0 = charge pump, 0xff = 3-way valve
telegram->read_value(wWComfort_, 9);
if (wWComfort_ == 0x00) {
@@ -319,8 +352,9 @@ void Boiler::process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram) {
// warm water storage sensors (if present)
// wWStorageTemp2 is also used by some brands as the boiler temperature - see https://github.com/emsesp/EMS-ESP/issues/206
has_update(telegram->read_value(wWStorageTemp1_, 9)); // 0x8300 if not available
has_update(telegram->read_value(wWStorageTemp2_, 11)); // 0x8000 if not available - this is boiler temp
has_update(telegram->read_value(wWStorageTemp1_, 9)); // 0x8300 if not available
has_update(telegram->read_value(wWStorageTemp2_,
11)); // 0x8000 if not available - this is boiler temp
has_update(telegram->read_value(retTemp_, 13));
has_update(telegram->read_value(flameCurr_, 15));
@@ -456,7 +490,8 @@ void Boiler::process_UBAMonitorSlow(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(outdoorTemp_, 0));
has_update(telegram->read_value(boilTemp_, 2));
has_update(telegram->read_value(exhaustTemp_, 4));
has_update(telegram->read_value(switchTemp_, 25)); // only if there is a mixer module present
has_update(telegram->read_value(switchTemp_,
25)); // only if there is a mixer module present
has_update(telegram->read_value(heatingPumpMod_, 9));
has_update(telegram->read_value(burnStarts_, 10, 3)); // force to 3 bytes
has_update(telegram->read_value(burnWorkMin_, 13, 3)); // force to 3 bytes
@@ -515,7 +550,8 @@ void Boiler::process_UBAParametersPlus(std::shared_ptr<const Telegram> telegram)
void Boiler::process_UBAParameterWWPlus(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(wWActivated_, 5)); // 0x01 means on
has_update(telegram->read_value(wWCircPump_, 10)); // 0x01 means yes
has_update(telegram->read_value(wWCircMode_, 11)); // 1=1x3min... 6=6x3min, 7=continuous
has_update(telegram->read_value(wWCircMode_,
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
}
@@ -588,13 +624,12 @@ void Boiler::process_UBAEnergySupplied(std::shared_ptr<const Telegram> telegram)
}
// Heatpump power - type 0x48D
void Boiler::process_HpPower(std::shared_ptr<const Telegram> telegram){
void Boiler::process_HpPower(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(hpPower_, 11));
}
// Heatpump outdoor unit - type 0x48F
void Boiler::process_HpOutdoor(std::shared_ptr<const Telegram> telegram){
void Boiler::process_HpOutdoor(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(hpTc0_, 6));
has_update(telegram->read_value(hpTc1_, 4));
has_update(telegram->read_value(hpTc3_, 2));
@@ -625,7 +660,8 @@ void Boiler::process_UBAOutdoorTemp(std::shared_ptr<const Telegram> telegram) {
// UBASetPoint 0x1A
void Boiler::process_UBASetPoints(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(setFlowTemp_, 0)); // boiler set temp from thermostat
has_update(telegram->read_value(setFlowTemp_,
0)); // boiler set temp from thermostat
has_update(telegram->read_value(setBurnPow_, 1)); // max json power in %
has_update(telegram->read_value(wWSetPumpPower_, 2)); // ww pump speed/power?
}
@@ -727,8 +763,9 @@ bool Boiler::set_warmwater_temp(const char * value, const int8_t id) {
write_command(EMS_TYPE_UBAParameterWWPlus, 6, v, EMS_TYPE_UBAParameterWWPlus);
} else {
// some boiler have it in 0x33, some in 0x35
write_command(EMS_TYPE_UBAFlags, 3, v, 0x34); // for i9000, see #397
write_command(EMS_TYPE_UBAParameterWW, 2, v, EMS_TYPE_UBAParameterWW); // read seltemp back
write_command(EMS_TYPE_UBAFlags, 3, v, 0x34); // for i9000, see #397
write_command(EMS_TYPE_UBAParameterWW, 2, v,
EMS_TYPE_UBAParameterWW); // read seltemp back
}
return true;
@@ -769,11 +806,11 @@ bool Boiler::set_flow_temp(const char * value, const int8_t id) {
bool Boiler::set_burn_power(const char * value, const int8_t id) {
int v = 0;
if (!Helpers::value2number(value, v)) {
LOG_WARNING(F("Set burner max. power: Invalid value"));
LOG_WARNING(F("Set burner max power: Invalid value"));
return false;
}
LOG_INFO(F("Setting burner max. power to %d %"), v);
LOG_INFO(F("Setting burner max power to %d %"), v);
write_command(EMS_TYPE_UBASetPoints, 1, v, EMS_TYPE_UBASetPoints);
return true;
@@ -955,11 +992,11 @@ bool Boiler::set_hyst_off(const char * value, const int8_t id) {
bool Boiler::set_burn_period(const char * value, const int8_t id) {
int v = 0;
if (!Helpers::value2number(value, v)) {
LOG_WARNING(F("Set burner min. period: Invalid value"));
LOG_WARNING(F("Set burner min period: Invalid value"));
return false;
}
LOG_INFO(F("Setting burner min. period to %d min"), v);
LOG_INFO(F("Setting burner min period to %d min"), v);
if (get_toggle_fetch(EMS_TYPE_UBAParametersPlus)) {
write_command(EMS_TYPE_UBAParametersPlus, 10, v, EMS_TYPE_UBAParametersPlus);
} else {
@@ -1088,7 +1125,8 @@ bool Boiler::set_warmwater_onetime(const char * value, const int8_t id) {
LOG_INFO(F("Setting warm water OneTime loading %s"), v ? "on" : "off");
if (get_toggle_fetch(EMS_TYPE_UBAParameterWWPlus)) {
write_command(EMS_TYPE_UBAFlags, 0, (v ? 0x22 : 0x02), 0xE9); // not sure if this is in flags
write_command(EMS_TYPE_UBAFlags, 0, (v ? 0x22 : 0x02),
0xE9); // not sure if this is in flags
} else {
write_command(EMS_TYPE_UBAFlags, 0, (v ? 0x22 : 0x02), 0x34);
}
@@ -1107,7 +1145,8 @@ bool Boiler::set_warmwater_circulation(const char * value, const int8_t id) {
LOG_INFO(F("Setting warm water circulation %s"), v ? "on" : "off");
if (get_toggle_fetch(EMS_TYPE_UBAParameterWWPlus)) {
write_command(EMS_TYPE_UBAFlags, 1, (v ? 0x22 : 0x02), 0xE9); // not sure if this is in flags
write_command(EMS_TYPE_UBAFlags, 1, (v ? 0x22 : 0x02),
0xE9); // not sure if this is in flags
} else {
write_command(EMS_TYPE_UBAFlags, 1, (v ? 0x22 : 0x02), 0x34);
}
@@ -1134,7 +1173,7 @@ bool Boiler::set_warmwater_circulation_pump(const char * value, const int8_t id)
return true;
}
// Set the mode of circulation, 1x3min, ... 6x3min, continuos
// Set the mode of circulation, 1x3min, ... 6x3min, continuous
// true = on, false = off
bool Boiler::set_warmwater_circulation_mode(const char * value, const int8_t id) {
int v = 0;
@@ -1146,7 +1185,7 @@ bool Boiler::set_warmwater_circulation_mode(const char * value, const int8_t id)
if (v < 7) {
LOG_INFO(F("Setting warm water circulation mode %dx3min"), v);
} else if (v == 7) {
LOG_INFO(F("Setting warm water circulation mode continuos"));
LOG_INFO(F("Setting warm water circulation mode continuous"));
} else {
LOG_WARNING(F("Set warm water circulation mode: Invalid value"));
return false;
@@ -1164,7 +1203,7 @@ bool Boiler::set_warmwater_circulation_mode(const char * value, const int8_t id)
// Reset command
// 0 & 1 Reset-Mode (Manual, others)
// 8 reset maintenance message Hxx
// 12 & 13 Reset that Error-memory
// 12 & 13 Reset the Error-memory
bool Boiler::set_reset(const char * value, const int8_t id) {
uint8_t num;
if (!Helpers::value2enum(value, num, FL_(enum_reset))) {

View File

@@ -44,13 +44,16 @@ Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, c
bool Heatpump::publish_ha_config() {
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
doc["uniq_id"] = F_(heatpump);
doc["ic"] = F_(iconheatpump);
doc["ic"] = F_(icondevice);
char stat_t[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s"), Mqtt::base().c_str(), Mqtt::tag_to_topic(device_type(), DeviceValueTAG::TAG_NONE).c_str());
doc["stat_t"] = stat_t;
doc["name"] = FJSON("ID");
char name_s[40];
snprintf_P(name_s, sizeof(name_s), PSTR("* %s Product ID"), device_type_name().c_str());
doc["name"] = name_s;
doc["val_tpl"] = FJSON("{{value_json.id}}");
JsonObject dev = doc.createNestedObject("dev");
dev["name"] = FJSON("EMS-ESP Heat Pump");

View File

@@ -60,7 +60,7 @@ Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
register_device_value(tag, &id_, DeviceValueType::UINT, nullptr, FL_(ID), DeviceValueUOM::NONE);
register_device_value(tag, &flowSetTemp_, DeviceValueType::UINT, nullptr, FL_(flowSetTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &flowTempHc_, DeviceValueType::USHORT, FL_(div10), FL_(flowTempHc), DeviceValueUOM::DEGREES);
register_device_value(tag, &pumpStatus_, DeviceValueType::BOOL, nullptr, FL_(pumpStatus), DeviceValueUOM::PUMP);
register_device_value(tag, &pumpStatus_, DeviceValueType::BOOL, nullptr, FL_(pumpStatus), DeviceValueUOM::BOOLEAN);
register_device_value(tag, &status_, DeviceValueType::INT, nullptr, FL_(mixerStatus), DeviceValueUOM::PERCENT);
register_device_value(tag, &flowTempVf_, DeviceValueType::USHORT, FL_(div10), FL_(flowTempVf), DeviceValueUOM::DEGREES);
} else {
@@ -69,7 +69,7 @@ Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
uint8_t tag = TAG_WWC1 + hc_ - 1;
register_device_value(tag, &id_, DeviceValueType::UINT, nullptr, FL_(ID), DeviceValueUOM::NONE);
register_device_value(tag, &flowTempHc_, DeviceValueType::USHORT, FL_(div10), FL_(wwTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &pumpStatus_, DeviceValueType::BOOL, nullptr, FL_(wwPumpStatus), DeviceValueUOM::PUMP);
register_device_value(tag, &pumpStatus_, DeviceValueType::BOOL, nullptr, FL_(wwPumpStatus), DeviceValueUOM::BOOLEAN);
register_device_value(tag, &status_, DeviceValueType::INT, nullptr, FL_(wwTempStatus), DeviceValueUOM::NONE);
}
@@ -89,6 +89,8 @@ bool Mixer::publish_ha_config() {
snprintf_P(uniq_id, sizeof(uniq_id), PSTR("Mixer%02X"), device_id() - 0x20 + 1);
doc["uniq_id"] = uniq_id;
doc["ic"] = F_(icondevice);
char stat_t[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s"), Mqtt::base().c_str(), Mqtt::tag_to_topic(device_type(), DeviceValueTAG::TAG_NONE).c_str());
doc["stat_t"] = stat_t;
@@ -97,7 +99,14 @@ bool Mixer::publish_ha_config() {
snprintf_P(name, sizeof(name), PSTR("Mixer %02X"), device_id() - 0x20 + 1);
doc["name"] = name;
doc["val_tpl"] = FJSON("{{value_json.id}}");
char tpl[30];
if (type_ == Type::HC) {
snprintf_P(tpl, sizeof(tpl), PSTR("{{value_json.hc%d.id}}"), device_id() - 0x20 + 1);
} else {
snprintf_P(tpl, sizeof(tpl), PSTR("{{value_json.wwc%d.id}}"), device_id() - 0x28 + 1);
}
doc["val_tpl"] = tpl;
JsonObject dev = doc.createNestedObject("dev");
dev["name"] = FJSON("EMS-ESP Mixer");
dev["sw"] = EMSESP_APP_VERSION;

View File

@@ -30,7 +30,9 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
// telegram handlers
if (flags == EMSdevice::EMS_DEVICE_FLAG_SM10) {
register_telegram_type(0x0097, F("SM10Monitor"), true, MAKE_PF_CB(process_SM10Monitor));
register_telegram_type(0x97, F("SM10Monitor"), false, MAKE_PF_CB(process_SM10Monitor));
register_telegram_type(0x96, F("SM10Config"), true, MAKE_PF_CB(process_SM10Config));
EMSESP::send_read_request(0x97, device_id);
}
if (flags == EMSdevice::EMS_DEVICE_FLAG_SM100) {
@@ -55,7 +57,7 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
if (flags == EMSdevice::EMS_DEVICE_FLAG_ISM) {
register_telegram_type(0x0103, F("ISM1StatusMessage"), true, MAKE_PF_CB(process_ISM1StatusMessage));
register_telegram_type(0x0101, F("ISM1Set"), false, MAKE_PF_CB(process_ISM1Set));
register_telegram_type(0x0101, F("ISM1Set"), true, MAKE_PF_CB(process_ISM1Set));
}
// device values...
@@ -64,12 +66,12 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
if (device_id == 0x2A) {
register_device_value(TAG_NONE, &type_, DeviceValueType::TEXT, nullptr, FL_(type), DeviceValueUOM::NONE);
strlcpy(type_, "warm water circuit", sizeof(type_));
register_device_value(TAG_DEVICE_DATA_WW, &wwTemp_1_, DeviceValueType::UINT, nullptr, FL_(wwTemp1), DeviceValueUOM::DEGREES);
register_device_value(TAG_DEVICE_DATA_WW, &wwTemp_3_, DeviceValueType::UINT, nullptr, FL_(wwTemp3), DeviceValueUOM::DEGREES);
register_device_value(TAG_DEVICE_DATA_WW, &wwTemp_4_, DeviceValueType::UINT, nullptr, FL_(wwTemp4), DeviceValueUOM::DEGREES);
register_device_value(TAG_DEVICE_DATA_WW, &wwTemp_5_, DeviceValueType::UINT, nullptr, FL_(wwTemp5), DeviceValueUOM::DEGREES);
register_device_value(TAG_DEVICE_DATA_WW, &wwTemp_7_, DeviceValueType::UINT, nullptr, FL_(wwTemp7), DeviceValueUOM::DEGREES);
register_device_value(TAG_DEVICE_DATA_WW, &wwPump_, DeviceValueType::UINT, nullptr, FL_(wwPump), DeviceValueUOM::DEGREES);
register_device_value(TAG_NONE, &wwTemp_1_, DeviceValueType::UINT, nullptr, FL_(wwTemp1), DeviceValueUOM::DEGREES);
register_device_value(TAG_NONE, &wwTemp_3_, DeviceValueType::UINT, nullptr, FL_(wwTemp3), DeviceValueUOM::DEGREES);
register_device_value(TAG_NONE, &wwTemp_4_, DeviceValueType::UINT, nullptr, FL_(wwTemp4), DeviceValueUOM::DEGREES);
register_device_value(TAG_NONE, &wwTemp_5_, DeviceValueType::UINT, nullptr, FL_(wwTemp5), DeviceValueUOM::DEGREES);
register_device_value(TAG_NONE, &wwTemp_7_, DeviceValueType::UINT, nullptr, FL_(wwTemp7), DeviceValueUOM::DEGREES);
register_device_value(TAG_NONE, &wwPump_, DeviceValueType::UINT, nullptr, FL_(wwPump), DeviceValueUOM::DEGREES);
return;
}
@@ -78,35 +80,125 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
register_device_value(TAG_NONE, &collectorTemp_, DeviceValueType::SHORT, FL_(div10), FL_(collectorTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_NONE, &tankBottomTemp_, DeviceValueType::SHORT, FL_(div10), FL_(tankBottomTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_NONE, &tankBottomTemp2_, DeviceValueType::SHORT, FL_(div10), FL_(tank2BottomTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_NONE, &heatExchangerTemp_, DeviceValueType::SHORT, FL_(div10), FL_(heatExchangerTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_NONE, &tankBottomMaxTemp_, DeviceValueType::UINT, nullptr, FL_(tankMaxTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_SM100TankBottomMaxTemp));
register_device_value(TAG_NONE, &solarPumpModulation_, DeviceValueType::UINT, nullptr, FL_(solarPumpModulation), DeviceValueUOM::PERCENT);
register_device_value(TAG_NONE, &cylinderPumpModulation_, DeviceValueType::UINT, nullptr, FL_(cylinderPumpModulation), DeviceValueUOM::PERCENT);
register_device_value(TAG_NONE, &solarPump_, DeviceValueType::BOOL, nullptr, FL_(solarPump), DeviceValueUOM::PUMP);
register_device_value(TAG_NONE, &valveStatus_, DeviceValueType::BOOL, nullptr, FL_(valveStatus), DeviceValueUOM::NONE);
register_device_value(TAG_NONE, &tankHeated_, DeviceValueType::BOOL, nullptr, FL_(tankHeated), DeviceValueUOM::NONE);
register_device_value(TAG_NONE, &collectorShutdown_, DeviceValueType::BOOL, nullptr, FL_(collectorShutdown), DeviceValueUOM::NONE);
register_device_value(TAG_NONE, &solarPump_, DeviceValueType::BOOL, nullptr, FL_(solarPump), DeviceValueUOM::BOOLEAN);
register_device_value(TAG_NONE, &pumpWorkTime_, DeviceValueType::TIME, nullptr, FL_(pumpWorkTime), DeviceValueUOM::MINUTES);
register_device_value(TAG_NONE, &tankMaxTemp_, DeviceValueType::UINT, nullptr, FL_(tankMaxTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_TankMaxTemp));
register_device_value(TAG_NONE, &energyLastHour_, DeviceValueType::ULONG, FL_(div10), FL_(energyLastHour), DeviceValueUOM::WH);
register_device_value(TAG_NONE, &energyTotal_, DeviceValueType::ULONG, FL_(div10), FL_(energyTotal), DeviceValueUOM::KWH);
register_device_value(TAG_NONE, &energyToday_, DeviceValueType::ULONG, nullptr, FL_(energyToday), DeviceValueUOM::WH);
if (flags == EMSdevice::EMS_DEVICE_FLAG_SM10) {
register_device_value(TAG_NONE, &solarPumpModulation_, DeviceValueType::UINT, nullptr, FL_(solarPumpModulation), DeviceValueUOM::PERCENT);
register_device_value(TAG_NONE, &solarPumpMinMod_, DeviceValueType::UINT, nullptr, FL_(pumpMinMod), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_PumpMinMod));
register_device_value(
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, &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));
register_device_value(TAG_NONE, &wwMinTemp_, DeviceValueType::UINT, nullptr, FL_(wwMinTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwMinTemp));
register_device_value(TAG_NONE, &solarIsEnabled_, DeviceValueType::BOOL, nullptr, FL_(activated), DeviceValueUOM::BOOLEAN, MAKE_CF_CB(set_solarEnabled));
}
if (flags == EMSdevice::EMS_DEVICE_FLAG_ISM) {
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, &energyLastHour_, DeviceValueType::ULONG, FL_(div10), FL_(energyLastHour), DeviceValueUOM::WH);
}
if (flags == EMSdevice::EMS_DEVICE_FLAG_SM100) {
register_device_value(TAG_NONE, &solarPumpModulation_, DeviceValueType::UINT, nullptr, FL_(solarPumpModulation), DeviceValueUOM::PERCENT);
register_device_value(TAG_NONE, &solarPumpMinMod_, DeviceValueType::UINT, nullptr, FL_(pumpMinMod), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_PumpMinMod));
register_device_value(
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, &tankBottomTemp2_, DeviceValueType::SHORT, FL_(div10), FL_(tank2BottomTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_NONE, &heatExchangerTemp_, DeviceValueType::SHORT, FL_(div10), FL_(heatExchangerTemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_NONE, &cylinderPumpModulation_, DeviceValueType::UINT, nullptr, FL_(cylinderPumpModulation), DeviceValueUOM::PERCENT);
register_device_value(TAG_NONE, &valveStatus_, DeviceValueType::BOOL, nullptr, FL_(valveStatus), DeviceValueUOM::BOOLEAN);
register_device_value(TAG_NONE, &tankHeated_, DeviceValueType::BOOL, nullptr, FL_(tankHeated), DeviceValueUOM::BOOLEAN);
register_device_value(TAG_NONE, &collectorShutdown_, DeviceValueType::BOOL, nullptr, FL_(collectorShutdown), DeviceValueUOM::BOOLEAN);
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, &energyLastHour_, DeviceValueType::ULONG, FL_(div10), FL_(energyLastHour), DeviceValueUOM::WH);
register_device_value(TAG_NONE, &energyToday_, DeviceValueType::ULONG, nullptr, FL_(energyToday), DeviceValueUOM::WH);
register_device_value(TAG_NONE, &energyTotal_, DeviceValueType::ULONG, FL_(div10), FL_(energyTotal), DeviceValueUOM::KWH);
register_device_value(TAG_NONE,
&heatTransferSystem_,
DeviceValueType::BOOL,
nullptr,
FL_(heatTransferSystem),
DeviceValueUOM::BOOLEAN,
MAKE_CF_CB(set_heatTransferSystem));
register_device_value(TAG_NONE, &externalTank_, DeviceValueType::BOOL, nullptr, FL_(externalTank), DeviceValueUOM::BOOLEAN, MAKE_CF_CB(set_externalTank));
register_device_value(
TAG_NONE, &thermalDisinfect_, DeviceValueType::BOOL, nullptr, FL_(thermalDisinfect), DeviceValueUOM::BOOLEAN, MAKE_CF_CB(set_thermalDisinfect));
register_device_value(TAG_NONE, &heatMetering_, DeviceValueType::BOOL, nullptr, FL_(heatMetering), DeviceValueUOM::BOOLEAN, MAKE_CF_CB(set_heatMetering));
register_device_value(TAG_NONE, &solarIsEnabled_, DeviceValueType::BOOL, nullptr, FL_(activated), DeviceValueUOM::BOOLEAN, MAKE_CF_CB(set_solarEnabled));
// telegram 0x035A
register_device_value(
TAG_NONE, &solarPumpMode_, DeviceValueType::ENUM, FL_(enum_solarmode), FL_(solarPumpMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_solarMode));
register_device_value(TAG_NONE,
&solarPumpKick_,
DeviceValueType::BOOL,
nullptr,
FL_(solarPumpKick),
DeviceValueUOM::BOOLEAN,
MAKE_CF_CB(set_solarPumpKick)); // pump kick for vacuum collector, 00=off
register_device_value(TAG_NONE,
&plainWaterMode_,
DeviceValueType::BOOL,
nullptr,
FL_(plainWaterMode),
DeviceValueUOM::BOOLEAN,
MAKE_CF_CB(set_plainWaterMode)); // system does not use antifreeze, 00=off
register_device_value(TAG_NONE,
&doubleMatchFlow_,
DeviceValueType::BOOL,
nullptr,
FL_(doubleMatchFlow),
DeviceValueUOM::BOOLEAN,
MAKE_CF_CB(set_doubleMatchFlow)); // double Match Flow, 00=off
// telegram 0x380
register_device_value(TAG_NONE, &climateZone_, DeviceValueType::UINT, nullptr, FL_(climateZone), DeviceValueUOM::NONE, MAKE_CF_CB(set_climateZone)); // climate zone identifier
register_device_value(TAG_NONE,
&collector1Area_,
DeviceValueType::USHORT,
FL_(div10),
FL_(collector1Area),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_collector1Area)); // Area of collector field 1
register_device_value(TAG_NONE,
&collector1Type_,
DeviceValueType::ENUM,
FL_(enum_collectortype),
FL_(collector1Type),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_collector1Type)); // Type of collector field 1, 01=flat, 02=vacuum
}
}
// publish HA config
bool Solar::publish_ha_config() {
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
doc["name"] = FJSON("ID");
doc["uniq_id"] = F_(solar);
doc["ic"] = F_(icondevice);
char stat_t[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s"), Mqtt::base().c_str(), Mqtt::tag_to_topic(device_type(), DeviceValueTAG::TAG_NONE).c_str());
doc["stat_t"] = stat_t;
char name_s[40];
snprintf_P(name_s, sizeof(name_s), PSTR("* %s Product ID"), device_type_name().c_str());
doc["name"] = name_s;
doc["val_tpl"] = FJSON("{{value_json.id}}");
JsonObject dev = doc.createNestedObject("dev");
dev["name"] = FJSON("EMS-ESP Solar");
@@ -123,13 +215,59 @@ bool Solar::publish_ha_config() {
return true;
}
// SM10Monitor - type 0x96
// 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));
has_update(telegram->read_value(tankMaxTemp_, 5));
has_update(telegram->read_value(wwMinTemp_, 6));
}
// SM10Monitor - type 0x97
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_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
has_update(telegram->read_bitvalue(solarPump_, 7, 1));
has_update(telegram->read_value(pumpWorkTime_, 8, 3));
// mask out pump-boosts
if (solarpumpmod == 0 && solarPumpModulation_ == 100) {
solarPumpModulation_ = solarPumpMinMod_; // set to minimum
}
if (!Helpers::hasValue(maxFlow_)) {
EMSESP::webSettingsService.read([&](WebSettings & settings) { maxFlow_ = settings.solar_maxflow; });
}
// solar publishes every minute, do not count reads by other devices
if (telegram->dest == 0) {
// water 4.184 J/gK, glycol ~2.6-2.8 J/gK, no aceotrope
// solarPower_ = (collectorTemp_ - tankBottomTemp_) * solarPumpModulation_ * maxFlow_ * 10 / 1434; // water
solarPower_ = (collectorTemp_ - tankBottomTemp_) * solarPumpModulation_ * maxFlow_ * 10 / 1665; //40% glycol@40°C
if (energy.size() >= 60) {
energy.pop_front();
}
energy.push_back(solarPower_);
uint32_t sum = 0;
for (auto e : energy) {
sum += e;
}
energyLastHour_ = sum / 6; // counts in 0.1 Wh
}
}
/*
@@ -150,10 +288,10 @@ void Solar::process_SM100SystemConfig(std::shared_ptr<const Telegram> telegram)
*/
void Solar::process_SM100SolarCircuitConfig(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(collectorMaxTemp_, 0, 1));
has_update(telegram->read_value(tankBottomMaxTemp_, 3, 1));
has_update(telegram->read_value(tankMaxTemp_, 3, 1));
has_update(telegram->read_value(collectorMinTemp_, 4, 1));
has_update(telegram->read_value(solarPumpMode_, 5, 1));
has_update(telegram->read_value(solarPumpMinRPM_, 6, 1));
has_update(telegram->read_value(solarPumpMinMod_, 6, 1));
has_update(telegram->read_value(solarPumpTurnoffDiff_, 7, 1));
has_update(telegram->read_value(solarPumpTurnonDiff_, 8, 1));
has_update(telegram->read_value(solarPumpKick_, 9, 1));
@@ -262,11 +400,11 @@ void Solar::process_SM100Status(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(solarPumpModulation_, 9));
if (solarpumpmod == 0 && solarPumpModulation_ == 100) { // mask out boosts
solarPumpModulation_ = 15; // set to minimum
solarPumpModulation_ = solarPumpMinMod_; // set to minimum
}
if (cylinderpumpmod == 0 && cylinderPumpModulation_ == 100) { // mask out boosts
cylinderPumpModulation_ = 15; // set to minimum
cylinderPumpModulation_ = solarPumpMinMod_; // set to minimum
}
has_update(telegram->read_bitvalue(tankHeated_, 3, 1)); // issue #422
has_update(telegram->read_bitvalue(collectorShutdown_, 3, 0)); // collector shutdown
@@ -317,12 +455,9 @@ void Solar::process_SM100Time(std::shared_ptr<const Telegram> telegram) {
void Solar::process_ISM1StatusMessage(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(collectorTemp_, 4)); // Collector Temperature
has_update(telegram->read_value(tankBottomTemp_, 6)); // Temperature Bottom of Solar Boiler tank
uint16_t Wh = 0xFFFF;
uint16_t Wh = energyLastHour_ / 10;
has_update(telegram->read_value(Wh, 2)); // Solar Energy produced in last hour only ushort, is not * 10
if (Wh != 0xFFFF) {
energyLastHour_ = Wh * 10; // set to *10
}
energyLastHour_ = Wh * 10; // set to *10
has_update(telegram->read_bitvalue(solarPump_, 8, 0)); // PS1 Solar pump on (1) or off (0)
has_update(telegram->read_value(pumpWorkTime_, 10, 3)); // force to 3 bytes
@@ -334,21 +469,223 @@ void Solar::process_ISM1StatusMessage(std::shared_ptr<const Telegram> telegram)
* Junkers ISM1 Solar Module - type 0x0101 EMS+ for setting values
*/
void Solar::process_ISM1Set(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(setpoint_maxBottomTemp_, 6));
has_update(telegram->read_value(tankMaxTemp_, 6));
}
// set temperature for tank
bool Solar::set_SM100TankBottomMaxTemp(const char * value, const int8_t id) {
/*
* Settings
*/
// collector shutdown temperature
bool Solar::set_CollectorMaxTemp(const char * value, const int8_t id) {
int temperature;
if (!Helpers::value2number(value, temperature)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_SM10) {
write_command(0x96, 3, (uint8_t)temperature / 10, 0x96);
} else {
write_command(0x35A, 0, (uint8_t)temperature, 0x35A);
}
return true;
}
// write value
// 90 30 FF 03 02 5A 59 B3
// note: optionally add the validate to 0x035A which will pick up the adjusted tank1MaxTempCurrent_
write_command(0x35A, 0x03, (uint8_t)temperature);
// collector shutdown temperature
bool Solar::set_CollectorMinTemp(const char * value, const int8_t id) {
int temperature;
if (!Helpers::value2number(value, temperature)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_SM10) {
write_command(0x96, 4, (uint8_t)temperature / 10, 0x96);
} else {
write_command(0x35A, 4, (uint8_t)temperature, 0x35A);
}
return true;
}
bool Solar::set_TankMaxTemp(const char * value, const int8_t id) {
int temperature;
if (!Helpers::value2number(value, temperature)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_SM10) {
write_command(0x96, 5, (uint8_t)temperature, 0x96);
} else if (flags() == EMSdevice::EMS_DEVICE_FLAG_ISM) {
write_command(0x101, 6, (uint8_t)temperature, 0x101);
} else {
write_command(0x35A, 3, (uint8_t)temperature, 0x35A);
}
return true;
}
bool Solar::set_PumpMinMod(const char * value, const int8_t id) {
int modulation;
if (!Helpers::value2number(value, modulation)) {
return false;
}
write_command(0x96, 2, (uint8_t)modulation, 0x96);
return true;
}
bool Solar::set_wwMinTemp(const char * value, const int8_t id) {
int temperature;
if (!Helpers::value2number(value, temperature)) {
return false;
}
write_command(0x96, 6, (uint8_t)temperature, 0x96);
return true;
}
bool Solar::set_TurnoffDiff(const char * value, const int8_t id) {
int temperature;
if (!Helpers::value2number(value, temperature)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_SM10) {
write_command(0x96, 8, (uint8_t)temperature, 0x96);
} else {
write_command(0x35A, 7, (uint8_t)temperature, 0x35A);
}
return true;
}
bool Solar::set_TurnonDiff(const char * value, const int8_t id) {
int temperature;
if (!Helpers::value2number(value, temperature)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_SM10) {
write_command(0x96, 7, (uint8_t)temperature, 0x96);
} else {
write_command(0x35A, 8, (uint8_t)temperature, 0x35A);
}
return true;
}
// external value to calculate energy
bool Solar::set_SM10MaxFlow(const char * value, const int8_t id) {
float flow;
if (!Helpers::value2float(value, flow)) {
return false;
}
maxFlow_ = (flow * 10);
EMSESP::webSettingsService.update(
[&](WebSettings & settings) {
settings.solar_maxflow = maxFlow_;
return StateUpdateResult::CHANGED;
},
"local");
return true;
}
bool Solar::set_heatTransferSystem(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
return false;
}
write_command(0x358, 5, v ? 0x01 : 0x00, 0x358);
return true;
}
bool Solar::set_externalTank(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
return false;
}
write_command(0x358, 9, v ? 0x01 : 0x00, 0x358);
return true;
}
bool Solar::set_thermalDisinfect(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
return false;
}
write_command(0x358, 10, v ? 0x01 : 0x00, 0x358);
return true;
}
bool Solar::set_heatMetering(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
return false;
}
write_command(0x358, 14, v ? 0x01 : 0x00, 0x358);
return true;
}
bool Solar::set_solarEnabled(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_SM10) {
write_command(0x96, 0, v ? 0xFF : 0x00, 0x96);
} else {
write_command(0x358, 19, v ? 0x01 : 0x00, 0x358);
}
return true;
}
bool Solar::set_solarMode(const char * value, const int8_t id) {
uint8_t num;
if (!Helpers::value2enum(value, num, FL_(enum_solarmode))) {
return false;
}
write_command(0x35A, 5, num, 0x35A);
return true;
}
bool Solar::set_solarPumpKick(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
return false;
}
write_command(0x35A, 9, v ? 0x01 : 0x00, 0x35A);
return true;
}
bool Solar::set_plainWaterMode(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
return false;
}
write_command(0x35A, 10, v ? 0x01 : 0x00, 0x35A);
return true;
}
bool Solar::set_doubleMatchFlow(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
return false;
}
write_command(0x35A, 11, v ? 0x01 : 0x00, 0x35A);
return true;
}
bool Solar::set_climateZone(const char * value, const int8_t id) {
int v = 0;
if (!Helpers::value2number(value, v)) {
return false;
}
write_command(0x380, 0, v, 0x380);
return true;
}
bool Solar::set_collector1Area(const char * value, const int8_t id) {
float v = 0;
if (!Helpers::value2float(value, v)) {
return false;
}
write_command(0x380, 3, (uint16_t)(v * 10), 0x380);
return true;
}
bool Solar::set_collector1Type(const char * value, const int8_t id) {
uint8_t num;
if (!Helpers::value2enum(value, num, FL_(enum_collectortype))) {
return false;
}
write_command(0x380, 5, num, 0x380);
return true;
}

View File

@@ -40,7 +40,6 @@ class Solar : public EMSdevice {
uint8_t cylinderPumpModulation_; // PS5: modulation cylinder pump
uint8_t solarPump_; // PS1: solar pump active
uint8_t valveStatus_; // VS2: status 3-way valve for cylinder 2 (solar thermal system) with valve
int16_t setpoint_maxBottomTemp_; // setpoint for maximum collector temp
uint32_t energyLastHour_;
uint32_t energyToday_;
uint32_t energyTotal_;
@@ -61,10 +60,10 @@ class Solar : public EMSdevice {
// telegram 0x035A
uint8_t collectorMaxTemp_; // maximum allowed collectorTemp array 1
uint8_t tankBottomMaxTemp_; // Current value for max tank temp
uint8_t tankMaxTemp_; // Current value for max tank temp
uint8_t collectorMinTemp_; // minimum allowed collectorTemp array 1
uint8_t solarPumpMode_; // 00=off, 01=PWM, 02=10V
uint8_t solarPumpMinRPM_; // minimum RPM setting, *5 %
uint8_t solarPumpMinMod_; // minimum modulation setting, *5 %
uint8_t solarPumpTurnoffDiff_; // solar pump turnoff collector/tank diff
uint8_t solarPumpTurnonDiff_; // solar pump turnon collector/tank diff
uint8_t solarPumpKick_; // pump kick for vacuum collector, 00=off
@@ -86,10 +85,18 @@ class Solar : public EMSdevice {
// SM100wwStatus - 0x07AA
uint8_t wwPump_;
// SM10Config - 0x96
uint8_t wwMinTemp_;
uint8_t maxFlow_; // set this to caltulate power
uint32_t solarPower_; // calculated from maxFlow
std::deque<uint16_t> energy;
char type_[20]; // Solar of WWC
uint8_t id_;
void process_SM10Monitor(std::shared_ptr<const Telegram> telegram);
void process_SM10Config(std::shared_ptr<const Telegram> telegram);
void process_SM100SystemConfig(std::shared_ptr<const Telegram> telegram);
void process_SM100SolarCircuitConfig(std::shared_ptr<const Telegram> telegram);
void process_SM100ParamCfg(std::shared_ptr<const Telegram> telegram);
@@ -112,7 +119,28 @@ class Solar : public EMSdevice {
void process_ISM1Set(std::shared_ptr<const Telegram> telegram);
bool set_SM100TankBottomMaxTemp(const char * value, const int8_t id);
bool set_CollectorMaxTemp(const char * value, const int8_t id);
bool set_CollectorMinTemp(const char * value, const int8_t id);
bool set_TankMaxTemp(const char * value, const int8_t id);
bool set_PumpMinMod(const char * value, const int8_t id);
bool set_wwMinTemp(const char * value, const int8_t id);
bool set_TurnonDiff(const char * value, const int8_t id);
bool set_TurnoffDiff(const char * value, const int8_t id);
bool set_SM10MaxFlow(const char * value, const int8_t id);
// SM100
bool set_heatTransferSystem(const char * value, const int8_t id);
bool set_externalTank(const char * value, const int8_t id);
bool set_thermalDisinfect(const char * value, const int8_t id);
bool set_heatMetering(const char * value, const int8_t id);
bool set_solarEnabled(const char * value, const int8_t id);
bool set_solarMode(const char * value, const int8_t id);
bool set_solarPumpKick(const char * value, const int8_t id);
bool set_plainWaterMode(const char * value, const int8_t id);
bool set_doubleMatchFlow(const char * value, const int8_t id);
bool set_climateZone(const char * value, const int8_t id);
bool set_collector1Area(const char * value, const int8_t id);
bool set_collector1Type(const char * value, const int8_t id);
};
} // namespace emsesp

View File

@@ -35,7 +35,7 @@ Switch::Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const
register_telegram_type(0x1E, F("WM10TempMessage"), false, MAKE_PF_CB(process_WM10TempMessage));
register_device_value(TAG_NONE, &id_, DeviceValueType::UINT, nullptr, FL_(ID), DeviceValueUOM::NONE);
register_device_value(TAG_NONE, &activated_, DeviceValueType::BOOL, nullptr, FL_(activated), DeviceValueUOM::NONE);
register_device_value(TAG_NONE, &activated_, DeviceValueType::BOOL, nullptr, FL_(activated), DeviceValueUOM::BOOLEAN);
register_device_value(TAG_NONE, &flowTempHc_, DeviceValueType::USHORT, FL_(div10), FL_(flowTempHc), DeviceValueUOM::DEGREES);
register_device_value(TAG_NONE, &status_, DeviceValueType::INT, nullptr, FL_(status), DeviceValueUOM::NONE);
id_ = product_id;
@@ -50,12 +50,16 @@ bool Switch::publish_ha_config() {
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
doc["uniq_id"] = F_(switch);
doc["ic"] = F_(icondevice);
char stat_t[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s"), Mqtt::base().c_str(), Mqtt::tag_to_topic(device_type(), DeviceValueTAG::TAG_NONE).c_str());
doc["stat_t"] = stat_t;
doc["name"] = FJSON("ID");
char name_s[40];
snprintf_P(name_s, sizeof(name_s), PSTR("* %s Product ID"), device_type_name().c_str());
doc["name"] = name_s;
doc["val_tpl"] = FJSON("{{value_json.id}}");
JsonObject dev = doc.createNestedObject("dev");
dev["name"] = FJSON("EMS-ESP Switch");

View File

@@ -38,7 +38,8 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
// or if its the master thermostat we defined
// see https://github.com/emsesp/EMS-ESP/issues/362#issuecomment-629628161
if ((master_thermostat == device_id)
|| ((master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT) && (device_id < 0x19) && ((actual_master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT) || (device_id < actual_master_thermostat)))) {
|| ((master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT) && (device_id < 0x19)
&& ((actual_master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT) || (device_id < actual_master_thermostat)))) {
EMSESP::actual_master_thermostat(device_id);
actual_master_thermostat = device_id;
reserve_telgram_functions(25); // reserve some space for the telegram registries, to avoid memory fragmentation
@@ -196,12 +197,16 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
bool Thermostat::publish_ha_config() {
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
doc["uniq_id"] = F_(thermostat);
doc["ic"] = F_(icondevice);
char stat_t[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s"), Mqtt::base().c_str(), Mqtt::tag_to_topic(device_type(), DeviceValueTAG::TAG_NONE).c_str());
doc["stat_t"] = stat_t;
doc["name"] = FJSON("ID");
char name_s[40];
snprintf_P(name_s, sizeof(name_s), PSTR("* %s Product ID"), device_type_name().c_str());
doc["name"] = name_s;
doc["val_tpl"] = FJSON("{{value_json.id}}");
JsonObject dev = doc.createNestedObject("dev");
dev["name"] = FJSON("EMS-ESP Thermostat");
@@ -340,7 +345,7 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
// now create the HA topics to send to MQTT for each sensor
if (Mqtt::ha_enabled()) {
register_mqtt_ha_config_hc(hc_num);
publish_ha_config_hc(hc_num);
}
// set the flag saying we want its data during the next auto fetch
@@ -364,7 +369,7 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
// publish config topic for HA MQTT Discovery for each of the heating circuit
// e.g. homeassistant/climate/ems-esp/thermostat_hc1/config
void Thermostat::register_mqtt_ha_config_hc(uint8_t hc_num) {
void Thermostat::publish_ha_config_hc(uint8_t hc_num) {
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
char str1[20];
@@ -622,6 +627,7 @@ void Thermostat::process_RC20Set(std::shared_ptr<const Telegram> telegram) {
return;
}
has_update(telegram->read_value(hc->mode, 23));
hc->hamode = hc->mode; // set special HA mode
}
// type 0xAE - data from the RC20 thermostat (0x17) - not for RC20's
@@ -647,6 +653,7 @@ void Thermostat::process_RC20Set_2(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(hc->nighttemp, 1)); // is * 2,
has_update(telegram->read_value(hc->daytemp, 2)); // is * 2,
has_update(telegram->read_value(hc->mode, 3));
hc->hamode = hc->mode; // set special HA mode
has_update(telegram->read_value(hc->program, 11)); // 1 .. 9 predefined programs
}
@@ -768,6 +775,7 @@ void Thermostat::process_JunkersMonitor(std::shared_ptr<const Telegram> telegram
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
}
// type 0x02A5 - data from Worchester CRF200
@@ -778,7 +786,8 @@ void Thermostat::process_CRFMonitor(std::shared_ptr<const Telegram> telegram) {
}
has_update(telegram->read_value(hc->curr_roomTemp, 0)); // is * 10
has_update(telegram->read_bitvalue(hc->modetype, 2, 0));
has_update(telegram->read_bitvalue(hc->mode, 2, 4)); // bit 4, mode (auto=0 or off=1)
has_update(telegram->read_bitvalue(hc->mode, 2, 4)); // bit 4, mode (auto=0 or off=1)
hc->hamode = 2 - 2 * hc->mode; // set special HA mode
has_update(telegram->read_value(hc->setpoint_roomTemp, 6, 1)); // is * 2, force as single byte
has_update(telegram->read_value(hc->targetflowtemp, 4));
}
@@ -793,6 +802,7 @@ void Thermostat::process_RC300Monitor(std::shared_ptr<const Telegram> telegram)
has_update(telegram->read_value(hc->curr_roomTemp, 0)); // is * 10
has_update(telegram->read_bitvalue(hc->modetype, 10, 1));
has_update(telegram->read_bitvalue(hc->mode, 10, 0)); // bit 1, mode (auto=1 or manual=0)
hc->hamode = hc->mode + 1; // set special HA mode
// if manual, take the current setpoint temp at pos 6
// if auto, take the next setpoint temp at pos 7
@@ -936,6 +946,7 @@ void Thermostat::process_RC30Set(std::shared_ptr<const Telegram> telegram) {
}
has_update(telegram->read_value(hc->mode, 23));
hc->hamode = hc->mode; // set special HA mode
}
// type 0x3E (HC1), 0x48 (HC2), 0x52 (HC3), 0x5C (HC4) - data from the RC35 thermostat (0x10) - 16 bytes
@@ -985,6 +996,7 @@ void Thermostat::process_RC35Set(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(hc->roominfluence, 4)); // is * 1
has_update(telegram->read_value(hc->offsettemp, 6)); // is * 2
has_update(telegram->read_value(hc->mode, 7)); // night, day, auto
hc->hamode = hc->mode; // set special HA mode
has_update(telegram->read_value(hc->summertemp, 22)); // is * 1
has_update(telegram->read_value(hc->nofrosttemp, 23)); // is * 1
@@ -993,7 +1005,7 @@ void Thermostat::process_RC35Set(std::shared_ptr<const Telegram> telegram) {
has_update(telegram->read_value(hc->control, 26)); // 0-off, 1-RC20 (remote), 2-RC35
has_update(telegram->read_value(hc->controlmode, 33)); // 0-outdoortemp, 1-roomtemp
has_update(telegram->read_value(hc->tempautotemp, 37));
has_update(telegram->read_value(hc->noreducetemp, 38)); // outdoor temperature for no reduce
has_update(telegram->read_value(hc->noreducetemp, 38)); // outdoor temperature for no reduce
has_update(telegram->read_value(hc->minflowtemp, 16));
if (hc->heatingtype == 3) {
has_update(telegram->read_value(hc->designtemp, 36)); // is * 1
@@ -2003,7 +2015,10 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
// if we know what to send and to where, go and do it
if (offset != -1) {
char s[10];
LOG_INFO(F("Setting thermostat temperature to %s for heating circuit %d, mode %s"), Helpers::render_value(s, temperature, 2), hc->hc_num(), mode_tostring(mode).c_str());
LOG_INFO(F("Setting thermostat temperature to %s for heating circuit %d, mode %s"),
Helpers::render_value(s, temperature, 2),
hc->hc_num(),
mode_tostring(mode).c_str());
// add the write command to the Tx queue. value is *2
// post validate is the corresponding monitor or set type IDs as they can differ per model
@@ -2117,13 +2132,21 @@ void Thermostat::register_device_values() {
register_device_value(TAG_THERMOSTAT_DATA, &floordrystatus_, DeviceValueType::ENUM, FL_(enum_floordrystatus), FL_(floordrystatus), DeviceValueUOM::NONE);
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, &ibaBuildingType_, DeviceValueType::ENUM, FL_(enum_ibaBuildingType), FL_(ibaBuildingType), DeviceValueUOM::NONE, MAKE_CF_CB(set_building));
register_device_value(TAG_DEVICE_DATA_WW, &wwSetTemp_, DeviceValueType::UINT, nullptr, FL_(wwSetTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwtemp));
register_device_value(TAG_DEVICE_DATA_WW, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode));
register_device_value(TAG_DEVICE_DATA_WW, &wwSetTempLow_, DeviceValueType::UINT, nullptr, FL_(wwSetTempLow), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwtemplow));
register_device_value(TAG_DEVICE_DATA_WW, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode), FL_(wWCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwcircmode));
register_device_value(TAG_DEVICE_DATA_WW, &wwExtra1_, DeviceValueType::UINT, nullptr, FL_(wwExtra1), DeviceValueUOM::DEGREES);
register_device_value(TAG_DEVICE_DATA_WW, &wwExtra2_, DeviceValueType::UINT, nullptr, FL_(wwExtra2), DeviceValueUOM::DEGREES);
register_device_value(TAG_THERMOSTAT_DATA,
&ibaBuildingType_,
DeviceValueType::ENUM,
FL_(enum_ibaBuildingType),
FL_(ibaBuildingType),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_building));
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, &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));
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;
case EMS_DEVICE_FLAG_RC20_N:
case EMS_DEVICE_FLAG_RC20:
@@ -2133,24 +2156,67 @@ void Thermostat::register_device_values() {
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, &ibaClockOffset_, DeviceValueType::UINT, nullptr, FL_(ibaClockOffset), DeviceValueUOM::NONE); // offset (in sec) to clock, 0xff=-1s, 0x02=2s
register_device_value(TAG_THERMOSTAT_DATA, &ibaCalIntTemperature_, DeviceValueType::INT, FL_(div2), FL_(ibaCalIntTemperature), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_calinttemp));
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,
&ibaClockOffset_,
DeviceValueType::UINT,
nullptr,
FL_(ibaClockOffset),
DeviceValueUOM::NONE); // offset (in sec) to clock, 0xff=-1s, 0x02=2s
register_device_value(TAG_THERMOSTAT_DATA,
&ibaCalIntTemperature_,
DeviceValueType::INT,
FL_(div2),
FL_(ibaCalIntTemperature),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_calinttemp));
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, &dampedoutdoortemp_, DeviceValueType::INT, nullptr, FL_(dampedoutdoortemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_THERMOSTAT_DATA, &ibaBuildingType_, DeviceValueType::ENUM, FL_(enum_ibaBuildingType2), FL_(ibaBuildingType), DeviceValueUOM::NONE, MAKE_CF_CB(set_building));
register_device_value(TAG_DEVICE_DATA_WW, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode2), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode));
register_device_value(TAG_DEVICE_DATA_WW, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode2), FL_(wWCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwcircmode));
register_device_value(TAG_THERMOSTAT_DATA,
&ibaBuildingType_,
DeviceValueType::ENUM,
FL_(enum_ibaBuildingType2),
FL_(ibaBuildingType),
DeviceValueUOM::NONE,
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, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode2), FL_(wWCircMode), DeviceValueUOM::NONE, 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));
register_device_value(TAG_THERMOSTAT_DATA, &ibaCalIntTemperature_, DeviceValueType::INT, FL_(div2), FL_(ibaCalIntTemperature), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_calinttemp));
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,
&ibaCalIntTemperature_,
DeviceValueType::INT,
FL_(div2),
FL_(ibaCalIntTemperature),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_calinttemp));
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, &tempsensor1_, DeviceValueType::USHORT, FL_(div10), FL_(tempsensor1), DeviceValueUOM::DEGREES);
register_device_value(TAG_THERMOSTAT_DATA, &tempsensor2_, DeviceValueType::USHORT, FL_(div10), FL_(tempsensor2), DeviceValueUOM::DEGREES);
register_device_value(TAG_THERMOSTAT_DATA, &dampedoutdoortemp_, DeviceValueType::INT, nullptr, FL_(dampedoutdoortemp), DeviceValueUOM::DEGREES);
register_device_value(TAG_THERMOSTAT_DATA, &ibaBuildingType_, DeviceValueType::ENUM, FL_(enum_ibaBuildingType2), FL_(ibaBuildingType), DeviceValueUOM::NONE, MAKE_CF_CB(set_building));
register_device_value(TAG_DEVICE_DATA_WW, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode2), FL_(wwMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwmode));
register_device_value(TAG_DEVICE_DATA_WW, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode2), FL_(wWCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwcircmode));
register_device_value(TAG_THERMOSTAT_DATA,
&ibaBuildingType_,
DeviceValueType::ENUM,
FL_(enum_ibaBuildingType2),
FL_(ibaBuildingType),
DeviceValueUOM::NONE,
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, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode2), FL_(wWCircMode), DeviceValueUOM::NONE, 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));
@@ -2161,7 +2227,7 @@ void Thermostat::register_device_values() {
}
// registers the values for a heating circuit
void Thermostat::register_device_values_hc(std::shared_ptr<emsesp::Thermostat::HeatingCircuit> hc) {
void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCircuit> hc) {
uint8_t model = hc->get_model();
// heating circuit
@@ -2184,8 +2250,17 @@ void Thermostat::register_device_values_hc(std::shared_ptr<emsesp::Thermostat::H
if (has_flags(EMS_DEVICE_FLAG_NO_WRITE) || device_id() != EMSESP::actual_master_thermostat()) {
register_device_value(tag, &hc->setpoint_roomTemp, DeviceValueType::SHORT, setpoint_temp_divider, FL_(setpoint_roomTemp), DeviceValueUOM::DEGREES);
} else {
register_device_value(tag, &hc->setpoint_roomTemp, DeviceValueType::SHORT, setpoint_temp_divider, FL_(setpoint_roomTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_temp), 5, 29);
register_device_value(tag, &hc->setpoint_roomTemp, DeviceValueType::SHORT, setpoint_temp_divider, FL_(temp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_temp), 5, 29);
register_device_value(tag,
&hc->setpoint_roomTemp,
DeviceValueType::SHORT,
setpoint_temp_divider,
FL_(setpoint_roomTemp),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_temp),
5,
29);
register_device_value(
tag, &hc->setpoint_roomTemp, DeviceValueType::SHORT, setpoint_temp_divider, FL_(temp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_temp), 5, 29);
}
register_device_value(tag, &hc->curr_roomTemp, DeviceValueType::SHORT, curr_temp_divider, FL_(curr_roomTemp), DeviceValueUOM::DEGREES);
@@ -2209,8 +2284,9 @@ void Thermostat::register_device_values_hc(std::shared_ptr<emsesp::Thermostat::H
// manual & day = heat
// night & off = off
// everything else auto
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_hamode), FL_(hamode), DeviceValueUOM::NONE);
register_device_value(tag, &hc->hamode, DeviceValueType::ENUM, FL_(enum_hamode), FL_(hamode), DeviceValueUOM::NONE);
}
switch (model) {
case EMS_DEVICE_FLAG_RC100:
case EMS_DEVICE_FLAG_RC300:
@@ -2228,9 +2304,11 @@ void Thermostat::register_device_values_hc(std::shared_ptr<emsesp::Thermostat::H
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->summer_setmode, DeviceValueType::ENUM, FL_(enum_summermode), FL_(summermode), DeviceValueUOM::NONE, MAKE_CF_CB(set_summermode));
register_device_value(tag, &hc->summermode, DeviceValueType::BOOL, nullptr, FL_(summermode), DeviceValueUOM::NONE);
register_device_value(tag, &hc->controlmode, DeviceValueType::ENUM, FL_(enum_controlmode), FL_(controlmode), DeviceValueUOM::NONE, MAKE_CF_CB(set_controlmode));
register_device_value(
tag, &hc->summer_setmode, DeviceValueType::ENUM, FL_(enum_summermode), FL_(summersetmode), DeviceValueUOM::NONE, 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));
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;
@@ -2260,8 +2338,8 @@ void Thermostat::register_device_values_hc(std::shared_ptr<emsesp::Thermostat::H
register_device_value(tag, &hc->holidaytemp, DeviceValueType::UINT, FL_(div2), FL_(holidaytemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_holidaytemp));
register_device_value(tag, &hc->targetflowtemp, DeviceValueType::UINT, nullptr, FL_(targetflowtemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &hc->summertemp, DeviceValueType::UINT, nullptr, FL_(summertemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_summertemp));
register_device_value(tag, &hc->summermode, DeviceValueType::BOOL, nullptr, FL_(summermode), DeviceValueUOM::NONE);
register_device_value(tag, &hc->holidaymode, DeviceValueType::BOOL, nullptr, FL_(holidaymode), DeviceValueUOM::NONE, MAKE_CF_CB(set_holiday));
register_device_value(tag, &hc->summermode, DeviceValueType::BOOL, nullptr, FL_(summermode), DeviceValueUOM::BOOLEAN);
register_device_value(tag, &hc->holidaymode, DeviceValueType::BOOL, nullptr, FL_(holidaymode), DeviceValueUOM::BOOLEAN, MAKE_CF_CB(set_holiday));
register_device_value(tag, &hc->nofrosttemp, DeviceValueType::INT, nullptr, FL_(nofrosttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nofrosttemp));
register_device_value(tag, &hc->roominfluence, DeviceValueType::UINT, nullptr, FL_(roominfluence), DeviceValueUOM::NONE, MAKE_CF_CB(set_roominfluence));
register_device_value(tag, &hc->minflowtemp, DeviceValueType::UINT, nullptr, FL_(minflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_minflowtemp));
@@ -2269,7 +2347,8 @@ void Thermostat::register_device_values_hc(std::shared_ptr<emsesp::Thermostat::H
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->controlmode, DeviceValueType::ENUM, FL_(enum_controlmode2), FL_(controlmode), DeviceValueUOM::NONE, MAKE_CF_CB(set_controlmode));
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));
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));

View File

@@ -39,6 +39,7 @@ class Thermostat : public EMSdevice {
int16_t remotetemp; // for readback
uint8_t tempautotemp;
uint8_t mode;
uint8_t hamode; // special mode for HA. See https://github.com/emsesp/EMS-ESP32/issues/66
uint8_t modetype;
uint8_t summermode;
uint8_t holidaymode;
@@ -148,7 +149,7 @@ class Thermostat : public EMSdevice {
char dummychar_[5]; // 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
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
uint8_t ibaLanguage_; // language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian
int8_t ibaCalIntTemperature_; // offset int. temperature sensor, by * 0.1 Kelvin (-5.0 to 5.0K)
int8_t ibaMinExtTemperature_; // min ext temp for heating curve, in deg., 0xF6=-10, 0x0 = 0, 0xFF=-1
@@ -254,8 +255,8 @@ class Thermostat : public EMSdevice {
std::shared_ptr<Thermostat::HeatingCircuit> heating_circuit(std::shared_ptr<const Telegram> telegram);
std::shared_ptr<Thermostat::HeatingCircuit> heating_circuit(const uint8_t hc_num);
void register_mqtt_ha_config_hc(uint8_t hc_num);
void register_device_values_hc(std::shared_ptr<emsesp::Thermostat::HeatingCircuit> hc);
void publish_ha_config_hc(uint8_t hc_num);
void register_device_values_hc(std::shared_ptr<Thermostat::HeatingCircuit> hc);
bool thermostat_ha_cmd(const char * message, uint8_t hc_num);

View File

@@ -34,7 +34,13 @@ static const __FlashStringHelper * DeviceValueUOM_s[] __attribute__((__aligned__
F_(minutes),
F_(ua),
F_(bar),
F_(kw)
F_(kw),
F_(w),
F_(kb),
F_(seconds),
F_(dbm),
F_(num),
F_(bool)
};
@@ -45,7 +51,7 @@ static const __FlashStringHelper * const DeviceValueTAG_s[] PROGMEM = {
F_(tag_none), // ""
F_(tag_heartbeat), // ""
F_(tag_boiler_data), // ""
F_(tag_device_data_ww), // "warm water"
F_(tag_boiler_data_ww), // "ww"
F_(tag_thermostat_data), // ""
F_(tag_hc1), // "hc1"
F_(tag_hc2), // "hc2"
@@ -80,7 +86,7 @@ static const __FlashStringHelper * const DeviceValueTAG_mqtt[] PROGMEM = {
F_(tag_none), // ""
F_(heartbeat), // "heartbeat"
F_(tag_boiler_data_mqtt), // ""
F_(tag_device_data_ww_mqtt), // "ww"
F_(tag_boiler_data_ww_mqtt), // "ww"
F_(tag_thermostat_data), // ""
F_(tag_hc1), // "hc1"
F_(tag_hc2), // "hc2"
@@ -118,7 +124,7 @@ const std::string EMSdevice::tag_to_mqtt(uint8_t tag) {
}
const std::string EMSdevice::uom_to_string(uint8_t uom) {
if (uom == DeviceValueUOM::NONE || uom >= DeviceValueUOM::PUMP) {
if (uom == DeviceValueUOM::NONE) {
return std::string{};
}
return uuid::read_flash_string(DeviceValueUOM_s[uom - 1]); // offset by 1 to account for NONE
@@ -298,7 +304,14 @@ std::string EMSdevice::to_string() const {
if (brand_ == Brand::NO_BRAND) {
snprintf_P(&str[0], str.capacity() + 1, PSTR("%s (DeviceID:0x%02X, ProductID:%d, Version:%s)"), name_.c_str(), device_id_, product_id_, version_.c_str());
} else {
snprintf_P(&str[0], str.capacity() + 1, PSTR("%s %s (DeviceID:0x%02X ProductID:%d, Version:%s)"), brand_to_string().c_str(), name_.c_str(), device_id_, product_id_, version_.c_str());
snprintf_P(&str[0],
str.capacity() + 1,
PSTR("%s %s (DeviceID:0x%02X ProductID:%d, Version:%s)"),
brand_to_string().c_str(),
name_.c_str(),
device_id_,
product_id_,
version_.c_str());
}
return str;
@@ -412,11 +425,6 @@ void EMSdevice::register_mqtt_topic(const std::string & topic, mqtt_subfunction_
Mqtt::subscribe(device_type_, topic, f);
}
// add command to library
// void EMSdevice::register_cmd(const __FlashStringHelper * cmd, cmdfunction_p f, uint8_t flag) {
// Command::add(device_type_, cmd, f, flag);
// }
// 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) {
telegram_functions_.emplace_back(telegram_type_id, telegram_type_name, fetch, f);
@@ -470,26 +478,46 @@ void EMSdevice::register_device_value(uint8_t tag,
devicevalues_.emplace_back(device_type_, tag, value_p, type, options, options_size, short_name, full_name, uom, 0, has_cmd, min, max);
}
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, int32_t min, uint32_t max) {
// function with min and max values
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,
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, FLAG_HC);
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, 0);
Command::add(device_type_, name[0], f, name[1], 0);
}
}
}
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) {
// function with no min and max values
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) {
register_device_value(tag, value_p, type, options, name, uom, f, 0, 0);
}
// 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, int32_t min, uint32_t max) {
// register_device_value(tag, value_p, type, options, name, uom, nullptr, min, max);
// }
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) {
// no command function
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) {
register_device_value(tag, value_p, type, options, name, uom, nullptr, 0, 0);
}
@@ -509,7 +537,7 @@ std::string EMSdevice::get_value_uom(const char * key) {
for (const auto & dv : devicevalues_) {
if (dv.full_name != nullptr) {
if (uuid::read_flash_string(dv.full_name) == p) {
// ignore TIME since "minutes" is already included
// ignore TIME since "minutes" is already added to the string value
if ((dv.uom == DeviceValueUOM::NONE) || (dv.uom == DeviceValueUOM::MINUTES)) {
break;
}
@@ -521,37 +549,34 @@ std::string EMSdevice::get_value_uom(const char * key) {
return std::string{}; // not found
}
// prepare array of device values, as 3 elements serialized (name, value, uom) in array to send to Web UI
// returns number of elements
bool EMSdevice::generate_values_json_web(JsonObject & json) {
// prepare array of device values used for the WebUI
// v = value, u=uom, n=name, c=cmd
void EMSdevice::generate_values_json_web(JsonObject & json) {
json["name"] = to_string_short();
JsonArray data = json.createNestedArray("data");
uint8_t num_elements = 0;
for (const auto & dv : devicevalues_) {
// ignore if full_name empty
if (dv.full_name != nullptr) {
JsonObject obj; // create the object, if needed
// handle Booleans (true, false)
if ((dv.type == DeviceValueType::BOOL) && Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) {
// see if we have options for the bool's
if (dv.options_size == 2) {
data.add(*(uint8_t *)(dv.value_p) ? dv.options[0] : dv.options[1]);
} else {
// always render booleans as on or off
data.add(*(uint8_t *)(dv.value_p) ? F_(on) : F_(off));
}
obj = data.createNestedObject();
obj["v"] = *(bool *)(dv.value_p);
}
// handle TEXT strings
else if ((dv.type == DeviceValueType::TEXT) && (Helpers::hasValue((char *)(dv.value_p)))) {
data.add((char *)(dv.value_p));
obj = data.createNestedObject();
obj["v"] = (char *)(dv.value_p);
}
// handle ENUMs
else if ((dv.type == DeviceValueType::ENUM) && Helpers::hasValue(*(uint8_t *)(dv.value_p))) {
if (*(uint8_t *)(dv.value_p) < dv.options_size) {
data.add(dv.options[*(uint8_t *)(dv.value_p)]);
obj = data.createNestedObject();
obj["v"] = dv.options[*(uint8_t *)(dv.value_p)];
}
}
@@ -562,81 +587,60 @@ bool EMSdevice::generate_values_json_web(JsonObject & json) {
// the nested if's is necessary due to the way the ArduinoJson templates are pre-processed by the compiler
uint8_t divider = (dv.options_size == 1) ? Helpers::atoint(uuid::read_flash_string(dv.options[0]).c_str()) : 0;
// INT
if ((dv.type == DeviceValueType::INT) && Helpers::hasValue(*(int8_t *)(dv.value_p))) {
if (divider) {
data.add(Helpers::round2(*(int8_t *)(dv.value_p), divider));
} else {
data.add(*(int8_t *)(dv.value_p));
}
obj = data.createNestedObject();
obj["v"] = (divider) ? Helpers::round2(*(int8_t *)(dv.value_p), divider) : *(int8_t *)(dv.value_p);
} else if ((dv.type == DeviceValueType::UINT) && Helpers::hasValue(*(uint8_t *)(dv.value_p))) {
if (divider) {
data.add(Helpers::round2(*(uint8_t *)(dv.value_p), divider));
} else {
data.add(*(uint8_t *)(dv.value_p));
}
obj = data.createNestedObject();
obj["v"] = (divider) ? Helpers::round2(*(uint8_t *)(dv.value_p), divider) : *(uint8_t *)(dv.value_p);
} else if ((dv.type == DeviceValueType::SHORT) && Helpers::hasValue(*(int16_t *)(dv.value_p))) {
if (divider) {
data.add(Helpers::round2(*(int16_t *)(dv.value_p), divider));
} else {
data.add(*(int16_t *)(dv.value_p));
}
obj = data.createNestedObject();
obj["v"] = (divider) ? Helpers::round2(*(int16_t *)(dv.value_p), divider) : *(int16_t *)(dv.value_p);
} else if ((dv.type == DeviceValueType::USHORT) && Helpers::hasValue(*(uint16_t *)(dv.value_p))) {
if (divider) {
data.add(Helpers::round2(*(uint16_t *)(dv.value_p), divider));
} else {
data.add(*(uint16_t *)(dv.value_p));
}
obj = data.createNestedObject();
obj["v"] = (divider) ? Helpers::round2(*(uint16_t *)(dv.value_p), divider) : *(uint16_t *)(dv.value_p);
} else if ((dv.type == DeviceValueType::ULONG) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
if (divider) {
data.add(Helpers::round2(*(uint32_t *)(dv.value_p), divider));
} else {
data.add(*(uint32_t *)(dv.value_p));
}
obj = data.createNestedObject();
obj["v"] = (divider) ? Helpers::round2(*(uint32_t *)(dv.value_p), divider) : *(uint32_t *)(dv.value_p);
} else if ((dv.type == DeviceValueType::TIME) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
uint32_t time_value = *(uint32_t *)(dv.value_p);
time_value = (divider) ? time_value / divider : time_value; // sometimes we need to divide by 60
char time_s[40];
snprintf_P(time_s, 40, PSTR("%d days %d hours %d minutes"), (time_value / 1440), ((time_value % 1440) / 60), (time_value % 60));
data.add(time_s);
obj = data.createNestedObject();
obj["v"] = (divider) ? time_value / divider : time_value; // sometimes we need to divide by 60
}
}
// check if we've added a data element by comparing the size
// then add the remaining elements
uint8_t sz = data.size();
if (sz > num_elements) {
// check if we've added a data element then add the remaining elements
if (obj.containsKey("v")) {
// add the unit of measure (uom)
if (dv.uom == DeviceValueUOM::MINUTES) {
data.add(nullptr); // use null for time/date
} else {
data.add(uom_to_string(dv.uom));
}
obj["u"] = dv.uom;
// add name, prefixing the tag if it exists
if ((dv.tag == DeviceValueTAG::TAG_NONE) || tag_to_string(dv.tag).empty()) {
data.add(dv.full_name);
// except if it's a BOILER which uses a tag to split the MQTT topics
if ((dv.tag == DeviceValueTAG::TAG_NONE) || tag_to_string(dv.tag).empty() || device_type_ == DeviceType::BOILER) {
obj["n"] = dv.full_name;
} else {
char name[50];
snprintf_P(name, sizeof(name), "(%s) %s", tag_to_string(dv.tag).c_str(), uuid::read_flash_string(dv.full_name).c_str());
data.add(name);
obj["n"] = name;
}
// add the name of the Command function if it exists
if (dv.has_cmd) {
data.add(dv.short_name);
if (dv.tag >= DeviceValueTAG::TAG_HC1) {
obj["c"] = tag_to_string(dv.tag) + "/" + uuid::read_flash_string(dv.short_name);
} else {
obj["c"] = dv.short_name;
}
} else {
data.add("");
obj["c"] = "";
}
num_elements = sz + 3; // increase count by 3
}
}
}
return (num_elements != 0);
}
// builds json with specific device value information
// e.g. http://ems-esp/api?device=thermostat&cmd=seltemp
bool EMSdevice::get_value_info(JsonObject & root, const char * cmd, const int8_t id) {
JsonObject json = root;
int8_t tag = id;
@@ -646,6 +650,8 @@ bool EMSdevice::get_value_info(JsonObject & root, const char * cmd, const int8_t
tag = DeviceValueTAG::TAG_HC1 + id - 1;
} else if (id >= 8 && id <= 11) {
tag = DeviceValueTAG::TAG_WWC1 + id - 8;
} else if (id != -1) {
return false;
}
// search device value with this tag
@@ -657,10 +663,20 @@ bool EMSdevice::get_value_info(JsonObject & root, const char * cmd, const int8_t
const char * max = "max";
const char * value = "value";
json["name"] = dv.short_name;
json["name"] = dv.short_name;
// prefix tag if it's included
if (dv.full_name != nullptr) {
if (dv.tag >= DeviceValueTAG::TAG_HC1) {
json["fullname"] = tag_to_string(dv.tag) + " " + uuid::read_flash_string(dv.full_name);
} else {
json["fullname"] = dv.full_name;
}
}
if (!tag_to_mqtt(dv.tag).empty()) {
json["circuit"] = tag_to_mqtt(dv.tag);
}
switch (dv.type) {
case DeviceValueType::ENUM: {
if (Helpers::hasValue((uint8_t)(*(uint8_t *)(dv.value_p)))) {
@@ -670,16 +686,17 @@ bool EMSdevice::get_value_info(JsonObject & root, const char * cmd, const int8_t
json[value] = dv.options[*(uint8_t *)(dv.value_p)]; // text
}
}
json[type] = F_(enum);
uint8_t min_ = (uuid::read_flash_string(dv.options[0]) == "") ? 1 : 0;
json[min] = min_;
json[max] = dv.options_size - 1;
json[type] = F_(enum);
// uint8_t min_ = (uuid::read_flash_string(dv.options[0]) == "") ? 1 : 0;
// json[min] = min_;
// json[max] = dv.options_size - 1;
JsonArray enum_ = json.createNestedArray(F_(enum));
for (uint8_t i = min_; i < dv.options_size; i++) {
for (uint8_t i = 0; i < dv.options_size; i++) {
enum_.add(dv.options[i]);
}
break;
}
case DeviceValueType::USHORT:
if (Helpers::hasValue(*(uint16_t *)(dv.value_p))) {
json[value] = Helpers::round2(*(uint16_t *)(dv.value_p), divider);
@@ -688,6 +705,7 @@ bool EMSdevice::get_value_info(JsonObject & root, const char * cmd, const int8_t
json[min] = 0;
json[max] = divider ? EMS_VALUE_USHORT_NOTSET / divider : EMS_VALUE_USHORT_NOTSET;
break;
case DeviceValueType::UINT:
if (Helpers::hasValue(*(uint8_t *)(dv.value_p))) {
json[value] = Helpers::round2(*(uint8_t *)(dv.value_p), divider);
@@ -700,6 +718,7 @@ bool EMSdevice::get_value_info(JsonObject & root, const char * cmd, const int8_t
json[max] = divider ? EMS_VALUE_UINT_NOTSET / divider : EMS_VALUE_UINT_NOTSET;
}
break;
case DeviceValueType::SHORT:
if (Helpers::hasValue(*(int16_t *)(dv.value_p))) {
json[value] = Helpers::round2(*(int16_t *)(dv.value_p), divider);
@@ -708,6 +727,7 @@ bool EMSdevice::get_value_info(JsonObject & root, const char * cmd, const int8_t
json[min] = divider ? -EMS_VALUE_SHORT_NOTSET / divider : -EMS_VALUE_SHORT_NOTSET;
json[max] = divider ? EMS_VALUE_SHORT_NOTSET / divider : EMS_VALUE_SHORT_NOTSET;
break;
case DeviceValueType::INT:
if (Helpers::hasValue(*(int8_t *)(dv.value_p))) {
json[value] = Helpers::round2(*(int8_t *)(dv.value_p), divider);
@@ -721,6 +741,7 @@ bool EMSdevice::get_value_info(JsonObject & root, const char * cmd, const int8_t
json[max] = divider ? EMS_VALUE_INT_NOTSET / divider : EMS_VALUE_INT_NOTSET;
}
break;
case DeviceValueType::ULONG:
if (Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
json[value] = Helpers::round2(*(uint32_t *)(dv.value_p), divider);
@@ -729,42 +750,15 @@ bool EMSdevice::get_value_info(JsonObject & root, const char * cmd, const int8_t
json[min] = 0;
json[max] = divider ? EMS_VALUE_ULONG_NOTSET / divider : EMS_VALUE_ULONG_NOTSET;
break;
case DeviceValueType::BOOL: {
if (Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) {
if (dv.options_size == 2) {
json[value] = (bool)(*(uint8_t *)(dv.value_p)) ? dv.options[0] : dv.options[1];
} else if (Mqtt::bool_format() == BOOL_FORMAT_ONOFF) {
json[value] = (bool)(*(uint8_t *)(dv.value_p)) ? F_(on) : F_(off);
} else if (Mqtt::bool_format() == BOOL_FORMAT_ONOFF_CAP) {
json[value] = (bool)(*(uint8_t *)(dv.value_p)) ? F_(ON) : F_(OFF);
} else if (Mqtt::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");
json[min] = 0;
json[max] = 1;
JsonArray enum_ = json.createNestedArray(F_(enum));
if (dv.options_size == 2) {
enum_.add(dv.options[1]);
enum_.add(dv.options[0]);
} else if (Mqtt::bool_format() == BOOL_FORMAT_ONOFF) {
enum_.add(F_(off));
enum_.add(F_(on));
} else if (Mqtt::bool_format() == BOOL_FORMAT_ONOFF_CAP) {
enum_.add(F_(OFF));
enum_.add(F_(ON));
} else if (Mqtt::bool_format() == BOOL_FORMAT_TRUEFALSE) {
enum_.add(false);
enum_.add(true);
} else {
enum_.add(0);
enum_.add(1);
json[value] = (bool)(*(uint8_t *)(dv.value_p)) ? true : false;
}
json[type] = F("boolean");
break;
}
case DeviceValueType::TIME:
if (Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
json[value] = (divider) ? *(uint32_t *)(dv.value_p) / divider : *(uint32_t *)(dv.value_p);
@@ -773,28 +767,35 @@ bool EMSdevice::get_value_info(JsonObject & root, const char * cmd, const int8_t
json[min] = 0;
json[max] = divider ? EMS_VALUE_ULONG_NOTSET / divider : EMS_VALUE_ULONG_NOTSET;
break;
case DeviceValueType::TEXT:
if (Helpers::hasValue((char *)(dv.value_p))) {
json[value] = (char *)(dv.value_p);
}
json[type] = F_(text);
break;
default:
json[type] = F_(unknown);
break;
}
if (dv.uom != DeviceValueUOM::NONE) {
// add uom if it's not a " " (single space)
if ((dv.uom != DeviceValueUOM::NONE) && (dv.uom != DeviceValueUOM::NUM) && (dv.uom != DeviceValueUOM::BOOLEAN)) {
json["unit"] = EMSdevice::uom_to_string(dv.uom);
}
json["writeable"] = dv.has_cmd;
// if we have individual limits, overwrite the common limits
if (dv.min != 0 || dv.max != 0) {
json[min] = dv.min;
json[max] = dv.max;
}
return true;
}
}
return false;
}
@@ -810,10 +811,11 @@ bool EMSdevice::generate_values_json(JsonObject & root, const uint8_t tag_filter
bool has_value = false;
// only show if tag is either empty (TAG_NONE) or matches a value
// and don't show if full_name is empty unless we're outputing for mqtt payloads
// for nested we use all values, dont show command only (have_cmd and no fulname)
if (((nested) || tag_filter == DeviceValueTAG::TAG_NONE || (tag_filter == dv.tag)) && (dv.full_name != nullptr || !console) && !(dv.full_name == nullptr && dv.has_cmd)) {
// for nested we use all values, dont show command only (have_cmd and no fullname)
if (((nested) || tag_filter == DeviceValueTAG::TAG_NONE || (tag_filter == dv.tag)) && (dv.full_name != nullptr || !console)
&& !(dv.full_name == nullptr && dv.has_cmd)) {
// we have a tag if it matches the filter given, and that the tag name is not empty/""
bool have_tag = ((dv.tag != tag_filter) && !tag_to_string(dv.tag).empty());
bool have_tag = ((dv.tag != tag_filter) && !tag_to_string(dv.tag).empty()) && (device_type_ != DeviceType::BOILER);
char name[80];
if (console) {
@@ -837,31 +839,26 @@ bool EMSdevice::generate_values_json(JsonObject & root, const uint8_t tag_filter
// handle Booleans (true, false)
if ((dv.type == DeviceValueType::BOOL) && Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) {
// see if we have options for the bool's
if (dv.options_size == 2 && Mqtt::bool_format() != BOOL_FORMAT_10) {
json[name] = *(uint8_t *)(dv.value_p) ? dv.options[0] : dv.options[1];
// 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
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
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)
json[name] = (bool)(*(uint8_t *)(dv.value_p)) ? true : false;
has_value = true;
} else {
// 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
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
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)
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;
}
// numerical 1 or 0
json[name] = (uint8_t)(*(uint8_t *)(dv.value_p)) ? 1 : 0;
has_value = true;
}
}
// handle TEXT strings

View File

@@ -30,17 +30,6 @@
namespace emsesp {
// Home Assistant icons (https://materialdesignicons.com/)
// MAKE_PSTR(icontemperature, "mdi:temperature-celsius")
MAKE_PSTR(icontemperature, "mdi:coolant-temperature")
MAKE_PSTR(iconpercent, "mdi:percent-outline")
MAKE_PSTR(iconfire, "mdi:fire")
MAKE_PSTR(iconfan, "mdi:fan")
MAKE_PSTR(iconflame, "mdi:flash")
MAKE_PSTR(iconvalve, "mdi:valve")
MAKE_PSTR(iconpump, "mdi:pump")
MAKE_PSTR(iconheatpump, "mdi:water-pump")
enum DeviceValueType : uint8_t {
BOOL,
INT,
@@ -57,14 +46,50 @@ enum DeviceValueType : uint8_t {
// Unit Of Measurement mapping - maps to DeviceValueUOM_s in emsdevice.cpp
// uom - also used with HA
// sequence is important!
enum DeviceValueUOM : uint8_t { NONE = 0, DEGREES, PERCENT, LMIN, KWH, WH, HOURS, MINUTES, UA, BAR, KW, PUMP };
enum DeviceValueUOM : uint8_t {
NONE = 0, // 0
DEGREES, // 1
PERCENT, // 2
LMIN, // 3
KWH, // 4
WH, // 5
HOURS, // 6
MINUTES, // 7
UA, // 8
BAR, // 9
KW, // 10
W, // 11
KB, // 12
SECONDS, // 13
DBM, // 14
NUM, // 15
BOOLEAN // 16
};
// Home Assistant icons (https://materialdesignicons.com)
// the following are used with the UOMs (unit of measurements)
MAKE_PSTR(icondegrees, "mdi:coolant-temperature") // DeviceValueUOM::DEGREES
MAKE_PSTR(iconpercent, "mdi:percent-outline") // DeviceValueUOM::PERCENT
MAKE_PSTR(icontime, "mdi:clock-outline") // DeviceValueUOM::SECONDS MINUTES & HOURS
MAKE_PSTR(iconkb, "mdi:memory") // DeviceValueUOM::KB
MAKE_PSTR(iconlmin, "mdi:water-boiler") // DeviceValueUOM::LMIN
MAKE_PSTR(iconkwh, "mdi:transmission-tower") // DeviceValueUOM::KWH & WH
MAKE_PSTR(iconua, "mdi:flash-circle") // DeviceValueUOM::UA
MAKE_PSTR(iconbar, "mdi:gauge") // DeviceValueUOM::BAR
MAKE_PSTR(iconkw, "mdi:omega") // DeviceValueUOM::KW & W
MAKE_PSTR(icondbm, "mdi:wifi-strength-2") // DeviceValueUOM::DBM
MAKE_PSTR(iconnum, "mdi:counter") // DeviceValueUOM::NUM
MAKE_PSTR(icondevice, "mdi:home-automation") // for devices in HA
// TAG mapping - maps to DeviceValueTAG_s in emsdevice.cpp
enum DeviceValueTAG : uint8_t {
TAG_NONE = 0, // wild card
TAG_HEARTBEAT,
TAG_BOILER_DATA,
TAG_DEVICE_DATA_WW,
TAG_BOILER_DATA_WW,
TAG_THERMOSTAT_DATA,
TAG_HC1,
TAG_HC2,
@@ -220,7 +245,7 @@ class EMSdevice {
std::string get_value_uom(const char * key);
bool get_value_info(JsonObject & root, const char * cmd, const int8_t id);
bool generate_values_json(JsonObject & json, const uint8_t tag_filter, const bool nested, const bool console = false);
bool generate_values_json_web(JsonObject & json);
void generate_values_json_web(JsonObject & json);
void register_device_value(uint8_t tag,
void * value_p,
@@ -232,9 +257,28 @@ class EMSdevice {
bool has_cmd,
int32_t min,
uint32_t max);
void 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, int32_t min, uint32_t max);
void 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);
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,
cmdfunction_p f,
int32_t min,
uint32_t max);
void 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);
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);

View File

@@ -39,7 +39,8 @@ WebSettingsService EMSESP::webSettingsService = WebSettingsService(&webServer, &
WebStatusService EMSESP::webStatusService = WebStatusService(&webServer, EMSESP::esp8266React.getSecurityManager());
WebDevicesService EMSESP::webDevicesService = WebDevicesService(&webServer, EMSESP::esp8266React.getSecurityManager());
WebAPIService EMSESP::webAPIService = WebAPIService(&webServer);
WebAPIService EMSESP::webAPIService = WebAPIService(&webServer, EMSESP::esp8266React.getSecurityManager());
WebLogService EMSESP::webLogService = WebLogService(&webServer, EMSESP::esp8266React.getSecurityManager());
using DeviceFlags = EMSdevice;
using DeviceType = EMSdevice::DeviceType;
@@ -90,6 +91,15 @@ void EMSESP::fetch_device_values(const uint8_t device_id) {
}
}
// for a specific EMS device type go and request data values
void EMSESP::fetch_device_values_type(const uint8_t device_type) {
for (const auto & emsdevice : emsdevices) {
if ((emsdevice) && (emsdevice->device_type() == device_type)) {
emsdevice->fetch_values();
}
}
}
// clears list of recognized devices
void EMSESP::clear_all_devices() {
// temporary removed: clearing the list causes a crash, the associated commands and mqtt should also be removed.
@@ -288,7 +298,7 @@ void EMSESP::show_ems(uuid::console::Shell & shell) {
}
// show EMS device values to the shell console
// this is the only time generate_values_json is called in verbose mode (set to true)
// generate_values_json is called in verbose mode (set to true)
void EMSESP::show_device_values(uuid::console::Shell & shell) {
if (emsdevices.empty()) {
shell.printfln(F("No EMS devices detected. Try using 'scan devices' from the ems menu."));
@@ -314,8 +324,8 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) {
shell.printf(" %s: ", key);
JsonVariant data = p.value();
shell.print(COLOR_BRIGHT_GREEN);
if (data.is<char *>()) {
shell.print(data.as<char *>());
if (data.is<const char *>()) {
shell.print(data.as<const char *>());
} else if (data.is<int>()) {
shell.print(data.as<int>());
} else if (data.is<float>()) {
@@ -447,11 +457,13 @@ void EMSESP::publish_device_values(uint8_t device_type) {
// if its a boiler, generate json for each group and publish it directly
if (device_type == DeviceType::BOILER) {
emsdevice->generate_values_json(json, DeviceValueTAG::TAG_BOILER_DATA, false);
Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_BOILER_DATA), json);
json.clear();
emsdevice->generate_values_json(json, DeviceValueTAG::TAG_DEVICE_DATA_WW, false);
Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_DEVICE_DATA_WW), json);
if (emsdevice->generate_values_json(json, DeviceValueTAG::TAG_BOILER_DATA, false)) {
Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_BOILER_DATA), json);
}
doc.clear();
if (emsdevice->generate_values_json(json, DeviceValueTAG::TAG_BOILER_DATA_WW, false)) {
Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_BOILER_DATA_WW), json);
}
need_publish = false;
}
@@ -462,14 +474,16 @@ void EMSESP::publish_device_values(uint8_t device_type) {
if (nested) {
need_publish |= emsdevice->generate_values_json(json, DeviceValueTAG::TAG_NONE, true); // nested
} else {
emsdevice->generate_values_json(json, DeviceValueTAG::TAG_THERMOSTAT_DATA, false); // not nested
Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_NONE), json);
json.clear();
if (emsdevice->generate_values_json(json, DeviceValueTAG::TAG_THERMOSTAT_DATA, false)) { // not nested
Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_NONE), json);
}
doc.clear();
for (uint8_t hc_tag = TAG_HC1; hc_tag <= DeviceValueTAG::TAG_HC4; hc_tag++) {
emsdevice->generate_values_json(json, hc_tag, false); // not nested
Mqtt::publish(Mqtt::tag_to_topic(device_type, hc_tag), json);
json.clear();
if (emsdevice->generate_values_json(json, hc_tag, false)) { // not nested
Mqtt::publish(Mqtt::tag_to_topic(device_type, hc_tag), json);
}
doc.clear();
}
need_publish = false;
}
@@ -482,9 +496,10 @@ void EMSESP::publish_device_values(uint8_t device_type) {
need_publish |= emsdevice->generate_values_json(json, DeviceValueTAG::TAG_NONE, true); // nested
} else {
for (uint8_t hc_tag = TAG_HC1; hc_tag <= DeviceValueTAG::TAG_WWC4; hc_tag++) {
emsdevice->generate_values_json(json, hc_tag, false); // not nested
Mqtt::publish(Mqtt::tag_to_topic(device_type, hc_tag), json);
json.clear();
if (emsdevice->generate_values_json(json, hc_tag, false)) { // not nested
Mqtt::publish(Mqtt::tag_to_topic(device_type, hc_tag), json);
}
doc.clear();
}
need_publish = false;
}
@@ -547,6 +562,7 @@ void EMSESP::publish_response(std::shared_ptr<const Telegram> telegram) {
Mqtt::publish(F_(response), doc.as<JsonObject>());
}
// builds json with the detail of each value, for all specific device type or the dallas sensor
bool EMSESP::get_device_value_info(JsonObject & root, const char * cmd, const int8_t id, const uint8_t devicetype) {
for (const auto & emsdevice : emsdevices) {
if (emsdevice->device_type() == devicetype) {
@@ -765,7 +781,8 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
}
read_next_ = false;
} 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_)))) {
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());
} else if (!trace_raw_) {
LOG_TRACE(pretty_telegram(telegram).c_str());
@@ -806,7 +823,8 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
found = emsdevice->handle_telegram(telegram);
// if we correctly processes the telegram follow up with sending it via MQTT if needed
if (found && Mqtt::connected()) {
if ((mqtt_.get_publish_onchange(emsdevice->device_type()) && emsdevice->has_update()) || (telegram->type_id == publish_id_ && telegram->dest == txservice_.ems_bus_id())) {
if ((mqtt_.get_publish_onchange(emsdevice->device_type()) && emsdevice->has_update())
|| (telegram->type_id == publish_id_ && telegram->dest == txservice_.ems_bus_id())) {
if (telegram->type_id == publish_id_) {
publish_id_ = 0;
}
@@ -824,7 +842,8 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
if (watch() == WATCH_UNKNOWN) {
LOG_NOTICE(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)) {
if (first_scan_done_ && !knowndevice && (telegram->src != EMSbus::ems_bus_id()) && (telegram->src != 0x0B) && (telegram->src != 0x0C)
&& (telegram->src != 0x0D)) {
send_read_request(EMSdevice::EMS_TYPE_VERSION, telegram->src);
}
}
@@ -920,7 +939,8 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::
// sometimes boilers share the same product id as controllers
// so only add boilers if the device_id is 0x08, which is fixed for EMS
if (device.device_type == DeviceType::BOILER) {
if (device_id == EMSdevice::EMS_DEVICE_ID_BOILER || (device_id >= EMSdevice::EMS_DEVICE_ID_BOILER_1 && device_id <= EMSdevice::EMS_DEVICE_ID_BOILER_F)) {
if (device_id == EMSdevice::EMS_DEVICE_ID_BOILER
|| (device_id >= EMSdevice::EMS_DEVICE_ID_BOILER_1 && device_id <= EMSdevice::EMS_DEVICE_ID_BOILER_F)) {
device_p = &device;
break;
}
@@ -936,7 +956,8 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::
if (device_p == nullptr) {
LOG_NOTICE(F("Unrecognized EMS device (device ID 0x%02X, product ID %d). Please report on GitHub."), device_id, product_id);
std::string name("unknown");
emsdevices.push_back(EMSFactory::add(DeviceType::GENERIC, device_id, product_id, version, name, DeviceFlags::EMS_DEVICE_FLAG_NONE, EMSdevice::Brand::NO_BRAND));
emsdevices.push_back(
EMSFactory::add(DeviceType::GENERIC, device_id, product_id, version, name, DeviceFlags::EMS_DEVICE_FLAG_NONE, EMSdevice::Brand::NO_BRAND));
return false; // not found
}
@@ -949,32 +970,56 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::
fetch_device_values(device_id); // go and fetch its data
// add info command, but not for all devices
// add info commands, but not for all devices
if ((device_type == DeviceType::CONNECT) || (device_type == DeviceType::CONTROLLER) || (device_type == DeviceType::GATEWAY)) {
return true;
}
Command::add_with_json(device_type, F_(info), [device_type](const char * value, const int8_t id, JsonObject & json) { return command_info(device_type, json, id); });
Command::add_with_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(
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(
device_type,
F_(commands),
[device_type](const char * value, const int8_t id, JsonObject & json) { return command_commands(device_type, json, id); },
F_(commands_cmd));
return true;
}
// list all available commands, return as json
bool EMSESP::command_commands(uint8_t device_type, JsonObject & json, const int8_t id) {
return Command::list(device_type, json);
}
// export all values to info command
// value is ignored here
bool EMSESP::command_info(uint8_t device_type, JsonObject & json, const int8_t id) {
// info command always shows in verbose mode, so full names are displayed
bool EMSESP::command_info(uint8_t device_type, JsonObject & json, const int8_t id, bool verbose) {
bool has_value = false;
uint8_t tag;
if (id >= 1 && id <= 4) {
tag = DeviceValueTAG::TAG_HC1 + id - 1;
} else if (id >= 9 && id <= 10) {
tag = DeviceValueTAG::TAG_WWC1 + id - 9;
} else {
} else if (id == -1 || id == 0) {
tag = DeviceValueTAG::TAG_NONE;
} else {
return false;
}
for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_type) && ((device_type != DeviceType::THERMOSTAT) || (emsdevice->device_id() == EMSESP::actual_master_thermostat()))) {
has_value |= emsdevice->generate_values_json(json, tag, (id < 1), (id == -1)); // nested for id -1,0 & console for id -1
if (emsdevice && (emsdevice->device_type() == device_type)
&& ((device_type != DeviceType::THERMOSTAT) || (emsdevice->device_id() == EMSESP::actual_master_thermostat()))) {
has_value |= emsdevice->generate_values_json(json, tag, (id < 1), verbose && (id == -1)); // nested for id -1,0 & console for id -1
}
}
@@ -987,7 +1032,12 @@ void EMSESP::send_read_request(const uint16_t type_id, const uint8_t dest, const
}
// sends write request
void EMSESP::send_write_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid) {
void EMSESP::send_write_request(const uint16_t type_id,
const uint8_t dest,
const uint8_t offset,
uint8_t * message_data,
const uint8_t message_length,
const uint16_t validate_typeid) {
txservice_.add(Telegram::Operation::TX_WRITE, dest, type_id, offset, message_data, message_length, validate_typeid, true);
}
@@ -1018,6 +1068,8 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
// get_uptime is only updated once per loop, does not give the right time
LOG_TRACE(F("[UART_DEBUG] Echo after %d ms: %s"), ::millis() - rx_time_, Helpers::data_to_hex(data, length).c_str());
#endif
// add to RxQueue for log/watch
rxservice_.add(data, length);
return; // it's an echo
}
@@ -1053,7 +1105,7 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
tx_successful = true;
// if telegram is longer read next part with offset + 25 for ems+
if (length == 32) {
if (txservice_.read_next_tx() == read_id_) {
if (txservice_.read_next_tx(data[3]) == read_id_) {
read_next_ = true;
}
}
@@ -1158,11 +1210,12 @@ void EMSESP::start() {
// main loop calling all services
void EMSESP::loop() {
esp8266React.loop(); // web
esp8266React.loop(); // web services
system_.loop(); // does LED and checks system health, and syslog service
// if we're doing an OTA upload, skip MQTT and EMS
if (!system_.upload_status()) {
webLogService.loop(); // log in Web UI
rxservice_.loop(); // process any incoming Rx telegrams
shower_.loop(); // check for shower on/off
dallassensor_.loop(); // read dallas sensor temperatures

View File

@@ -35,10 +35,11 @@
#include <ESP8266React.h>
#include "WebStatusService.h"
#include "WebDevicesService.h"
#include "WebSettingsService.h"
#include "WebAPIService.h"
#include "web/WebStatusService.h"
#include "web/WebDevicesService.h"
#include "web/WebSettingsService.h"
#include "web/WebAPIService.h"
#include "web/WebLogService.h"
#include "emsdevice.h"
#include "emsfactory.h"
@@ -93,7 +94,12 @@ class EMSESP {
static std::string pretty_telegram(std::shared_ptr<const Telegram> telegram);
static void send_read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset = 0, const uint8_t length = 0);
static void send_write_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid);
static void send_write_request(const uint16_t type_id,
const uint8_t dest,
const uint8_t offset,
uint8_t * message_data,
const uint8_t message_length,
const uint16_t validate_typeid);
static void send_write_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t value);
static void send_write_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid);
@@ -126,6 +132,10 @@ class EMSESP {
return (!(dallassensor_.sensors().empty()));
}
static uint32_t sensor_reads() {
return dallassensor_.reads();
}
static uint32_t sensor_fails() {
return dallassensor_.fails();
}
@@ -173,6 +183,7 @@ class EMSESP {
}
static void fetch_device_values(const uint8_t device_id = 0);
static void fetch_device_values_type(const uint8_t device_type);
static bool add_device(const uint8_t device_id, const uint8_t product_id, std::string & version, const uint8_t brand);
static void scan_devices();
@@ -195,6 +206,7 @@ class EMSESP {
static WebStatusService webStatusService;
static WebDevicesService webDevicesService;
static WebAPIService webAPIService;
static WebLogService webLogService;
static uuid::log::Logger logger();
@@ -209,7 +221,8 @@ class EMSESP {
static void process_version(std::shared_ptr<const Telegram> telegram);
static void publish_response(std::shared_ptr<const Telegram> telegram);
static void publish_all_loop();
static bool command_info(uint8_t device_type, JsonObject & json, const int8_t id);
static bool command_info(uint8_t device_type, JsonObject & json, const int8_t id, bool verbose = true);
static bool command_commands(uint8_t device_type, JsonObject & json, const int8_t id);
static constexpr uint32_t EMS_FETCH_FREQUENCY = 60000; // check every minute
static uint32_t last_fetch_;

View File

@@ -24,6 +24,8 @@
#include "version.h"
#include "default_settings.h"
#include <uuid/log.h>
// forward declarators
// used to bind EMS-ESP functions to external frameworks
namespace emsesp {

View File

@@ -478,7 +478,8 @@ bool Helpers::value2enum(const char * v, uint8_t & value, const __FlashStringHel
std::string str = toLower(v);
for (value = 0; strs[value]; value++) {
std::string str1 = toLower(uuid::read_flash_string(strs[value]));
if ((str1 == uuid::read_flash_string(F_(off)) && str == "false") || (str1 == uuid::read_flash_string(F_(on)) && str == "true") || (str == str1) || (v[0] == ('0' + value) && v[1] == '\0')) {
if ((str1 == uuid::read_flash_string(F_(off)) && str == "false") || (str1 == uuid::read_flash_string(F_(on)) && str == "true") || (str == str1)
|| (v[0] == ('0' + value) && v[1] == '\0')) {
return true;
}
}

View File

@@ -18,6 +18,8 @@
#pragma once
/* spell-checker: disable */
// common words
MAKE_PSTR_WORD(debug)
MAKE_PSTR_WORD(exit)
@@ -80,6 +82,7 @@ MAKE_PSTR_WORD(command)
MAKE_PSTR_WORD(commands)
MAKE_PSTR_WORD(info)
MAKE_PSTR_WORD(settings)
MAKE_PSTR_WORD(value)
// devices
MAKE_PSTR_WORD(boiler)
@@ -94,6 +97,7 @@ MAKE_PSTR_WORD(heatpump)
MAKE_PSTR_WORD(generic)
MAKE_PSTR_WORD(dallassensor)
MAKE_PSTR_WORD(unknown)
MAKE_PSTR_WORD(Dallassensor)
// format strings
MAKE_PSTR(EMSESP, "EMS-ESP")
@@ -110,7 +114,7 @@ MAKE_PSTR(tx_mode_fmt, "Tx mode: %d")
MAKE_PSTR(bus_id_fmt, "Bus ID: %02X")
MAKE_PSTR(log_level_fmt, "Log level: %s")
//strings
// strings
MAKE_PSTR(cmd_optional, "[cmd]")
MAKE_PSTR(ha_optional, "[ha]")
MAKE_PSTR(deep_optional, "[deep]")
@@ -135,6 +139,10 @@ MAKE_PSTR(new_password_prompt2, "Retype new password: ")
MAKE_PSTR(password_prompt, "Password: ")
MAKE_PSTR(unset, "<unset>")
// command descriptions
MAKE_PSTR(info_cmd, "list all values")
MAKE_PSTR(commands_cmd, "list all commands")
MAKE_PSTR_WORD(number)
MAKE_PSTR_WORD(enum)
MAKE_PSTR_WORD(text)
@@ -150,7 +158,7 @@ MAKE_PSTR_LIST(div100, F_(100))
MAKE_PSTR_LIST(div60, F_(60))
// Unit Of Measurement mapping - maps to DeviceValueUOM_s in emsdevice.cpp
// uom - also used with HA
// uom - also used with HA see https://github.com/home-assistant/core/blob/d7ac4bd65379e11461c7ce0893d3533d8d8b8cbf/homeassistant/const.py#L384
MAKE_PSTR(percent, "%")
MAKE_PSTR(degrees, "°C")
MAKE_PSTR(kwh, "kWh")
@@ -161,13 +169,20 @@ MAKE_PSTR(hours, "hours")
MAKE_PSTR(ua, "uA")
MAKE_PSTR(lmin, "l/min")
MAKE_PSTR(kw, "kW")
MAKE_PSTR(w, "W")
MAKE_PSTR(kb, "KB")
MAKE_PSTR(seconds, "seconds")
MAKE_PSTR(dbm, "dBm")
MAKE_PSTR(num, " ") // this is hack so HA renders numbers correctly
MAKE_PSTR(bool, " ") // 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
// tags must not have spaces
MAKE_PSTR(tag_none, "")
MAKE_PSTR(tag_heartbeat, "")
MAKE_PSTR(tag_boiler_data, "")
MAKE_PSTR(tag_device_data_ww, "warm water")
MAKE_PSTR(tag_boiler_data_ww, "ww")
MAKE_PSTR(tag_thermostat_data, "")
MAKE_PSTR(tag_hc1, "hc1")
MAKE_PSTR(tag_hc2, "hc2")
@@ -195,9 +210,8 @@ MAKE_PSTR(tag_hs15, "hs15")
MAKE_PSTR(tag_hs16, "hs16")
// MQTT topic names
// MAKE_PSTR(tag_heartbeat_mqtt, "heartbeat")
MAKE_PSTR(tag_boiler_data_mqtt, "")
MAKE_PSTR(tag_device_data_ww_mqtt, "ww")
MAKE_PSTR(tag_boiler_data_ww_mqtt, "ww")
// boiler
MAKE_PSTR_WORD(time)
@@ -208,7 +222,7 @@ MAKE_PSTR_WORD(3x3min)
MAKE_PSTR_WORD(4x3min)
MAKE_PSTR_WORD(5x3min)
MAKE_PSTR_WORD(6x3min)
MAKE_PSTR_WORD(continuos)
MAKE_PSTR_WORD(continuous)
MAKE_PSTR(3wayvalve, "3-way valve")
MAKE_PSTR(chargepump, "charge pump")
MAKE_PSTR_WORD(hot)
@@ -223,8 +237,8 @@ MAKE_PSTR_WORD(error)
// boiler lists
MAKE_PSTR_LIST(enum_off_time_date, F_(off), F_(time), F_(date))
MAKE_PSTR_LIST(enum_freq, F_(off), F_(1x3min), F_(2x3min), F_(3x3min), F_(4x3min), F_(5x3min), F_(6x3min), F_(continuos))
MAKE_PSTR_LIST(enum_charge, F_(3wayvalve), F_(chargepump))
MAKE_PSTR_LIST(enum_freq, F_(off), F_(1x3min), F_(2x3min), F_(3x3min), F_(4x3min), F_(5x3min), F_(6x3min), F_(continuous))
MAKE_PSTR_LIST(enum_charge, F_(chargepump), F_(3wayvalve))
MAKE_PSTR_LIST(enum_comfort, F_(hot), F_(eco), F_(intelligent))
MAKE_PSTR_LIST(enum_flow, F_(off), F_(flow), F_(bufferedflow), F_(buffer), F_(layeredbuffer))
MAKE_PSTR_LIST(enum_reset, F_(maintenance), F_(error))
@@ -283,7 +297,16 @@ MAKE_PSTR(functioning_mode, "functioning mode")
MAKE_PSTR(smoke_temperature, "smoke temperature")
// thermostat lists
MAKE_PSTR_LIST(enum_ibaMainDisplay, F_(internal_temperature), F_(internal_setpoint), F_(external_temperature), F_(burner_temperature), F_(ww_temperature), F_(functioning_mode), F_(time), F_(date), F_(smoke_temperature))
MAKE_PSTR_LIST(enum_ibaMainDisplay,
F_(internal_temperature),
F_(internal_setpoint),
F_(external_temperature),
F_(burner_temperature),
F_(ww_temperature),
F_(functioning_mode),
F_(time),
F_(date),
F_(smoke_temperature))
MAKE_PSTR_LIST(enum_ibaLanguage, F_(german), F_(dutch), F_(french), F_(italian))
MAKE_PSTR_LIST(enum_floordrystatus, F_(off), F_(start), F_(heat), F_(hold), F_(cool), F_(end))
MAKE_PSTR_LIST(enum_ibaBuildingType, F_(blank), F_(light), F_(medium), F_(heavy)) // RC300
@@ -295,11 +318,11 @@ MAKE_PSTR_LIST(enum_wwCircMode2, F_(off), F_(on), F_(auto))
MAKE_PSTR_LIST(enum_heatingtype, F_(off), F_(radiator), F_(convector), F_(floor))
MAKE_PSTR_LIST(enum_summermode, F_(summer), F_(auto), F_(winter))
MAKE_PSTR_LIST(enum_mode, F_(manual), F_(auto))
MAKE_PSTR_LIST(enum_mode2, F_(off), F_(manual), F_(auto))
MAKE_PSTR_LIST(enum_mode3, F_(night), F_(day), F_(auto))
MAKE_PSTR_LIST(enum_mode4, F_(blank), F_(manual), F_(auto), F_(holiday))
MAKE_PSTR_LIST(enum_mode5, F_(auto), F_(off))
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_modetype, F_(eco), F_(comfort))
MAKE_PSTR_LIST(enum_modetype2, F_(day))
@@ -316,19 +339,21 @@ MAKE_PSTR_LIST(enum_control, F_(off), F_(rc20), F_(rc3x))
MAKE_PSTR_LIST(enum_hamode, F_(off), F_(heat), F_(auto), F_(heat), F_(off), F_(heat), F_(auto), F_(auto), F_(auto), F_(auto))
/*
* MQTT topics and full text for values and commands
*/
// solar list
MAKE_PSTR_LIST(enum_solarmode, F_(constant), F("pwm"), F("analog"))
MAKE_PSTR_LIST(enum_collectortype, F_(blank), F("flat"), F("vacuum"))
// MQTT topics and full text for values and commands
MAKE_PSTR(homeassistant, "homeassistant/")
// id for all devices
// id used to store the device ID, goes into MQTT payload
// empty full name to prevent being shown in web or console
MAKE_PSTR_LIST(ID, F_(id))
// Boiler
// extra commands, no output
MAKE_PSTR_LIST(wwtapactivated, F("wwtapactivated"))
MAKE_PSTR_LIST(reset, F("reset"))
MAKE_PSTR_LIST(wwtapactivated, F("wwtapactivated"), F("turn on/off DHW by going into maintenance mode"))
MAKE_PSTR_LIST(reset, F("reset"), F("reset messages"))
// single mqtt topics
MAKE_PSTR_WORD(heating_active)
@@ -347,7 +372,7 @@ MAKE_PSTR_LIST(curFlowTemp, F("curflowtemp"), F("current flow temperature"))
MAKE_PSTR_LIST(retTemp, F("rettemp"), F("return temperature"))
MAKE_PSTR_LIST(switchTemp, F("switchtemp"), F("mixing switch temperature"))
MAKE_PSTR_LIST(sysPress, F("syspress"), F("system pressure"))
MAKE_PSTR_LIST(boilTemp, F("boiltemp"), F("max boiler temperature"))
MAKE_PSTR_LIST(boilTemp, F("boiltemp"), F("boiler temperature"))
MAKE_PSTR_LIST(exhaustTemp, F("exhausttemp"), F("exhaust temperature"))
MAKE_PSTR_LIST(burnGas, F("burngas"), F("gas"))
MAKE_PSTR_LIST(flameCurr, F("flamecurr"), F("flame current"))
@@ -367,7 +392,7 @@ MAKE_PSTR_LIST(boilHystOff, F("boilhystoff"), F("hysteresis off temperature"))
MAKE_PSTR_LIST(setFlowTemp, F("setflowtemp"), F("set flow temperature"))
MAKE_PSTR_LIST(setBurnPow, F("setburnpow"), F("burner set power"))
MAKE_PSTR_LIST(curBurnPow, F("curburnpow"), F("burner current power"))
MAKE_PSTR_LIST(burnStarts, F("burnstarts"), F("burner # starts"))
MAKE_PSTR_LIST(burnStarts, F("burnstarts"), F("# burner starts"))
MAKE_PSTR_LIST(burnWorkMin, F("burnworkmin"), F("total burner operating time"))
MAKE_PSTR_LIST(heatWorkMin, F("heatworkmin"), F("total heat operating time"))
MAKE_PSTR_LIST(UBAuptime, F("ubauptime"), F("total UBA operating time"))
@@ -378,7 +403,6 @@ MAKE_PSTR_LIST(maintenanceMessage, F("maintenancemessage"), F("maintenance messa
MAKE_PSTR_LIST(maintenanceDate, F("maintenancedate"), F("maintenance set date"))
MAKE_PSTR_LIST(maintenanceType, F_(maintenance), F("maintenance scheduled"))
MAKE_PSTR_LIST(maintenanceTime, F("maintenancetime"), F("maintenance set time"))
MAKE_PSTR_LIST(upTimeControl, F("uptimecontrol"), F("operating time total heat"))
MAKE_PSTR_LIST(upTimeCompHeating, F("uptimecompheating"), F("operating time compressor heating"))
MAKE_PSTR_LIST(upTimeCompCooling, F("uptimecompcooling"), F("operating time compressor cooling"))
@@ -400,7 +424,7 @@ MAKE_PSTR_LIST(auxElecHeatNrgConsWW, F("auxelecheatnrgconsww"), F("auxiliary ele
MAKE_PSTR_LIST(hpPower, F("hppower"), F("heatpump power"))
MAKE_PSTR_LIST(hpTc0, F("hptc0"), F("water temperature condenser inlet (TC0)"))
MAKE_PSTR_LIST(hpTc1, F("hptc1"), F("water temperture condenser output (TC1)"))
MAKE_PSTR_LIST(hpTc1, F("hptc1"), F("water temperature condenser output (TC1)"))
MAKE_PSTR_LIST(hpTc3, F("hptc3"), F("condenser temperature (TC3)"))
MAKE_PSTR_LIST(hpTr3, F("hptr3"), F("refrigerant temperature liquid side (condenser output) (TR3)"))
MAKE_PSTR_LIST(hpTr4, F("hptr4"), F("evaporator inlet temperature (TR4)"))
@@ -411,39 +435,39 @@ MAKE_PSTR_LIST(hpTl2, F("hptl2"), F("air inlet temperature (TL2)"))
MAKE_PSTR_LIST(hpPl1, F("hppl1"), F("low pressure side temperature (PL1)"))
MAKE_PSTR_LIST(hpPh1, F("hpph1"), F("high pressure side temperature (PH1)"))
MAKE_PSTR_LIST(wWSelTemp, F("wwseltemp"), F("selected temperature"))
MAKE_PSTR_LIST(wWSetTemp, F("wwsettemp"), F("set temperature"))
MAKE_PSTR_LIST(wWType, F("wwtype"), F("type"))
MAKE_PSTR_LIST(wWComfort, F("wwcomfort"), F("comfort"))
MAKE_PSTR_LIST(wWFlowTempOffset, F("wwflowtempoffset"), F("flow temperature offset"))
MAKE_PSTR_LIST(wWMaxPower, F("wwmaxpower"), F("max power"))
MAKE_PSTR_LIST(wWCircPump, F("wwcircpump"), F("circulation pump available"))
MAKE_PSTR_LIST(wWChargeType, F("wwchargetype"), F("charging type"))
MAKE_PSTR_LIST(wWDisinfectionTemp, F("wwdisinfectiontemp"), F("disinfection temperature"))
MAKE_PSTR_LIST(wWCircMode, F("wwcircmode"), F("circulation pump freq"))
MAKE_PSTR_LIST(wWCirc, F("wwcirc"), F("circulation active"))
MAKE_PSTR_LIST(wWCurTemp, F("wwcurtemp"), F("current intern temperature"))
MAKE_PSTR_LIST(wWCurTemp2, F("wwcurtemp2"), F("current extern temperature"))
MAKE_PSTR_LIST(wWCurFlow, F("wwcurflow"), F("current tap water flow"))
MAKE_PSTR_LIST(wWStorageTemp1, F("wwstoragetemp1"), F("storage intern temperature"))
MAKE_PSTR_LIST(wWStorageTemp2, F("wwstoragetemp2"), F("storage extern temperature"))
MAKE_PSTR_LIST(wWActivated, F("wwactivated"), F("activated"))
MAKE_PSTR_LIST(wWOneTime, F("wwonetime"), F("one time charging"))
MAKE_PSTR_LIST(wWDisinfecting, F("wwdisinfecting"), F("disinfecting"))
MAKE_PSTR_LIST(wWCharging, F("wwcharging"), F("charging"))
MAKE_PSTR_LIST(wWRecharging, F("wwrecharging"), F("recharging"))
MAKE_PSTR_LIST(wWTempOK, F("wwtempok"), F("temperature ok"))
MAKE_PSTR_LIST(wWActive, F("wwactive"), F("active"))
MAKE_PSTR_LIST(wWHeat, F("wwheat"), F("heating"))
MAKE_PSTR_LIST(wWSetPumpPower, F("wwsetpumppower"), F("pump set power"))
MAKE_PSTR_LIST(mixerTemp, F("mixertemp"), F("mixer temperature"))
MAKE_PSTR_LIST(tankMiddleTemp, F("tankmiddletemp"), F("tank middle temperature (TS3)"))
MAKE_PSTR_LIST(wWStarts, F("wwstarts"), F("# starts"))
MAKE_PSTR_LIST(wWStarts2, F("wwstarts2"), F("# control starts"))
MAKE_PSTR_LIST(wWWorkM, F("wwworkm"), F("active time"))
MAKE_PSTR_LIST(wWSelTemp, F("wwseltemp"), F("ww selected temperature"))
MAKE_PSTR_LIST(wWSetTemp, F("wwsettemp"), F("ww set temperature"))
MAKE_PSTR_LIST(wWType, F("wwtype"), F("ww type"))
MAKE_PSTR_LIST(wWComfort, F("wwcomfort"), F("ww comfort"))
MAKE_PSTR_LIST(wWFlowTempOffset, F("wwflowtempoffset"), F("ww flow temperature offset"))
MAKE_PSTR_LIST(wWMaxPower, F("wwmaxpower"), F("ww max power"))
MAKE_PSTR_LIST(wWCircPump, F("wwcircpump"), F("ww circulation pump available"))
MAKE_PSTR_LIST(wWChargeType, F("wwchargetype"), F("ww charging type"))
MAKE_PSTR_LIST(wWDisinfectionTemp, F("wwdisinfectiontemp"), F("ww disinfection temperature"))
MAKE_PSTR_LIST(wWCircMode, F("wwcircmode"), F("ww circulation pump frequency"))
MAKE_PSTR_LIST(wWCirc, F("wwcirc"), F("ww circulation active"))
MAKE_PSTR_LIST(wWCurTemp, F("wwcurtemp"), F("ww current intern temperature"))
MAKE_PSTR_LIST(wWCurTemp2, F("wwcurtemp2"), F("ww current extern temperature"))
MAKE_PSTR_LIST(wWCurFlow, F("wwcurflow"), F("ww current tap water flow"))
MAKE_PSTR_LIST(wWStorageTemp1, F("wwstoragetemp1"), F("ww storage intern temperature"))
MAKE_PSTR_LIST(wWStorageTemp2, F("wwstoragetemp2"), F("ww storage extern temperature"))
MAKE_PSTR_LIST(wWActivated, F("wwactivated"), F("ww activated"))
MAKE_PSTR_LIST(wWOneTime, F("wwonetime"), F("ww one time charging"))
MAKE_PSTR_LIST(wWDisinfecting, F("wwdisinfecting"), F("ww disinfecting"))
MAKE_PSTR_LIST(wWCharging, F("wwcharging"), F("ww charging"))
MAKE_PSTR_LIST(wWRecharging, F("wwrecharging"), F("ww recharging"))
MAKE_PSTR_LIST(wWTempOK, F("wwtempok"), F("ww temperature ok"))
MAKE_PSTR_LIST(wWActive, F("wwactive"), F("ww active"))
MAKE_PSTR_LIST(wWHeat, F("wwheat"), F("ww heating"))
MAKE_PSTR_LIST(wWSetPumpPower, F("wwsetpumppower"), F("ww set pump power"))
MAKE_PSTR_LIST(mixerTemp, F("mixertemp"), F("ww mixer temperature"))
MAKE_PSTR_LIST(tankMiddleTemp, F("tankmiddletemp"), F("ww tank middle temperature (TS3)"))
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"))
// thermostat
// extra commands, no long name, does not show on web
// 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"))
@@ -452,34 +476,27 @@ MAKE_PSTR_LIST(hamode, F("hamode"))
// mqtt values / commands
MAKE_PSTR_LIST(dateTime, F("datetime"), F("date/time"))
MAKE_PSTR_LIST(errorCode, F("errorcode"), F("error code"))
MAKE_PSTR_LIST(ibaMainDisplay, F("display"), F("display"))
MAKE_PSTR_LIST(ibaLanguage, F("language"), F("language"))
MAKE_PSTR_LIST(ibaClockOffset, F("clockoffset"), F("clock offset"))
MAKE_PSTR_LIST(ibaBuildingType, F("building"), F("building"))
MAKE_PSTR_LIST(ibaCalIntTemperature, F("intoffset"), F("offset internal temperature"))
MAKE_PSTR_LIST(ibaMinExtTemperature, F("minexttemp"), F("min external temperature"))
MAKE_PSTR_LIST(ibaMinExtTemperature, F("minexttemp"), F("minimal external temperature"))
MAKE_PSTR_LIST(tempsensor1, F("inttemp1"), F("temperature sensor 1"))
MAKE_PSTR_LIST(tempsensor2, F("inttemp2"), F("temperature sensor 2"))
MAKE_PSTR_LIST(dampedoutdoortemp, F("dampedoutdoortemp"), F("damped outdoor temperature"))
MAKE_PSTR_LIST(floordrystatus, F("floordry"), F("floor drying"))
MAKE_PSTR_LIST(dampedoutdoortemp2, F("dampedoutdoortemp"), F("damped outdoor temperature"))
MAKE_PSTR_LIST(floordrytemp, F("floordrytemp"), F("floor drying temperature"))
MAKE_PSTR_LIST(wwMode, F("wwmode"), F("mode"))
MAKE_PSTR_LIST(wwSetTemp, F("wwsettemp"), F("set temperature"))
MAKE_PSTR_LIST(wwSetTempLow, F("wwsettemplow"), F("set temperature low"))
MAKE_PSTR_LIST(wwExtra1, F("wwextra1"), F("circuit 1 extra"))
MAKE_PSTR_LIST(wwExtra2, F("wwextra2"), F("circuit 2 extra"))
MAKE_PSTR_LIST(wwMode, F("wwmode"), F("ww mode"))
MAKE_PSTR_LIST(wwSetTemp, F("wwsettemp"), F("ww set temperature"))
MAKE_PSTR_LIST(wwSetTempLow, F("wwsettemplow"), F("ww set low temperature"))
MAKE_PSTR_LIST(wwExtra1, F("wwextra1"), F("ww circuit 1 extra"))
MAKE_PSTR_LIST(wwExtra2, F("wwextra2"), F("ww circuit 2 extra"))
MAKE_PSTR_LIST(setpoint_roomTemp, F("seltemp"), F("selected room temperature"))
MAKE_PSTR_LIST(curr_roomTemp, F("currtemp"), F("current room temperature"))
MAKE_PSTR_LIST(mode, F("mode"), F("mode"))
MAKE_PSTR_LIST(modetype, F("modetype"), F("mode type"))
MAKE_PSTR_LIST(daytemp, F("daytemp"), F("day temperature"))
MAKE_PSTR_LIST(heattemp, F("heattemp"), F("heat temperature"))
MAKE_PSTR_LIST(nighttemp, F("nighttemp"), F("night temperature"))
@@ -496,7 +513,7 @@ MAKE_PSTR_LIST(roominfluence, F("roominfluence"), F("room influence"))
MAKE_PSTR_LIST(nofrosttemp, F("nofrosttemp"), F("nofrost temperature"))
MAKE_PSTR_LIST(targetflowtemp, F("targetflowtemp"), F("target flow temperature"))
MAKE_PSTR_LIST(heatingtype, F("heatingtype"), F("heating type"))
MAKE_PSTR_LIST(summersetmode, F("summersetmode"), F("summer set mode"))
MAKE_PSTR_LIST(summersetmode, F("summersetmode"), F("set summer mode"))
MAKE_PSTR_LIST(controlmode, F("controlmode"), F("control mode"))
MAKE_PSTR_LIST(control, F("control"), F("control device"))
MAKE_PSTR_LIST(program, F("program"), F("program"))
@@ -521,10 +538,9 @@ MAKE_PSTR_LIST(flowTempHc, F("flowtemphc"), F("flow temperature in assigned hc (
MAKE_PSTR_LIST(pumpStatus, F("pumpstatus"), F("pump status in assigned hc (PC1)"))
MAKE_PSTR_LIST(mixerStatus, F("valvestatus"), F("mixing valve actuator in assigned hc (VC1)"))
MAKE_PSTR_LIST(flowTempVf, F("flowtempvf"), F("flow temperature in header (T0/Vf)"))
MAKE_PSTR_LIST(wwPumpStatus, F("pumpstatus"), F("pump status in assigned wwc (PC1)"))
MAKE_PSTR_LIST(wwTempStatus, F("wwtempstatus"), F("temperature switch in assigned wwc (MC1)"))
MAKE_PSTR_LIST(wwTemp, F("wwtemp"), F("current warm water temperature"))
MAKE_PSTR_LIST(wwTemp, F("wwtemp"), F("ww current temperature"))
// solar
MAKE_PSTR_LIST(type, F("type"), F("type"))
@@ -532,28 +548,48 @@ MAKE_PSTR_LIST(collectorTemp, F("collectortemp"), F("collector temperature (TS1)
MAKE_PSTR_LIST(tankBottomTemp, F("tankbottomtemp"), F("tank bottom temperature (TS2)"))
MAKE_PSTR_LIST(tank2BottomTemp, F("tank2bottomtemp"), F("second tank bottom temperature (TS5)"))
MAKE_PSTR_LIST(heatExchangerTemp, F("heatexchangertemp"), F("heat exchanger temperature (TS6)"))
MAKE_PSTR_LIST(collectorMaxTemp, F("collectormaxtemp"), F("maximum collector temperature"))
MAKE_PSTR_LIST(collectorMinTemp, F("collectormintemp"), F("minimum collector temperature"))
MAKE_PSTR_LIST(tankMaxTemp, F("tankmaxtemp"), F("maximum tank temperature"))
MAKE_PSTR_LIST(solarPumpModulation, F("solarpumpmodulation"), F("pump modulation (PS1)"))
MAKE_PSTR_LIST(cylinderPumpModulation, F("cylinderpumpmodulation"), F("cylinder pump modulation (PS5)"))
MAKE_PSTR_LIST(solarPump, F("solarpump"), F("pump (PS1)"))
MAKE_PSTR_LIST(valveStatus, F("valvestatus"), F("valve status"))
MAKE_PSTR_LIST(tankHeated, F("tankheated"), F("tank heated"))
MAKE_PSTR_LIST(collectorShutdown, F("collectorshutdown"), F("collector shutdown"))
MAKE_PSTR_LIST(pumpWorkTime, F("pumpWorktime"), F("pump working time"))
MAKE_PSTR_LIST(pumpWorkTime, F("pumpworktime"), F("pump working time"))
MAKE_PSTR_LIST(energyLastHour, F("energylasthour"), F("energy last hour"))
MAKE_PSTR_LIST(energyTotal, F("energytotal"), F("energy total"))
MAKE_PSTR_LIST(energyToday, F("energytoday"), F("energy today"))
MAKE_PSTR_LIST(wwTemp1, F("wwtemp1"), F("ww temperature 1"))
MAKE_PSTR_LIST(wwTemp3, F("wwtemp3"), F("ww temperature 3"))
MAKE_PSTR_LIST(wwTemp4, F("wwtemp4"), F("ww temperature 4"))
MAKE_PSTR_LIST(wwTemp5, F("wwtemp5"), F("ww temperature 5"))
MAKE_PSTR_LIST(wwTemp7, F("wwtemp7"), F("ww temperature 7"))
MAKE_PSTR_LIST(wwPump, F("wwpump"), F("ww pump"))
MAKE_PSTR_LIST(wwMinTemp, F("wwmintemp"), F("ww minimum temperature"))
MAKE_PSTR_LIST(pumpMinMod, F("pumpminmod"), F("minimum pump modulation"))
MAKE_PSTR_LIST(maxFlow, F("maxflow"), F("maximum solar flow"))
MAKE_PSTR_LIST(solarPower, F("solarpower"), F("actual solar power"))
MAKE_PSTR_LIST(solarPumpTurnonDiff, F("turnondiff"), F("pump turn on difference"))
MAKE_PSTR_LIST(solarPumpTurnoffDiff, F("turnoffdiff"), F("pump turn off difference"))
MAKE_PSTR_LIST(wwTemp1, F("wwtemp1"), F("temperature 1"))
MAKE_PSTR_LIST(wwTemp3, F("wwtemp3"), F("temperature 3"))
MAKE_PSTR_LIST(wwTemp4, F("wwtemp4"), F("temperature 4"))
MAKE_PSTR_LIST(wwTemp5, F("wwtemp5"), F("temperature 5"))
MAKE_PSTR_LIST(wwTemp7, F("wwtemp7"), F("temperature 7"))
MAKE_PSTR_LIST(wwPump, F("wwpump"), F("pump"))
// Solar SM100
MAKE_PSTR_LIST(heatTransferSystem, F("heattransfersystem"), F("heattransfer system"))
MAKE_PSTR_LIST(externalTank, F("externaltank"), F("external tank"))
MAKE_PSTR_LIST(thermalDisinfect, F("thermaldisinfect"), F("thermal disinfection"))
MAKE_PSTR_LIST(heatMetering, F("heatmetering"), F("heatmetering"))
// telegram 0x035A
MAKE_PSTR_LIST(solarPumpMode, F("solarpumpmode"), F("solar pump mode"))
MAKE_PSTR_LIST(solarPumpKick, F("pumpkick"), F("pumpkick"))
MAKE_PSTR_LIST(plainWaterMode, F("plainwatermode"), F("plain water mode"))
MAKE_PSTR_LIST(doubleMatchFlow, F("doublematchflow"), F("doublematchflow"))
// telegram 0x380
MAKE_PSTR_LIST(climateZone, F("climatezone"), F("climate zone"))
MAKE_PSTR_LIST(collector1Area, F("collector1area"), F("collector 1 area"))
MAKE_PSTR_LIST(collector1Type, F("collector1type"), F("collector 1 type"))
// switch
MAKE_PSTR_LIST(activated, F("activated"), F("activated"))

View File

@@ -228,12 +228,27 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) {
}
for (const auto & cf : Command::commands()) {
if (subscribe_format_ == 2 && cf.flag_ == MqttSubFlag::FLAG_HC) {
shell.printfln(F(" %s/%s/hc1/%s"), mqtt_base_.c_str(), EMSdevice::device_type_2_device_name(cf.device_type_).c_str(), uuid::read_flash_string(cf.cmd_).c_str());
shell.printfln(F(" %s/%s/hc2/%s"), mqtt_base_.c_str(), EMSdevice::device_type_2_device_name(cf.device_type_).c_str(), uuid::read_flash_string(cf.cmd_).c_str());
shell.printfln(F(" %s/%s/hc3/%s"), mqtt_base_.c_str(), EMSdevice::device_type_2_device_name(cf.device_type_).c_str(), uuid::read_flash_string(cf.cmd_).c_str());
shell.printfln(F(" %s/%s/hc4/%s"), mqtt_base_.c_str(), EMSdevice::device_type_2_device_name(cf.device_type_).c_str(), uuid::read_flash_string(cf.cmd_).c_str());
shell.printfln(F(" %s/%s/hc1/%s"),
mqtt_base_.c_str(),
EMSdevice::device_type_2_device_name(cf.device_type_).c_str(),
uuid::read_flash_string(cf.cmd_).c_str());
shell.printfln(F(" %s/%s/hc2/%s"),
mqtt_base_.c_str(),
EMSdevice::device_type_2_device_name(cf.device_type_).c_str(),
uuid::read_flash_string(cf.cmd_).c_str());
shell.printfln(F(" %s/%s/hc3/%s"),
mqtt_base_.c_str(),
EMSdevice::device_type_2_device_name(cf.device_type_).c_str(),
uuid::read_flash_string(cf.cmd_).c_str());
shell.printfln(F(" %s/%s/hc4/%s"),
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) {
shell.printfln(F(" %s/%s/%s"), mqtt_base_.c_str(), EMSdevice::device_type_2_device_name(cf.device_type_).c_str(), uuid::read_flash_string(cf.cmd_).c_str());
shell.printfln(F(" %s/%s/%s"),
mqtt_base_.c_str(),
EMSdevice::device_type_2_device_name(cf.device_type_).c_str(),
uuid::read_flash_string(cf.cmd_).c_str());
}
}
shell.println();
@@ -265,7 +280,12 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) {
shell.printfln(F(" [%02d] (Pub) topic=%s payload=%s (pid %d)"), message.id_, topic, content->payload.c_str(), message.packet_id_);
}
} else {
shell.printfln(F(" [%02d] (Pub) topic=%s payload=%s (pid %d, retry #%d)"), message.id_, topic, content->payload.c_str(), message.packet_id_, message.retry_count_);
shell.printfln(F(" [%02d] (Pub) topic=%s payload=%s (pid %d, retry #%d)"),
message.id_,
topic,
content->payload.c_str(),
message.packet_id_,
message.retry_count_);
}
} else {
// Subscribe messages
@@ -327,15 +347,9 @@ void Mqtt::on_message(const char * fulltopic, const char * payload, size_t len)
return; // invalid topic name
}
cmd_only++; // skip the /
int8_t id = -1;
// check for hcx/ prefix, commented out, this is now in command::call
// if (cmd_only[0] == 'h' && cmd_only[1] == 'c' && cmd_only[3] == '/') {
// id = cmd_only[2] - '0';
// cmd_only += 4;
// }
// LOG_INFO(F("devicetype= %d, topic = %s, cmd = %s, message = %s, id = %d"), mf.device_type_, topic, cmd_only, message, id);
if (!Command::call(mf.device_type_, cmd_only, message, id)) {
LOG_ERROR(F("No matching cmd (%s) in topic %s, id %d, or invalid data"), cmd_only, topic, id);
// 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);
Mqtt::publish(F_(response), "unknown");
}
return;
@@ -367,8 +381,8 @@ void Mqtt::on_message(const char * fulltopic, const char * payload, size_t len)
bool cmd_known = false;
JsonVariant data = doc["data"];
if (data.is<char *>()) {
cmd_known = Command::call(mf.device_type_, command, data.as<char *>(), n);
if (data.is<const char *>()) {
cmd_known = Command::call(mf.device_type_, command, data.as<const char *>(), 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);
@@ -400,7 +414,10 @@ void Mqtt::on_message(const char * fulltopic, const char * payload, size_t len)
// print all the topics related to a specific device type
void Mqtt::show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type) {
if (std::count_if(mqtt_subfunctions_.cbegin(), mqtt_subfunctions_.cend(), [=](MQTTSubFunction const & mqtt_subfunction) { return device_type == mqtt_subfunction.device_type_; }) == 0) {
if (std::count_if(mqtt_subfunctions_.cbegin(),
mqtt_subfunctions_.cend(),
[=](MQTTSubFunction const & mqtt_subfunction) { return device_type == mqtt_subfunction.device_type_; })
== 0) {
return;
}
@@ -626,7 +643,7 @@ void Mqtt::on_connect() {
doc["ip"] = WiFi.localIP().toString();
}
#endif
publish(F_(info), doc.as<JsonObject>());
publish(F_(info), doc.as<JsonObject>()); // topic called "info"
// create the EMS-ESP device in HA, which is MQTT retained
if (ha_enabled()) {
@@ -670,6 +687,7 @@ void Mqtt::ha_status() {
// doc["json_attr_t"] = FJSON("~/heartbeat"); // store also as HA attributes
doc["stat_t"] = FJSON("~/heartbeat");
doc["name"] = FJSON("EMS-ESP status");
doc["ic"] = F_(icondevice);
doc["val_tpl"] = FJSON("{{value_json['status']}}");
JsonObject dev = doc.createNestedObject("dev");
@@ -684,18 +702,36 @@ void Mqtt::ha_status() {
snprintf_P(topic, sizeof(topic), PSTR("sensor/%s/system/config"), mqtt_base_.c_str());
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
// create the sensors
// must match the MQTT payload keys
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("WiFi strength"), EMSdevice::DeviceType::SYSTEM, F("rssi"));
// create the sensors - must match the MQTT payload keys
if (!EMSESP::system_.ethernet_connected()) {
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("WiFi RSSI"), EMSdevice::DeviceType::SYSTEM, F("rssi"), DeviceValueUOM::DBM);
publish_mqtt_ha_sensor(DeviceValueType::INT,
DeviceValueTAG::TAG_HEARTBEAT,
F("WiFi strength"),
EMSdevice::DeviceType::SYSTEM,
F("wifistrength"),
DeviceValueUOM::PERCENT);
}
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Uptime"), EMSdevice::DeviceType::SYSTEM, F("uptime"));
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Uptime (sec)"), EMSdevice::DeviceType::SYSTEM, F("uptime_sec"));
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Free memory"), EMSdevice::DeviceType::SYSTEM, F("freemem"));
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("# MQTT fails"), EMSdevice::DeviceType::SYSTEM, F("mqttfails"));
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("# Rx received"), EMSdevice::DeviceType::SYSTEM, F("rxreceived"));
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("# Rx fails"), EMSdevice::DeviceType::SYSTEM, F("rxfails"));
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("# Tx reads"), EMSdevice::DeviceType::SYSTEM, F("txread"));
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("# Tx writes"), EMSdevice::DeviceType::SYSTEM, F("txwrite"));
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("# Tx fails"), EMSdevice::DeviceType::SYSTEM, F("txfails"));
publish_mqtt_ha_sensor(DeviceValueType::INT,
DeviceValueTAG::TAG_HEARTBEAT,
F("Uptime (sec)"),
EMSdevice::DeviceType::SYSTEM,
F("uptime_sec"),
DeviceValueUOM::SECONDS);
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("Free memory"), EMSdevice::DeviceType::SYSTEM, F("freemem"), DeviceValueUOM::KB);
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("# MQTT fails"), EMSdevice::DeviceType::SYSTEM, F("mqttfails"), DeviceValueUOM::NUM);
publish_mqtt_ha_sensor(DeviceValueType::INT,
DeviceValueTAG::TAG_HEARTBEAT,
F("# Rx received"),
EMSdevice::DeviceType::SYSTEM,
F("rxreceived"),
DeviceValueUOM::NUM);
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("# Rx fails"), EMSdevice::DeviceType::SYSTEM, F("rxfails"), DeviceValueUOM::NUM);
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("# Tx reads"), EMSdevice::DeviceType::SYSTEM, F("txread"), DeviceValueUOM::NUM);
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("# Tx writes"), EMSdevice::DeviceType::SYSTEM, F("txwrite"), DeviceValueUOM::NUM);
publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_HEARTBEAT, F("# Tx fails"), EMSdevice::DeviceType::SYSTEM, F("txfails"), DeviceValueUOM::NUM);
}
// add sub or pub task to the queue.
@@ -851,7 +887,13 @@ 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)"), topic, mqtt_message.id_, message->retain, mqtt_message.retry_count_ + 1, message->payload.size(), packet_id);
LOG_DEBUG(F("Publishing topic %s (#%02d, retain=%d, try#%d, size %d, pid %d)"),
topic,
mqtt_message.id_,
message->retain,
mqtt_message.retry_count_ + 1,
message->payload.size(),
packet_id);
if (packet_id == 0) {
// it failed. if we retried n times, give up. remove from queue
@@ -883,11 +925,6 @@ void Mqtt::process_queue() {
// HA config for a sensor and binary_sensor entity
// entity must match the key/value pair in the *_data topic
// e.g. homeassistant/sensor/ems-esp32/thermostat_hc1_seltemp/config
// with:
// {"uniq_id":"thermostat_hc1_seltemp","stat_t":"ems-esp32/thermostat_data","name":"Thermostat hc1 Setpoint room temperature",
// "val_tpl":"{{value_json.hc1.seltemp}}","unit_of_meas":"°C","ic":"mdi:temperature-celsius","dev":{"ids":["ems-esp-thermostat"]}}
//
// note: some string copying here into chars, it looks messy but does help with heap fragmentation issues
void Mqtt::publish_mqtt_ha_sensor(uint8_t type, // EMSdevice::DeviceValueType
uint8_t tag, // EMSdevice::DeviceValueTAG
@@ -900,10 +937,23 @@ void Mqtt::publish_mqtt_ha_sensor(uint8_t type, // EMSdevice
return;
}
// bool have_tag = !EMSdevice::tag_to_string(tag).empty() && (device_type != EMSdevice::DeviceType::BOILER); // ignore boiler
bool have_tag = !EMSdevice::tag_to_string(tag).empty();
// nested_format is 1 if nested, otherwise 2 for single topics
bool is_nested;
if (device_type == EMSdevice::DeviceType::BOILER) {
is_nested = false; // boiler never uses nested
} else {
is_nested = (nested_format_ == 1);
}
char device_name[50];
strlcpy(device_name, EMSdevice::device_type_2_device_name(device_type).c_str(), sizeof(device_name));
DynamicJsonDocument doc(EMSESP_JSON_SIZE_HA_CONFIG);
bool have_tag = !EMSdevice::tag_to_string(tag).empty() && (device_type != EMSdevice::DeviceType::BOILER); // ignore boiler
bool is_nested = (nested_format_ == 1) || (device_type == EMSdevice::DeviceType::BOILER); // boiler never uses nested
doc["~"] = mqtt_base_;
// create entity by add the tag if present, seperating with a .
char new_entity[50];
@@ -913,10 +963,6 @@ void Mqtt::publish_mqtt_ha_sensor(uint8_t type, // EMSdevice
snprintf_P(new_entity, sizeof(new_entity), PSTR("%s"), uuid::read_flash_string(entity).c_str());
}
// device name
char device_name[50];
strlcpy(device_name, EMSdevice::device_type_2_device_name(device_type).c_str(), sizeof(device_name));
// build unique identifier which will be used in the topic
// and replacing all . with _ as not to break HA
std::string uniq(50, '\0');
@@ -924,14 +970,12 @@ void Mqtt::publish_mqtt_ha_sensor(uint8_t type, // EMSdevice
std::replace(uniq.begin(), uniq.end(), '.', '_');
doc["uniq_id"] = uniq;
doc["~"] = mqtt_base_;
// state topic
char stat_t[MQTT_TOPIC_MAX_SIZE];
snprintf_P(stat_t, sizeof(stat_t), PSTR("~/%s"), tag_to_topic(device_type, tag).c_str());
doc["stat_t"] = stat_t;
// name
// name = <device> <tag> <name>
char new_name[80];
if (have_tag) {
snprintf_P(new_name, sizeof(new_name), PSTR("%s %s %s"), device_name, EMSdevice::tag_to_string(tag).c_str(), uuid::read_flash_string(name).c_str());
@@ -942,6 +986,7 @@ void Mqtt::publish_mqtt_ha_sensor(uint8_t type, // EMSdevice
doc["name"] = new_name;
// value template
// if its nested mqtt format then use the appended entity name, otherwise take the original
char val_tpl[50];
if (is_nested) {
snprintf_P(val_tpl, sizeof(val_tpl), PSTR("{{value_json.%s}}"), new_entity);
@@ -966,18 +1011,47 @@ void Mqtt::publish_mqtt_ha_sensor(uint8_t type, // EMSdevice
snprintf_P(topic, sizeof(topic), PSTR("sensor/%s/%s/config"), mqtt_base_.c_str(), uniq.c_str()); // topic
// unit of measure and map the HA icon
if (uom != DeviceValueUOM::NONE && uom != DeviceValueUOM::PUMP) {
if (uom != DeviceValueUOM::NONE) {
doc["unit_of_meas"] = EMSdevice::uom_to_string(uom);
}
switch (uom) {
case DeviceValueUOM::DEGREES:
doc["ic"] = F_(icontemperature);
doc["ic"] = F_(icondegrees);
break;
case DeviceValueUOM::PERCENT:
doc["ic"] = F_(iconpercent);
break;
case DeviceValueUOM::PUMP:
doc["ic"] = F_(iconpump);
case DeviceValueUOM::SECONDS:
case DeviceValueUOM::MINUTES:
case DeviceValueUOM::HOURS:
doc["ic"] = F_(icontime);
break;
case DeviceValueUOM::KB:
doc["ic"] = F_(iconkb);
break;
case DeviceValueUOM::LMIN:
doc["ic"] = F_(iconlmin);
break;
case DeviceValueUOM::WH:
case DeviceValueUOM::KWH:
doc["ic"] = F_(iconkwh);
break;
case DeviceValueUOM::UA:
doc["ic"] = F_(iconua);
break;
case DeviceValueUOM::BAR:
doc["ic"] = F_(iconbar);
break;
case DeviceValueUOM::W:
case DeviceValueUOM::KW:
doc["ic"] = F_(iconkw);
break;
case DeviceValueUOM::DBM:
doc["ic"] = F_(icondbm);
break;
case DeviceValueUOM::NUM:
doc["ic"] = F_(iconnum);
break;
case DeviceValueUOM::NONE:
default:
@@ -997,7 +1071,6 @@ void Mqtt::publish_mqtt_ha_sensor(uint8_t type, // EMSdevice
ids.add(ha_device);
}
doc.shrinkToFit();
publish_ha(topic, doc.as<JsonObject>());
}

View File

@@ -106,7 +106,12 @@ class Mqtt {
static void publish_ha(const std::string & topic, const JsonObject & payload);
static void publish_ha(const __FlashStringHelper * topic, const JsonObject & payload);
static void publish_mqtt_ha_sensor(uint8_t type, uint8_t tag, const __FlashStringHelper * name, const uint8_t device_type, const __FlashStringHelper * entity, const uint8_t uom = 0);
static void publish_mqtt_ha_sensor(uint8_t type,
uint8_t tag,
const __FlashStringHelper * name,
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 show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type);
@@ -140,6 +145,10 @@ class Mqtt {
return mqtt_base_;
}
static uint16_t publish_count() {
return mqtt_message_id_;
}
static uint32_t publish_fails() {
return mqtt_publish_fails_;
}

View File

@@ -63,35 +63,55 @@ bool System::command_send(const char * value, const int8_t id) {
// fetch device values
bool System::command_fetch(const char * value, const int8_t id) {
LOG_INFO(F("Requesting data from EMS devices"));
EMSESP::fetch_device_values();
std::string value_s(14, '\0');
if (Helpers::value2string(value, value_s)) {
if (value_s == "all") {
LOG_INFO(F("Requesting data from EMS devices"));
EMSESP::fetch_device_values();
return true;
} else if (value_s == "boiler") {
EMSESP::fetch_device_values_type(EMSdevice::DeviceType::BOILER);
return true;
} else if (value_s == "thermostat") {
EMSESP::fetch_device_values_type(EMSdevice::DeviceType::THERMOSTAT);
return true;
} else if (value_s == "solar") {
EMSESP::fetch_device_values_type(EMSdevice::DeviceType::SOLAR);
return true;
} else if (value_s == "mixer") {
EMSESP::fetch_device_values_type(EMSdevice::DeviceType::MIXER);
return true;
}
}
EMSESP::fetch_device_values(); // default if no name or id is given
return true;
}
// mqtt publish
bool System::command_publish(const char * value, const int8_t id) {
std::string ha(14, '\0');
if (Helpers::value2string(value, ha)) {
if (ha == "ha") {
std::string value_s(14, '\0');
if (Helpers::value2string(value, value_s)) {
if (value_s == "ha") {
EMSESP::publish_all(true); // includes HA
LOG_INFO(F("Publishing all data to MQTT, including HA configs"));
return true;
} else if (ha == "boiler") {
} else if (value_s == "boiler") {
EMSESP::publish_device_values(EMSdevice::DeviceType::BOILER);
return true;
} else if (ha == "thermostat") {
} else if (value_s == "thermostat") {
EMSESP::publish_device_values(EMSdevice::DeviceType::THERMOSTAT);
return true;
} else if (ha == "solar") {
} else if (value_s == "solar") {
EMSESP::publish_device_values(EMSdevice::DeviceType::SOLAR);
return true;
} else if (ha == "mixer") {
} else if (value_s == "mixer") {
EMSESP::publish_device_values(EMSdevice::DeviceType::MIXER);
return true;
} else if (ha == "other") {
} else if (value_s == "other") {
EMSESP::publish_other_values();
return true;
} else if (ha == "dallassensor") {
} else if (value_s == "dallassensor") {
EMSESP::publish_sensor_values(true);
return true;
}
@@ -393,7 +413,6 @@ void System::loop() {
send_heartbeat();
}
/*
#ifndef EMSESP_STANDALONE
#if defined(EMSESP_DEBUG)
static uint32_t last_memcheck_ = 0;
@@ -403,7 +422,6 @@ void System::loop() {
}
#endif
#endif
*/
#endif
}
@@ -419,14 +437,6 @@ void System::show_mem(const char * note) {
// create the json for heartbeat
bool System::heartbeat_json(JsonObject & doc) {
int8_t rssi;
if (!ethernet_connected_) {
rssi = wifi_quality();
if (rssi == -1) {
return false;
}
}
uint8_t ems_status = EMSESP::bus_status();
if (ems_status == EMSESP::BUS_STATUS_TX_ERRORS) {
doc["status"] = FJSON("txerror");
@@ -436,12 +446,9 @@ bool System::heartbeat_json(JsonObject & doc) {
doc["status"] = FJSON("disconnected");
}
if (!ethernet_connected_) {
doc["rssi"] = rssi;
}
doc["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
doc["uptime_sec"] = uuid::get_uptime_sec();
doc["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
doc["uptime_sec"] = uuid::get_uptime_sec();
doc["rxreceived"] = EMSESP::rxservice_.telegram_count();
doc["rxfails"] = EMSESP::rxservice_.telegram_error_count();
doc["txread"] = EMSESP::txservice_.telegram_read_count();
@@ -453,15 +460,24 @@ bool System::heartbeat_json(JsonObject & doc) {
if (EMSESP::dallas_enabled()) {
doc["dallasfails"] = EMSESP::sensor_fails();
}
#ifndef EMSESP_STANDALONE
doc["freemem"] = ESP.getFreeHeap();
doc["freemem"] = ESP.getFreeHeap() / 1000L; // kilobytes
#endif
if (analog_enabled_) {
doc["adc"] = analog_;
}
return (doc.size() > 0);
#ifndef EMSESP_STANDALONE
if (!ethernet_connected_) {
int8_t rssi = WiFi.RSSI();
doc["rssi"] = rssi;
doc["wifistrength"] = wifi_quality(rssi);
}
#endif
return true;
}
// send periodic MQTT message with system information
@@ -599,14 +615,15 @@ 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, MqttSubFlag::FLAG_NOSUB);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(send), System::command_send);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(publish), System::command_publish);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), System::command_fetch);
Command::add_with_json(EMSdevice::DeviceType::SYSTEM, F_(info), System::command_info);
Command::add_with_json(EMSdevice::DeviceType::SYSTEM, F_(settings), System::command_settings);
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"));
#if defined(EMSESP_DEBUG)
Command::add(EMSdevice::DeviceType::SYSTEM, F("test"), System::command_test);
Command::add(EMSdevice::DeviceType::SYSTEM, F("test"), System::command_test, F("run tests"));
#endif
}
@@ -628,19 +645,12 @@ void System::led_monitor() {
}
}
// Return the quality (Received Signal Strength Indicator) of the WiFi network as a %. Or -1 if disconnected.
// Return the quality (Received Signal Strength Indicator) of the WiFi network as a %
// High quality: 90% ~= -55dBm
// Medium quality: 50% ~= -75dBm
// Low quality: 30% ~= -85dBm
// Unusable quality: 8% ~= -96dBm
int8_t System::wifi_quality() {
#ifdef EMSESP_STANDALONE
return 100;
#else
if (WiFi.status() != WL_CONNECTED) {
return -1;
}
int32_t dBm = WiFi.RSSI();
int8_t System::wifi_quality(int8_t dBm) {
if (dBm <= -100) {
return 0;
}
@@ -649,7 +659,6 @@ int8_t System::wifi_quality() {
return 100;
}
return 2 * (dBm + 100);
#endif
}
// print users to console
@@ -693,7 +702,7 @@ void System::show_system(uuid::console::Shell & shell) {
shell.printfln(F("WiFi: Connected"));
shell.printfln(F("SSID: %s"), WiFi.SSID().c_str());
shell.printfln(F("BSSID: %s"), WiFi.BSSIDstr().c_str());
shell.printfln(F("RSSI: %d dBm (%d %%)"), WiFi.RSSI(), wifi_quality());
shell.printfln(F("RSSI: %d dBm (%d %%)"), WiFi.RSSI(), wifi_quality(WiFi.RSSI()));
shell.printfln(F("MAC address: %s"), WiFi.macAddress().c_str());
shell.printfln(F("Hostname: %s"), WiFi.getHostname());
shell.printfln(F("IPv4 address: %s/%s"), uuid::printable_to_string(WiFi.localIP()).c_str(), uuid::printable_to_string(WiFi.subnetMask()).c_str());
@@ -757,6 +766,11 @@ bool System::check_upgrade() {
return false;
}
// list commands
bool System::command_commands(const char * value, const int8_t id, JsonObject & json) {
return Command::list(EMSdevice::DeviceType::SYSTEM, json);
}
// export all settings to JSON text
// e.g. http://ems-esp/api?device=system&cmd=settings
// value and id are ignored
@@ -767,8 +781,8 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject &
node["version"] = EMSESP_APP_VERSION;
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & settings) {
node = json.createNestedObject("WIFI");
node["ssid"] = settings.ssid;
node = json.createNestedObject("WIFI");
// node["ssid"] = settings.ssid; // commented out - people don't like others to see this
node["hostname"] = settings.hostname;
node["static_ip_config"] = settings.staticIPConfig;
JsonUtils::writeIP(node, "local_ip", settings.localIP);
@@ -833,6 +847,7 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject &
EMSESP::webSettingsService.read([&](WebSettings & settings) {
node = json.createNestedObject("Settings");
node["tx_mode"] = settings.tx_mode;
node["tx_delay"] = settings.tx_delay;
node["ems_bus_id"] = settings.ems_bus_id;
node["syslog_enabled"] = settings.syslog_enabled;
node["syslog_level"] = settings.syslog_level;
@@ -848,7 +863,7 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject &
node["dallas_parasite"] = settings.dallas_parasite;
node["led_gpio"] = settings.led_gpio;
node["hide_led"] = settings.hide_led;
node["api_enabled"] = settings.api_enabled;
node["notoken_api"] = settings.notoken_api;
node["analog_enabled"] = settings.analog_enabled;
node["pbutton_gpio"] = settings.pbutton_gpio;
node["board_profile"] = settings.board_profile;
@@ -860,11 +875,6 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject &
// export status information including some basic settings
// e.g. http://ems-esp/api?device=system&cmd=info
bool System::command_info(const char * value, const int8_t id, JsonObject & json) {
if (id == 0) {
return EMSESP::system_.heartbeat_json(json);
}
JsonObject node;
node = json.createNestedObject("System");
@@ -872,7 +882,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & json
node["version"] = EMSESP_APP_VERSION;
node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
#ifndef EMSESP_STANDALONE
node["freemem"] = ESP.getFreeHeap();
node["freemem"] = ESP.getFreeHeap() / 1000L; // kilobytes
#endif
node = json.createNestedObject("Status");
@@ -900,10 +910,12 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & json
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();
}
if (EMSESP::dallas_enabled()) {
node["#dallas sensors"] = EMSESP::sensor_devices().size();
node["#dallas reads"] = EMSESP::sensor_reads();
node["#dallas fails"] = EMSESP::sensor_fails();
}
}
@@ -923,8 +935,8 @@ 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");
obj["type"] = F_(Dallassensor);
obj["name"] = F_(Dallassensor);
}
return true;

View File

@@ -54,6 +54,7 @@ class System {
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);
#if defined(EMSESP_DEBUG)
static bool command_test(const char * value, const int8_t id);
#endif
@@ -138,7 +139,7 @@ class System {
void system_check();
void measure_analog();
int8_t wifi_quality();
int8_t wifi_quality(int8_t dBm);
bool system_healthy_ = false;
uint32_t led_flash_speed_ = LED_WARNING_BLINK_FAST; // default boot flashes quickly

View File

@@ -22,15 +22,19 @@
namespace emsesp {
// CRC lookup table with poly 12 for faster checking
const uint8_t ems_crc_table[] = {0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1C, 0x1E, 0x20, 0x22, 0x24, 0x26, 0x28, 0x2A, 0x2C, 0x2E, 0x30, 0x32, 0x34, 0x36, 0x38,
0x3A, 0x3C, 0x3E, 0x40, 0x42, 0x44, 0x46, 0x48, 0x4A, 0x4C, 0x4E, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5A, 0x5C, 0x5E, 0x60, 0x62, 0x64, 0x66, 0x68, 0x6A, 0x6C, 0x6E, 0x70, 0x72,
0x74, 0x76, 0x78, 0x7A, 0x7C, 0x7E, 0x80, 0x82, 0x84, 0x86, 0x88, 0x8A, 0x8C, 0x8E, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9A, 0x9C, 0x9E, 0xA0, 0xA2, 0xA4, 0xA6, 0xA8, 0xAA, 0xAC,
0xAE, 0xB0, 0xB2, 0xB4, 0xB6, 0xB8, 0xBA, 0xBC, 0xBE, 0xC0, 0xC2, 0xC4, 0xC6, 0xC8, 0xCA, 0xCC, 0xCE, 0xD0, 0xD2, 0xD4, 0xD6, 0xD8, 0xDA, 0xDC, 0xDE, 0xE0, 0xE2, 0xE4, 0xE6,
0xE8, 0xEA, 0xEC, 0xEE, 0xF0, 0xF2, 0xF4, 0xF6, 0xF8, 0xFA, 0xFC, 0xFE, 0x19, 0x1B, 0x1D, 0x1F, 0x11, 0x13, 0x15, 0x17, 0x09, 0x0B, 0x0D, 0x0F, 0x01, 0x03, 0x05, 0x07, 0x39,
0x3B, 0x3D, 0x3F, 0x31, 0x33, 0x35, 0x37, 0x29, 0x2B, 0x2D, 0x2F, 0x21, 0x23, 0x25, 0x27, 0x59, 0x5B, 0x5D, 0x5F, 0x51, 0x53, 0x55, 0x57, 0x49, 0x4B, 0x4D, 0x4F, 0x41, 0x43,
0x45, 0x47, 0x79, 0x7B, 0x7D, 0x7F, 0x71, 0x73, 0x75, 0x77, 0x69, 0x6B, 0x6D, 0x6F, 0x61, 0x63, 0x65, 0x67, 0x99, 0x9B, 0x9D, 0x9F, 0x91, 0x93, 0x95, 0x97, 0x89, 0x8B, 0x8D,
0x8F, 0x81, 0x83, 0x85, 0x87, 0xB9, 0xBB, 0xBD, 0xBF, 0xB1, 0xB3, 0xB5, 0xB7, 0xA9, 0xAB, 0xAD, 0xAF, 0xA1, 0xA3, 0xA5, 0xA7, 0xD9, 0xDB, 0xDD, 0xDF, 0xD1, 0xD3, 0xD5, 0xD7,
0xC9, 0xCB, 0xCD, 0xCF, 0xC1, 0xC3, 0xC5, 0xC7, 0xF9, 0xFB, 0xFD, 0xFF, 0xF1, 0xF3, 0xF5, 0xF7, 0xE9, 0xEB, 0xED, 0xEF, 0xE1, 0xE3, 0xE5, 0xE7};
const uint8_t ems_crc_table[] = {0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1C, 0x1E, 0x20, 0x22, 0x24, 0x26,
0x28, 0x2A, 0x2C, 0x2E, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3A, 0x3C, 0x3E, 0x40, 0x42, 0x44, 0x46, 0x48, 0x4A, 0x4C, 0x4E,
0x50, 0x52, 0x54, 0x56, 0x58, 0x5A, 0x5C, 0x5E, 0x60, 0x62, 0x64, 0x66, 0x68, 0x6A, 0x6C, 0x6E, 0x70, 0x72, 0x74, 0x76,
0x78, 0x7A, 0x7C, 0x7E, 0x80, 0x82, 0x84, 0x86, 0x88, 0x8A, 0x8C, 0x8E, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9A, 0x9C, 0x9E,
0xA0, 0xA2, 0xA4, 0xA6, 0xA8, 0xAA, 0xAC, 0xAE, 0xB0, 0xB2, 0xB4, 0xB6, 0xB8, 0xBA, 0xBC, 0xBE, 0xC0, 0xC2, 0xC4, 0xC6,
0xC8, 0xCA, 0xCC, 0xCE, 0xD0, 0xD2, 0xD4, 0xD6, 0xD8, 0xDA, 0xDC, 0xDE, 0xE0, 0xE2, 0xE4, 0xE6, 0xE8, 0xEA, 0xEC, 0xEE,
0xF0, 0xF2, 0xF4, 0xF6, 0xF8, 0xFA, 0xFC, 0xFE, 0x19, 0x1B, 0x1D, 0x1F, 0x11, 0x13, 0x15, 0x17, 0x09, 0x0B, 0x0D, 0x0F,
0x01, 0x03, 0x05, 0x07, 0x39, 0x3B, 0x3D, 0x3F, 0x31, 0x33, 0x35, 0x37, 0x29, 0x2B, 0x2D, 0x2F, 0x21, 0x23, 0x25, 0x27,
0x59, 0x5B, 0x5D, 0x5F, 0x51, 0x53, 0x55, 0x57, 0x49, 0x4B, 0x4D, 0x4F, 0x41, 0x43, 0x45, 0x47, 0x79, 0x7B, 0x7D, 0x7F,
0x71, 0x73, 0x75, 0x77, 0x69, 0x6B, 0x6D, 0x6F, 0x61, 0x63, 0x65, 0x67, 0x99, 0x9B, 0x9D, 0x9F, 0x91, 0x93, 0x95, 0x97,
0x89, 0x8B, 0x8D, 0x8F, 0x81, 0x83, 0x85, 0x87, 0xB9, 0xBB, 0xBD, 0xBF, 0xB1, 0xB3, 0xB5, 0xB7, 0xA9, 0xAB, 0xAD, 0xAF,
0xA1, 0xA3, 0xA5, 0xA7, 0xD9, 0xDB, 0xDD, 0xDF, 0xD1, 0xD3, 0xD5, 0xD7, 0xC9, 0xCB, 0xCD, 0xCF, 0xC1, 0xC3, 0xC5, 0xC7,
0xF9, 0xFB, 0xFD, 0xFF, 0xF1, 0xF3, 0xF5, 0xF7, 0xE9, 0xEB, 0xED, 0xEF, 0xE1, 0xE3, 0xE5, 0xE7};
uint32_t EMSbus::last_bus_activity_ = 0; // timestamp of last time a valid Rx came in
bool EMSbus::bus_connected_ = false; // start assuming the bus hasn't been connected
@@ -55,7 +59,13 @@ uint8_t EMSbus::calculate_crc(const uint8_t * data, const uint8_t length) {
// creates a telegram object
// stores header in separate member objects and the rest in the message_data block
Telegram::Telegram(const uint8_t operation, const uint8_t src, const uint8_t dest, const uint16_t type_id, const uint8_t offset, const uint8_t * data, const uint8_t message_length)
Telegram::Telegram(const uint8_t operation,
const uint8_t src,
const uint8_t dest,
const uint16_t type_id,
const uint8_t offset,
const uint8_t * data,
const uint8_t message_length)
: operation(operation)
, src(src)
, dest(dest)
@@ -192,7 +202,8 @@ void RxService::add(uint8_t * data, uint8_t length) {
// if we're watching and "raw" print out actual telegram as bytes to the console
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) || ((trace_watch_id < 0x80) && ((src == trace_watch_id) || (dest == trace_watch_id)))) {
if ((trace_watch_id == WATCH_ID_NONE) || (type_id == trace_watch_id)
|| ((trace_watch_id < 0x80) && ((src == trace_watch_id) || (dest == trace_watch_id)))) {
LOG_NOTICE(F("Rx: %s"), Helpers::data_to_hex(data, length).c_str());
} else if (EMSESP::trace_raw()) {
LOG_TRACE(F("Rx: %s"), Helpers::data_to_hex(data, length).c_str());
@@ -349,7 +360,10 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
length++; // add one since we want to now include the CRC
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());
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());
set_post_send_query(tx_telegram.validateid_);
// send the telegram to the UART Tx
@@ -389,7 +403,14 @@ void TxService::send_telegram(const uint8_t * data, const uint8_t length) {
}
*/
void TxService::add(const uint8_t operation, const uint8_t dest, const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validateid, const bool front) {
void TxService::add(const uint8_t operation,
const uint8_t dest,
const uint16_t type_id,
const uint8_t offset,
uint8_t * message_data,
const uint8_t message_length,
const uint16_t validateid,
const bool front) {
auto telegram = std::make_shared<Telegram>(operation, ems_bus_id(), dest, type_id, offset, message_data, message_length);
#ifdef EMSESP_DEBUG
@@ -546,7 +567,10 @@ void TxService::retry_tx(const uint8_t operation, const uint8_t * data, const ui
reset_retry_count(); // give up
increment_telegram_fail_count(); // another Tx fail
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();
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();
return;
}
@@ -566,9 +590,12 @@ void TxService::retry_tx(const uint8_t operation, const uint8_t * data, const ui
tx_telegrams_.emplace_front(tx_telegram_id_++, std::move(telegram_last_), true, get_post_send_query());
}
uint16_t TxService::read_next_tx() {
uint16_t TxService::read_next_tx(uint8_t offset) {
// add to the top/front of the queue
uint8_t message_data[1] = {EMS_MAX_TELEGRAM_LENGTH}; // request all data, 32 bytes
if (telegram_last_->offset != offset) {
return 0;
}
add(Telegram::Operation::TX_READ, telegram_last_->dest, telegram_last_->type_id, telegram_last_->offset + 25, message_data, 1, 0, true);
return telegram_last_->type_id;
}

View File

@@ -55,7 +55,13 @@ namespace emsesp {
class Telegram {
public:
Telegram(const uint8_t operation, const uint8_t src, const uint8_t dest, const uint16_t type_id, const uint8_t offset, const uint8_t * message_data, const uint8_t message_length);
Telegram(const uint8_t operation,
const uint8_t src,
const uint8_t dest,
const uint16_t type_id,
const uint8_t offset,
const uint8_t * message_data,
const uint8_t message_length);
~Telegram() = default;
const uint8_t operation; // is Operation mode
@@ -262,7 +268,14 @@ class TxService : public EMSbus {
void start();
void send();
void add(const uint8_t operation, const uint8_t dest, const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validateid, const bool front = false);
void add(const uint8_t operation,
const uint8_t dest,
const uint16_t type_id,
const uint8_t offset,
uint8_t * message_data,
const uint8_t message_length,
const uint16_t validateid,
const bool front = false);
void add(const uint8_t operation, const uint8_t * data, const uint8_t length, const uint16_t validateid, const bool front = false);
void read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset = 0, const uint8_t length = 0);
void send_raw(const char * telegram_data);
@@ -270,7 +283,7 @@ class TxService : public EMSbus {
void retry_tx(const uint8_t operation, const uint8_t * data, const uint8_t length);
bool is_last_tx(const uint8_t src, const uint8_t dest) const;
uint16_t post_send_query();
uint16_t read_next_tx();
uint16_t read_next_tx(uint8_t offset);
uint8_t retry_count() const {
return retry_count_;

View File

@@ -37,7 +37,8 @@ bool Test::run_test(const char * command, int8_t id) {
// add some data
// Boiler -> Me, UBAMonitorFast(0x18), telegram: 08 00 18 00 00 02 5A 73 3D 0A 10 65 40 02 1A 80 00 01 E1 01 76 0E 3D 48 00 C9 44 02 00 (#data=25)
uart_telegram({0x08, 0x00, 0x18, 0x00, 0x00, 0x02, 0x5A, 0x73, 0x3D, 0x0A, 0x10, 0x65, 0x40, 0x02, 0x1A, 0x80, 0x00, 0x01, 0xE1, 0x01, 0x76, 0x0E, 0x3D, 0x48, 0x00, 0xC9, 0x44, 0x02, 0x00});
uart_telegram({0x08, 0x00, 0x18, 0x00, 0x00, 0x02, 0x5A, 0x73, 0x3D, 0x0A, 0x10, 0x65, 0x40, 0x02, 0x1A,
0x80, 0x00, 0x01, 0xE1, 0x01, 0x76, 0x0E, 0x3D, 0x48, 0x00, 0xC9, 0x44, 0x02, 0x00});
// Boiler -> Thermostat, UBAParameterWW(0x33), telegram: 08 97 33 00 23 24 (#data=2)
uart_telegram({0x08, 0x98, 0x33, 0x00, 0x23, 0x24});
@@ -46,12 +47,13 @@ bool Test::run_test(const char * command, int8_t id) {
uart_telegram({0x08, 0x0B, 0x33, 0x00, 0x08, 0xFF, 0x34, 0xFB, 0x00, 0x28, 0x00, 0x00, 0x46, 0x00, 0xFF, 0xFF, 0x00});
// Thermostat RCPLUSStatusMessage_HC1(0x01A5)
uart_telegram({0x98, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24, 0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
uart_telegram({0x98, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
return true;
}
if (strcmp(command, "general2") == 0) {
if (strcmp(command, "310") == 0) {
EMSESP::logger().info(F("Testing GB072/RC310..."));
add_device(0x08, 123); // GB072
@@ -59,7 +61,8 @@ bool Test::run_test(const char * command, int8_t id) {
// add some data
// Boiler -> Me, UBAMonitorFast(0x18), telegram: 08 00 18 00 00 02 5A 73 3D 0A 10 65 40 02 1A 80 00 01 E1 01 76 0E 3D 48 00 C9 44 02 00 (#data=25)
uart_telegram({0x08, 0x00, 0x18, 0x00, 0x00, 0x02, 0x5A, 0x73, 0x3D, 0x0A, 0x10, 0x65, 0x40, 0x02, 0x1A, 0x80, 0x00, 0x01, 0xE1, 0x01, 0x76, 0x0E, 0x3D, 0x48, 0x00, 0xC9, 0x44, 0x02, 0x00});
uart_telegram({0x08, 0x00, 0x18, 0x00, 0x00, 0x02, 0x5A, 0x73, 0x3D, 0x0A, 0x10, 0x65, 0x40, 0x02, 0x1A,
0x80, 0x00, 0x01, 0xE1, 0x01, 0x76, 0x0E, 0x3D, 0x48, 0x00, 0xC9, 0x44, 0x02, 0x00});
// Boiler -> Thermostat, UBAParameterWW(0x33), telegram: 08 97 33 00 23 24 (#data=2)
uart_telegram({0x08, 0x90, 0x33, 0x00, 0x23, 0x24});
@@ -68,7 +71,11 @@ bool Test::run_test(const char * command, int8_t id) {
uart_telegram({0x08, 0x0B, 0x33, 0x00, 0x08, 0xFF, 0x34, 0xFB, 0x00, 0x28, 0x00, 0x00, 0x46, 0x00, 0xFF, 0xFF, 0x00});
// Thermostat 0x2A5 for HC1
uart_telegram({0x10, 00, 0xFF, 00, 01, 0xA5, 0x80, 00, 01, 0x30, 0x28, 00, 0x30, 0x28, 01, 0x54, 03, 03, 01, 01, 0x54, 02, 0xA8, 00, 00, 0x11, 01, 03, 0xFF, 0xFF, 00});
uart_telegram({0x10, 00, 0xFF, 00, 01, 0xA5, 0x80, 00, 01, 0x30, 0x28, 00, 0x30, 0x28, 01, 0x54,
03, 03, 01, 01, 0x54, 02, 0xA8, 00, 00, 0x11, 01, 03, 0xFF, 0xFF, 00});
// RC300WWmode2(0x31D), data: 00 00 09 07
uart_telegram({0x10, 00, 0xFF, 00, 02, 0x1D, 00, 00, 0x09, 0x07});
return true;
}
@@ -123,7 +130,8 @@ bool Test::run_test(const char * command, int8_t id) {
uart_telegram({0x08, 0x0B, 0x14, 00, 0x3C, 0x1F, 0xAC, 0x70});
// Boiler -> Me, UBAMonitorFast(0x18), telegram: 08 00 18 00 00 02 5A 73 3D 0A 10 65 40 02 1A 80 00 01 E1 01 76 0E 3D 48 00 C9 44 02 00 (#data=25)
uart_telegram({0x08, 0x00, 0x18, 0x00, 0x00, 0x02, 0x5A, 0x73, 0x3D, 0x0A, 0x10, 0x65, 0x40, 0x02, 0x1A, 0x80, 0x00, 0x01, 0xE1, 0x01, 0x76, 0x0E, 0x3D, 0x48, 0x00, 0xC9, 0x44, 0x02, 0x00});
uart_telegram({0x08, 0x00, 0x18, 0x00, 0x00, 0x02, 0x5A, 0x73, 0x3D, 0x0A, 0x10, 0x65, 0x40, 0x02, 0x1A,
0x80, 0x00, 0x01, 0xE1, 0x01, 0x76, 0x0E, 0x3D, 0x48, 0x00, 0xC9, 0x44, 0x02, 0x00});
// Boiler -> Me, UBAParameterWW(0x33), telegram: 08 0B 33 00 08 FF 34 FB 00 28 00 00 46 00 FF FF 00 (#data=13)
uart_telegram({0x08, 0x0B, 0x33, 0x00, 0x08, 0xFF, 0x34, 0xFB, 0x00, 0x28, 0x00, 0x00, 0x46, 0x00, 0xFF, 0xFF, 0x00});
@@ -150,9 +158,11 @@ bool Test::run_test(const char * command, int8_t id) {
add_device(0x30, 163); // SM100
// SM100Monitor - type 0x0362 EMS+ - for SM100 and SM200
uart_telegram({0xB0, 0x0B, 0xFF, 00, 0x02, 0x62, 00, 0x44, 0x02, 0x7A, 0x80, 00, 0x80, 0x00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 00, 0x7C, 0x80, 00, 0x80, 00, 0x80, 00, 0x80});
uart_telegram({0xB0, 0x0B, 0xFF, 00, 0x02, 0x62, 00, 0x44, 0x02, 0x7A, 0x80, 00, 0x80, 0x00, 0x80, 00,
0x80, 00, 0x80, 00, 0x80, 00, 00, 0x7C, 0x80, 00, 0x80, 00, 0x80, 00, 0x80});
uart_telegram({0xB0, 0x0B, 0xFF, 0x00, 0x02, 0x62, 0x01, 0x44, 0x03, 0x30, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 0x33});
uart_telegram({0xB0, 0x0B, 0xFF, 0x00, 0x02, 0x62, 0x01, 0x44, 0x03, 0x30, 0x80, 00, 0x80, 00, 0x80, 00,
0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 0x33});
uart_telegram({0xB0, 00, 0xFF, 0x18, 02, 0x62, 0x80, 00, 0xB8});
@@ -320,35 +330,45 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
run_test("gateway");
}
if (command == "310") {
shell.printfln(F("Testing RC310..."));
run_test("310");
shell.invoke_command("show devices");
shell.invoke_command("show");
shell.invoke_command("call system publish");
shell.invoke_command("show mqtt");
}
if (command == "web") {
shell.printfln(F("Testing Web..."));
run_test("boiler");
run_test("thermostat");
// test call
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN);
JsonObject json = doc.to<JsonObject>();
(void)emsesp::Command::call(EMSdevice::DeviceType::BOILER, "info", nullptr, -1, json);
#if defined(EMSESP_STANDALONE)
Serial.print(COLOR_YELLOW);
if (json.size() != 0) {
serializeJson(doc, Serial);
}
shell.println();
Serial.print(COLOR_RESET);
#endif
DynamicJsonDocument doc(8000); // some absurb high number
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice) {
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN);
doc.clear();
JsonObject json = doc.to<JsonObject>();
Command::call(emsdevice->device_type(), "info", nullptr, -1, json);
Serial.print(COLOR_YELLOW);
if (json.size() != 0) {
serializeJson(doc, Serial);
}
shell.println();
Serial.print(COLOR_RESET);
doc.clear();
JsonObject root = doc.to<JsonObject>();
emsdevice->generate_values_json_web(root);
#if defined(EMSESP_STANDALONE)
Serial.print(COLOR_BRIGHT_MAGENTA);
serializeJson(doc, Serial);
Serial.print(COLOR_RESET);
Serial.println();
Serial.print("** memoryUsage=");
Serial.print(doc.memoryUsage());
@@ -357,10 +377,9 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
Serial.print(" measureJson=");
Serial.print(measureJson(doc));
Serial.println(" **");
Serial.print(COLOR_RESET);
#endif
}
}
#endif
return;
}
@@ -477,7 +496,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
add_device(0x18, 202); // Bosch TC100 - https://github.com/emsesp/EMS-ESP/issues/474
// 0x0A
uart_telegram({0x98, 0x0B, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
uart_telegram({0x98, 0x0B, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
}
if (command == "solar") {
@@ -507,9 +527,11 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
// SM100Monitor - type 0x0362 EMS+ - for SM100 and SM200
// B0 0B FF 00 02 62 00 44 02 7A 80 00 80 00 80 00 80 00 80 00 80 00 00 7C 80 00 80 00 80 00 80
rx_telegram({0xB0, 0x0B, 0xFF, 00, 0x02, 0x62, 00, 0x44, 0x02, 0x7A, 0x80, 00, 0x80, 0x00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 00, 0x7C, 0x80, 00, 0x80, 00, 0x80, 00, 0x80});
rx_telegram({0xB0, 0x0B, 0xFF, 00, 0x02, 0x62, 00, 0x44, 0x02, 0x7A, 0x80, 00, 0x80, 0x00, 0x80, 00,
0x80, 00, 0x80, 00, 0x80, 00, 00, 0x7C, 0x80, 00, 0x80, 00, 0x80, 00, 0x80});
rx_telegram({0xB0, 0x0B, 0xFF, 0x00, 0x02, 0x62, 0x01, 0x44, 0x03, 0x30, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 0x33});
rx_telegram({0xB0, 0x0B, 0xFF, 0x00, 0x02, 0x62, 0x01, 0x44, 0x03, 0x30, 0x80, 00, 0x80, 00, 0x80, 00,
0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 0x33});
rx_telegram({0xB0, 00, 0xFF, 0x18, 02, 0x62, 0x80, 00, 0xB8});
@@ -588,7 +610,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
// RCPLUSStatusMessage_HC1(0x01A5)
// 98 00 FF 00 01 A5 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03 (no CRC)
uart_telegram({0x98, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24, 0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
uart_telegram({0x98, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
uart_telegram("98 00 FF 00 01 A5 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03"); // without CRC
uart_telegram_withCRC("98 00 FF 00 01 A6 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03 6B"); // with CRC
@@ -617,7 +640,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
// fake telegrams. length includes CRC
// Boiler -> Me, UBAMonitorFast(0x18), telegram: 08 00 18 00 00 02 5A 73 3D 0A 10 65 40 02 1A 80 00 01 E1 01 76 0E 3D 48 00 C9 44 02 00 (#data=25)
uart_telegram({0x08, 0x00, 0x18, 0x00, 0x00, 0x02, 0x5A, 0x73, 0x3D, 0x0A, 0x10, 0x65, 0x40, 0x02, 0x1A, 0x80, 0x00, 0x01, 0xE1, 0x01, 0x76, 0x0E, 0x3D, 0x48, 0x00, 0xC9, 0x44, 0x02, 0x00});
uart_telegram({0x08, 0x00, 0x18, 0x00, 0x00, 0x02, 0x5A, 0x73, 0x3D, 0x0A, 0x10, 0x65, 0x40, 0x02, 0x1A,
0x80, 0x00, 0x01, 0xE1, 0x01, 0x76, 0x0E, 0x3D, 0x48, 0x00, 0xC9, 0x44, 0x02, 0x00});
// Boiler -> Thermostat, UBAParameterWW(0x33), telegram: 08 97 33 00 23 24 (#data=2)
uart_telegram({0x08, 0x97, 0x33, 0x00, 0x23, 0x24});
@@ -643,10 +667,12 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
// Thermostat -> all, telegram: 10 00 FF 00 01 A5 00 D7 21 00 00 00 00 30 01 84 01 01 03 01 84 01 F1 00 00 11 01 00 08 63 00
// 0x1A5 test ems+
uart_telegram({0x10, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xD7, 0x21, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x84, 0x01, 0x01, 0x03, 0x01, 0x84, 0x01, 0xF1, 0x00, 0x00, 0x11, 0x01, 0x00, 0x08, 0x63, 0x00});
uart_telegram({0x10, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xD7, 0x21, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x84,
0x01, 0x01, 0x03, 0x01, 0x84, 0x01, 0xF1, 0x00, 0x00, 0x11, 0x01, 0x00, 0x08, 0x63, 0x00});
// setting temp from 21.5 to 19.9C
uart_telegram({0x10, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xC7, 0x21, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x84, 0x01, 0x01, 0x03, 0x01, 0x84, 0x01, 0xF1, 0x00, 0x00, 0x11, 0x01, 0x00, 0x08, 0x63, 0x00});
uart_telegram({0x10, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xC7, 0x21, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x84,
0x01, 0x01, 0x03, 0x01, 0x84, 0x01, 0xF1, 0x00, 0x00, 0x11, 0x01, 0x00, 0x08, 0x63, 0x00});
// Thermostat -> Boiler, UBAFlags(0x35), telegram: 17 08 35 00 11 00 (#data=2)
uart_telegram({0x17, 0x08, 0x35, 0x00, 0x11, 0x00});
@@ -655,10 +681,12 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
uart_telegram({0x17, 0x08, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00});
// Thermostat -> Me, RC20Set(0xA8), telegram: 17 0B A8 00 01 00 FF F6 01 06 00 01 0D 01 00 FF FF 01 02 02 02 00 00 05 1F 05 1F 02 0E 00 FF (#data=27)
uart_telegram({0x17, 0x0B, 0xA8, 0x00, 0x01, 0x00, 0xFF, 0xF6, 0x01, 0x06, 0x00, 0x01, 0x0D, 0x01, 0x00, 0xFF, 0xFF, 0x01, 0x02, 0x02, 0x02, 0x00, 0x00, 0x05, 0x1F, 0x05, 0x1F, 0x02, 0x0E, 0x00, 0xFF});
uart_telegram({0x17, 0x0B, 0xA8, 0x00, 0x01, 0x00, 0xFF, 0xF6, 0x01, 0x06, 0x00, 0x01, 0x0D, 0x01, 0x00, 0xFF,
0xFF, 0x01, 0x02, 0x02, 0x02, 0x00, 0x00, 0x05, 0x1F, 0x05, 0x1F, 0x02, 0x0E, 0x00, 0xFF});
// Boiler(0x08) -> All(0x00), UBAMonitorWW(0x34), data: 36 01 A5 80 00 21 00 00 01 00 01 3E 8D 03 77 91 00 80 00
uart_telegram({0x08, 0x00, 0x34, 0x00, 0x36, 0x01, 0xA5, 0x80, 0x00, 0x21, 0x00, 0x00, 0x01, 0x00, 0x01, 0x3E, 0x8D, 0x03, 0x77, 0x91, 0x00, 0x80, 0x00});
uart_telegram(
{0x08, 0x00, 0x34, 0x00, 0x36, 0x01, 0xA5, 0x80, 0x00, 0x21, 0x00, 0x00, 0x01, 0x00, 0x01, 0x3E, 0x8D, 0x03, 0x77, 0x91, 0x00, 0x80, 0x00});
// test 0x2A - DHWStatus3
uart_telegram({0x88, 00, 0x2A, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0xD2, 00, 00, 0x80, 00, 00, 01, 0x9D, 0x80, 0x00, 0x02, 0x79, 00});
@@ -686,7 +714,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
EMSESP::send_write_request(0x91, 0x17, 0x00, t18, sizeof(t18), 0x00);
// TX - send EMS+
const uint8_t t13[] = {0x90, 0x0B, 0xFF, 00, 01, 0xBA, 00, 0x2E, 0x2A, 0x26, 0x1E, 0x03, 00, 0xFF, 0xFF, 05, 0x2A, 01, 0xE1, 0x20, 0x01, 0x0F, 05, 0x2A};
const uint8_t t13[] = {0x90, 0x0B, 0xFF, 00, 01, 0xBA, 00, 0x2E, 0x2A, 0x26, 0x1E, 0x03,
00, 0xFF, 0xFF, 05, 0x2A, 01, 0xE1, 0x20, 0x01, 0x0F, 05, 0x2A};
EMSESP::txservice_.add(Telegram::Operation::TX_RAW, t13, sizeof(t13), 0);
// EMS+ Junkers read request
@@ -791,7 +820,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
add_device(0x18, 157); // Bosch CR100 - https://github.com/emsesp/EMS-ESP/issues/355
// RCPLUSStatusMessage_HC1(0x01A5) - HC1
uart_telegram({0x98, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24, 0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
uart_telegram({0x98, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
uart_telegram("98 00 FF 00 01 A5 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03"); // without CRC
uart_telegram_withCRC("98 00 FF 00 01 A5 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03 13"); // with CRC
@@ -858,7 +888,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
if (command == "rx2") {
shell.printfln(F("Testing rx2..."));
uart_telegram({0x1B, 0x5B, 0xFD, 0x2D, 0x9E, 0x3A, 0xB6, 0xE5, 0x02, 0x20, 0x33, 0x30, 0x32, 0x3A, 0x20, 0x5B, 0x73, 0xFF, 0xFF, 0xCB, 0xDF, 0xB7, 0xA7, 0xB5, 0x67, 0x77, 0x77, 0xE4, 0xFF, 0xFD, 0x77, 0xFF});
uart_telegram({0x1B, 0x5B, 0xFD, 0x2D, 0x9E, 0x3A, 0xB6, 0xE5, 0x02, 0x20, 0x33, 0x30, 0x32, 0x3A, 0x20, 0x5B,
0x73, 0xFF, 0xFF, 0xCB, 0xDF, 0xB7, 0xA7, 0xB5, 0x67, 0x77, 0x77, 0xE4, 0xFF, 0xFD, 0x77, 0xFF});
}
// https://github.com/emsesp/EMS-ESP/issues/380#issuecomment-633663007
@@ -897,6 +928,23 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
shell.invoke_command("call system publish");
shell.invoke_command("show mqtt");
}
if (command == "api") {
#if defined(EMSESP_STANDALONE)
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;
request.method(HTTP_GET);
request.url("/api/thermostat/seltemp");
EMSESP::webAPIService.webAPIService_get(&request);
request.url("/api/boiler/syspress");
EMSESP::webAPIService.webAPIService_get(&request);
#endif
}
}
// simulates a telegram in the Rx queue, but without the CRC which is added automatically

View File

@@ -22,6 +22,7 @@
#define EMSESP_TEST_H
#include "emsesp.h"
#include <ESPAsyncWebServer.h>
namespace emsesp {
@@ -35,8 +36,9 @@ namespace emsesp {
// #define EMSESP_DEBUG_DEFAULT "mqtt_nested"
// #define EMSESP_DEBUG_DEFAULT "ha"
// #define EMSESP_DEBUG_DEFAULT "board_profile"
#define EMSESP_DEBUG_DEFAULT "shower_alert"
// #define EMSESP_DEBUG_DEFAULT "shower_alert"
// #define EMSESP_DEBUG_DEFAULT "310"
#define EMSESP_DEBUG_DEFAULT "api"
class Test {
public:

View File

@@ -102,10 +102,10 @@ void EMSuart::start(const uint8_t tx_mode, const uint8_t rx_gpio, const uint8_t
uart_param_config(EMSUART_UART, &uart_config);
uart_set_pin(EMSUART_UART, tx_gpio, rx_gpio, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
EMS_UART.int_ena.val = 0; // disable all intr.
EMS_UART.int_clr.val = 0xFFFFFFFF; // clear all intr. flags
EMS_UART.idle_conf.tx_brk_num = 10; // breaklength 10 bit
drop_next_rx_ = true;
EMS_UART.int_ena.val = 0; // disable all intr.
EMS_UART.int_clr.val = 0xFFFFFFFF; // clear all intr. flags
EMS_UART.idle_conf.tx_brk_num = 10; // breaklength 10 bit
drop_next_rx_ = true;
// EMS_UART.idle_conf.rx_idle_thrhd = 256;
// EMS_UART.auto_baud.glitch_filt = 192;
#if (EMSUART_UART != UART_NUM_2)
@@ -140,7 +140,6 @@ void EMSuart::restart() {
EMS_UART.int_ena.brk_det = 1; // activate only break
EMS_UART.conf0.txd_brk = (tx_mode_ == EMS_TXMODE_HW) ? 1 : 0;
portEXIT_CRITICAL(&mux_);
}
/*

View File

@@ -1,2 +1 @@
#define EMSESP_APP_VERSION "3.1.0"
#define EMSESP_PLATFORM "ESP32"
#define EMSESP_APP_VERSION "3.1.1b8"

304
src/web/WebAPIService.cpp Normal file
View File

@@ -0,0 +1,304 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// SUrlParser from https://github.com/Mad-ness/simple-url-parser
#include "emsesp.h"
using namespace std::placeholders; // for `_1` etc
namespace emsesp {
WebAPIService::WebAPIService(AsyncWebServer * server, SecurityManager * securityManager)
: _securityManager(securityManager)
, _apiHandler("/api", std::bind(&WebAPIService::webAPIService_post, this, _1, _2), 256) { // for POSTS
server->on("/api", HTTP_GET, std::bind(&WebAPIService::webAPIService_get, this, _1)); // for GETS
server->addHandler(&_apiHandler);
}
// GET /{device}
// GET /{device}/{name}
// GET /device={device}?cmd={name}?data={value}[?id={hc}
void WebAPIService::webAPIService_get(AsyncWebServerRequest * request) {
std::string device("");
std::string cmd("");
int id = -1;
std::string value("");
parse(request, device, cmd, id, value); // pass it defaults
}
// For POSTS with an optional JSON body
// 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 (not json.is<JsonObject>()) {
webAPIService_get(request);
return;
}
// extract values from the json
// these will be used as default values
auto && body = json.as<JsonObject>();
std::string device = body["name"].as<std::string>(); // note this was called device in the v2
std::string cmd = body["cmd"].as<std::string>();
int id = -1;
if (body.containsKey("id")) {
id = body["id"];
} else if (body.containsKey("hc")) {
id = body["hc"];
} else {
id = -1;
}
// make sure we have a value. There must always be a value
if (!body.containsKey(F_(value))) {
send_message_response(request, 400, "Problems parsing JSON"); // Bad Request
return;
}
std::string value = body["value"].as<std::string>(); // always convert value to string
// now parse the URL. The URL is always leading and will overwrite anything provided in the json body
parse(request, device, cmd, id, value); // pass it defaults
}
// parse the URL looking for query or path parameters
// reporting back any errors
void WebAPIService::parse(AsyncWebServerRequest * request, std::string & device_s, std::string & cmd_s, int id, std::string & value_s) {
// parse URL for the path names
SUrlParser p;
p.parse(request->url().c_str());
// remove the /api from the path
if (p.paths().front() == "api") {
p.paths().erase(p.paths().begin());
} else {
return; // bad URL
}
uint8_t device_type;
int8_t id_n = -1; // default hc
// check for query parameters first
// /device={device}?cmd={name}?data={value}[?id={hc}
if (p.paths().size() == 0) {
// get the device
if (request->hasParam(F_(device))) {
device_s = request->getParam(F_(device))->value().c_str();
}
// get cmd
if (request->hasParam(F_(cmd))) {
cmd_s = request->getParam(F_(cmd))->value().c_str();
}
// get data, which is optional. This is now replaced with the name 'value' in JSON body
if (request->hasParam(F_(data))) {
value_s = request->getParam(F_(data))->value().c_str();
}
if (request->hasParam(F_(value))) {
value_s = request->getParam(F_(value))->value().c_str();
}
// get id (or hc), which is optional
if (request->hasParam(F_(id))) {
id_n = Helpers::atoint(request->getParam(F_(id))->value().c_str());
}
if (request->hasParam("hc")) {
id_n = Helpers::atoint(request->getParam("hc")->value().c_str());
}
} else {
// parse paths and json data
// /{device}[/{hc}][/{name}]
// first param must be a valid device, which includes "system"
device_s = p.paths().front();
auto num_paths = p.paths().size();
if (num_paths == 1) {
// if there are no more paths parameters, default to 'info'
// cmd_s = "info_short";
// check empty command in Command::find_command and set the default there!
} else if (num_paths == 2) {
cmd_s = p.paths()[1];
} else if (num_paths > 2) {
// check in Command::find_command makes prefix to TAG
cmd_s = p.paths()[1] + "/" + p.paths()[2];
}
}
// now go and validate everything
// device check
if (device_s.empty()) {
send_message_response(request, 422, "Missing device"); // Unprocessable Entity
return;
}
device_type = EMSdevice::device_name_2_device_type(device_s.c_str());
if (device_type == EMSdevice::DeviceType::UNKNOWN) {
send_message_response(request, 422, "Invalid call"); // Unprocessable Entity
return;
}
// 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;
EMSESP::webSettingsService.read([&](WebSettings & settings) {
Authentication authentication = _securityManager->authenticateRequest(request);
admin_allowed = settings.notoken_api | AuthenticationPredicates::IS_ADMIN(authentication);
});
if ((method != HTTP_GET) || ((method == HTTP_GET) && have_data)) {
if (!admin_allowed) {
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);
// check for errors
if (!ok) {
send_message_response(request, 400, "Problems parsing elements"); // Bad Request
return;
}
if (!json.size()) {
send_message_response(request, 200, "OK"); // OK
return;
}
// send the json that came back from the command call
response->setLength();
request->send(response); // send json response
#if defined(EMSESP_STANDALONE)
Serial.print(COLOR_YELLOW);
if (json.size() != 0) {
serializeJsonPretty(json, Serial);
}
Serial.println();
Serial.print(COLOR_RESET);
#endif
}
// send a HTTP error back, with optional JSON body data
void WebAPIService::send_message_response(AsyncWebServerRequest * request, uint16_t error_code, const char * error_message) {
if (error_message == nullptr) {
AsyncWebServerResponse * response = request->beginResponse(error_code); // just send the code
request->send(response);
} else {
// build a return message and send it
PrettyAsyncJsonResponse * response = new PrettyAsyncJsonResponse(false, EMSESP_JSON_SIZE_SMALL);
JsonObject json = response->getRoot();
json["message"] = error_message;
response->setCode(error_code);
response->setLength();
response->setContentType("application/json");
request->send(response);
}
}
/**
* Extract only the path component from the passed URI and normalized it.
* Ex. //one/two////three/// becomes /one/two/three
*/
std::string SUrlParser::path() {
std::string s = "/"; // set up the beginning slash
for (std::string & f : m_folders) {
s += f;
s += "/";
}
s.pop_back(); // deleting last letter, that is slash '/'
return std::string(s);
}
SUrlParser::SUrlParser(const char * uri) {
parse(uri);
}
bool SUrlParser::parse(const char * uri) {
m_folders.clear();
m_keysvalues.clear();
enum Type { begin, folder, param, value };
std::string s;
const char * c = uri;
enum Type t = Type::begin;
std::string last_param;
if (c != NULL || *c != '\0') {
do {
if (*c == '/') {
if (s.length() > 0) {
m_folders.push_back(s);
s.clear();
}
t = Type::folder;
} else if (*c == '?' && (t == Type::folder || t == Type::begin)) {
if (s.length() > 0) {
m_folders.push_back(s);
s.clear();
}
t = Type::param;
} else if (*c == '=' && (t == Type::param || t == Type::begin)) {
m_keysvalues[s] = "";
last_param = s;
s.clear();
t = Type::value;
} else if (*c == '&' && (t == Type::value || t == Type::param || t == Type::begin)) {
if (t == Type::value) {
m_keysvalues[last_param] = s;
} else if ((t == Type::param || t == Type::begin) && (s.length() > 0)) {
m_keysvalues[s] = "";
last_param = s;
}
t = Type::param;
s.clear();
} else if (*c == '\0' && s.length() > 0) {
if (t == Type::value) {
m_keysvalues[last_param] = s;
} else if (t == Type::folder || t == Type::begin) {
m_folders.push_back(s);
} else if (t == Type::param) {
m_keysvalues[s] = "";
last_param = s;
}
s.clear();
} else if (*c == '\0' && s.length() == 0) {
if (t == Type::param && last_param.length() > 0) {
m_keysvalues[last_param] = "";
}
s.clear();
} else {
s += *c;
}
} while (*c++ != '\0');
}
return true;
}
} // namespace emsesp

76
src/web/WebAPIService.h Normal file
View File

@@ -0,0 +1,76 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef WebAPIService_h
#define WebAPIService_h
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
#include <string>
#include <unordered_map>
#include <vector>
#define EMSESP_API_SERVICE_PATH "/api"
namespace emsesp {
typedef std::unordered_map<std::string, std::string> KeyValueMap_t;
typedef std::vector<std::string> Folder_t;
class SUrlParser {
private:
KeyValueMap_t m_keysvalues;
Folder_t m_folders;
public:
SUrlParser(){};
SUrlParser(const char * url);
bool parse(const char * url);
Folder_t & paths() {
return m_folders;
};
KeyValueMap_t & params() {
return m_keysvalues;
};
std::string path();
};
class WebAPIService {
public:
WebAPIService(AsyncWebServer * server, SecurityManager * securityManager);
void webAPIService_post(AsyncWebServerRequest * request, JsonVariant & json); // for POSTs
void webAPIService_get(AsyncWebServerRequest * request); // for GETs
private:
SecurityManager * _securityManager;
AsyncCallbackJsonWebHandler _apiHandler; // for POSTs
void parse(AsyncWebServerRequest * request, std::string & device, std::string & cmd, int id, std::string & value);
void send_message_response(AsyncWebServerRequest * request, uint16_t error_code, const char * error_message = nullptr);
};
} // namespace emsesp
#endif

View File

@@ -23,10 +23,16 @@ namespace emsesp {
using namespace std::placeholders; // for `_1` etc
WebDevicesService::WebDevicesService(AsyncWebServer * server, SecurityManager * securityManager)
: _device_dataHandler(DEVICE_DATA_SERVICE_PATH, securityManager->wrapCallback(std::bind(&WebDevicesService::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_AUTHENTICATED)) {
server->on(EMSESP_DEVICES_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WebDevicesService::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));
: _device_dataHandler(DEVICE_DATA_SERVICE_PATH,
securityManager->wrapCallback(std::bind(&WebDevicesService::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,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WebDevicesService::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));
_device_dataHandler.setMethod(HTTP_POST);
_device_dataHandler.setMaxContentLength(256);
@@ -77,9 +83,10 @@ void WebDevicesService::all_devices(AsyncWebServerRequest * request) {
}
// 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) {
if (json.is<JsonObject>()) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_XXLARGE_DYN);
MsgpackAsyncJsonResponse * response = new MsgpackAsyncJsonResponse(false, EMSESP_JSON_SIZE_XXLARGE_DYN);
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice) {
if (emsdevice->unique_id() == json["id"]) {
@@ -102,28 +109,21 @@ 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) {
// only issue commands if the API is enabled
EMSESP::webSettingsService.read([&](WebSettings & settings) {
if (!settings.api_enabled) {
request->send(403); // forbidden error
return;
}
});
if (json.is<JsonObject>()) {
JsonObject dv = json["devicevalue"];
uint8_t id = json["id"];
// using the unique ID from the web find the real device type
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice) {
if (emsdevice->unique_id() == dv["id"].as<int>()) {
const char * cmd = dv["cmd"];
if (emsdevice->unique_id() == id) {
const char * cmd = dv["c"];
uint8_t device_type = emsdevice->device_type();
bool ok = false;
char s[10];
// the data could be in any format, but we need string
JsonVariant data = dv["data"];
if (data.is<char *>()) {
JsonVariant data = dv["v"];
if (data.is<const char *>()) {
ok = Command::call(device_type, cmd, data.as<const char *>());
} else if (data.is<int>()) {
ok = Command::call(device_type, cmd, Helpers::render_value(s, data.as<int16_t>(), 0));
@@ -133,16 +133,17 @@ void WebDevicesService::write_value(AsyncWebServerRequest * request, JsonVariant
ok = Command::call(device_type, cmd, data.as<bool>() ? "true" : "false");
}
if (ok) {
request->send(200);
}
return; // found device, quit
// send "Write command sent to device" or "Write command failed"
AsyncWebServerResponse * response = request->beginResponse(ok ? 200 : 204);
request->send(response);
return;
}
}
}
request->send(204); // no content error
}
AsyncWebServerResponse * response = request->beginResponse(204); // Write command failed
request->send(response);
}
} // namespace emsesp

View File

@@ -29,7 +29,6 @@
#define DEVICE_DATA_SERVICE_PATH "/rest/deviceData"
#define WRITE_VALUE_SERVICE_PATH "/rest/writeValue"
namespace emsesp {
class WebDevicesService {

171
src/web/WebLogService.cpp Normal file
View File

@@ -0,0 +1,171 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "emsesp.h"
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.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));
// for setting a level
server->addHandler(&_setLevel);
// start event source service
start();
}
void WebLogService::forbidden(AsyncWebServerRequest * request) {
request->send(403);
}
void WebLogService::start() {
uuid::log::Logger::register_handler(this, uuid::log::Level::INFO); // default is INFO
}
uuid::log::Level WebLogService::log_level() const {
return uuid::log::Logger::get_log_level(this);
}
void WebLogService::log_level(uuid::log::Level level) {
uuid::log::Logger::register_handler(this, level);
}
size_t WebLogService::maximum_log_messages() const {
return maximum_log_messages_;
}
void WebLogService::maximum_log_messages(size_t count) {
maximum_log_messages_ = std::max((size_t)1, count);
while (log_messages_.size() > maximum_log_messages_) {
log_messages_.pop_front();
}
}
WebLogService::QueuedLogMessage::QueuedLogMessage(unsigned long id, std::shared_ptr<uuid::log::Message> && content)
: id_(id)
, content_(std::move(content)) {
}
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));
}
void WebLogService::loop() {
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) {
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();
}
}
// 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;
size_t len = measureJson(jsonDocument);
char * buffer = new char[len + 1];
if (buffer) {
serializeJson(jsonDocument, buffer, len + 1);
_events.send(buffer, "message", millis());
}
delete[] buffer;
}
// send the current log buffer to the API
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;
}
log_message_id_tail_ = log_messages_.back().id_;
response->setLength();
request->send(response);
}
// sets the level after a POST
void WebLogService::setLevel(AsyncWebServerRequest * request, JsonVariant & json) {
if (not json.is<JsonObject>()) {
return;
}
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);
}
// return the current log level after a GET
void WebLogService::getLevel(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_SMALL);
JsonObject root = response->getRoot();
root["level"] = log_level();
root["max_messages"] = maximum_log_messages();
response->setLength();
request->send(response);
}
} // namespace emsesp

80
src/web/WebLogService.h Normal file
View File

@@ -0,0 +1,80 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020 Paul Derbyshire
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef WebLogService_h
#define WebLogService_h
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#include <uuid/log.h>
#define EVENT_SOURCE_LOG_PATH "/es/log"
#define FETCH_LOG_PATH "/rest/fetchLog"
#define LOG_SETTINGS_PATH "/rest/logSettings"
namespace emsesp {
class WebLogService : public uuid::log::Handler {
public:
static constexpr size_t MAX_LOG_MESSAGES = 30;
WebLogService(AsyncWebServer * server, SecurityManager * securityManager);
void start();
uuid::log::Level log_level() const;
void log_level(uuid::log::Level level);
size_t maximum_log_messages() const;
void maximum_log_messages(size_t count);
void loop();
virtual void operator<<(std::shared_ptr<uuid::log::Message> message);
private:
AsyncEventSource _events;
class QueuedLogMessage {
public:
QueuedLogMessage(unsigned long id, std::shared_ptr<uuid::log::Message> && content);
~QueuedLogMessage() = default;
unsigned long id_; // Sequential identifier for this log message
struct timeval time_; // Time message was received
const std::shared_ptr<const uuid::log::Message> content_; // Log message content
};
void forbidden(AsyncWebServerRequest * request);
void transmit(const QueuedLogMessage & message);
void fetchLog(AsyncWebServerRequest * request);
void getLevel(AsyncWebServerRequest * request);
void setLevel(AsyncWebServerRequest * request, JsonVariant & json);
AsyncCallbackJsonWebHandler _setLevel; // 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
};
} // namespace emsesp
#endif

View File

@@ -27,7 +27,8 @@ using namespace std::placeholders; // for `_1` etc
WebSettingsService::WebSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(WebSettings::read, WebSettings::update, this, server, EMSESP_SETTINGS_SERVICE_PATH, securityManager)
, _fsPersistence(WebSettings::read, WebSettings::update, this, fs, EMSESP_SETTINGS_FILE)
, _boardProfileHandler(EMSESP_BOARD_PROFILE_SERVICE_PATH, securityManager->wrapCallback(std::bind(&WebSettingsService::board_profile, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) {
, _boardProfileHandler(EMSESP_BOARD_PROFILE_SERVICE_PATH,
securityManager->wrapCallback(std::bind(&WebSettingsService::board_profile, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) {
_boardProfileHandler.setMethod(HTTP_POST);
_boardProfileHandler.setMaxContentLength(256);
server->addHandler(&_boardProfileHandler);
@@ -54,9 +55,10 @@ 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["api_enabled"] = settings.api_enabled;
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;
}
@@ -168,7 +170,8 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings)
settings.master_thermostat = root["master_thermostat"] | EMSESP_DEFAULT_MASTER_THERMOSTAT;
// doesn't need any follow-up actions
settings.api_enabled = root["api_enabled"] | EMSESP_DEFAULT_API_ENABLED;
settings.notoken_api = root["notoken_api"] | EMSESP_DEFAULT_NOTOKEN_API;
settings.solar_maxflow = root["solar_maxflow"] | EMSESP_DEFAULT_SOLAR_MAXFLOW;
return StateUpdateResult::CHANGED;
}

View File

@@ -22,7 +22,7 @@
#include <HttpEndpoint.h>
#include <FSPersistence.h>
#include "default_settings.h"
#include "../default_settings.h"
#define EMSESP_SETTINGS_FILE "/config/emsespSettings.json"
#define EMSESP_SETTINGS_SERVICE_PATH "/rest/emsespSettings"
@@ -50,9 +50,10 @@ class WebSettings {
bool dallas_parasite;
uint8_t led_gpio;
bool hide_led;
bool api_enabled;
bool notoken_api;
bool analog_enabled;
uint8_t pbutton_gpio;
uint8_t solar_maxflow;
String board_profile;
static void read(WebSettings & settings, JsonObject & root);

View File

@@ -24,7 +24,9 @@ namespace emsesp {
WebStatusService::WebStatusService(AsyncWebServer * server, SecurityManager * securityManager) {
// rest endpoint for web page
server->on(EMSESP_STATUS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WebStatusService::webStatusService, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
server->on(EMSESP_STATUS_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WebStatusService::webStatusService, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
WiFi.onEvent(std::bind(&WebStatusService::WiFiEvent, this, _1, _2));
}