mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-09 01:09:51 +03:00
Merge branch 'v2_cmd' into v2
This commit is contained in:
@@ -22,6 +22,8 @@
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
EMSESPDevicesService::EMSESPDevicesService(AsyncWebServer * server, SecurityManager * securityManager)
|
||||
: _device_dataHandler(DEVICE_DATA_SERVICE_PATH,
|
||||
securityManager->wrapCallback(std::bind(&EMSESPDevicesService::device_data, this, _1, _2), AuthenticationPredicates::IS_AUTHENTICATED)) {
|
||||
@@ -39,7 +41,7 @@ EMSESPDevicesService::EMSESPDevicesService(AsyncWebServer * server, SecurityMana
|
||||
}
|
||||
|
||||
void EMSESPDevicesService::scan_devices(AsyncWebServerRequest * request) {
|
||||
EMSESP::send_read_request(EMSdevice::EMS_TYPE_UBADevices, EMSdevice::EMS_DEVICE_ID_BOILER);
|
||||
EMSESP::scan_devices();
|
||||
request->send(200);
|
||||
}
|
||||
|
||||
@@ -55,7 +57,7 @@ void EMSESPDevicesService::all_devices(AsyncWebServerRequest * request) {
|
||||
obj["type"] = emsdevice->device_type_name();
|
||||
obj["brand"] = emsdevice->brand_to_string();
|
||||
obj["name"] = emsdevice->name();
|
||||
obj["deviceid"] = emsdevice->device_id();
|
||||
obj["deviceid"] = emsdevice->get_device_id();
|
||||
obj["productid"] = emsdevice->product_id();
|
||||
obj["version"] = emsdevice->version();
|
||||
}
|
||||
@@ -76,10 +78,9 @@ void EMSESPDevicesService::all_devices(AsyncWebServerRequest * request) {
|
||||
|
||||
void EMSESPDevicesService::device_data(AsyncWebServerRequest * request, JsonVariant & json) {
|
||||
if (json.is<JsonObject>()) {
|
||||
uint8_t id = json["id"]; // get id from selected table row
|
||||
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_EMSESP_DEVICE_SIZE);
|
||||
#ifndef EMSESP_STANDALONE
|
||||
uint8_t id = json["id"]; // get id from selected table row
|
||||
EMSESP::device_info(id, (JsonObject &)response->getRoot());
|
||||
#endif
|
||||
response->setLength();
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <SecurityManager.h>
|
||||
|
||||
// #define MAX_EMSESP_STATUS_SIZE 1024
|
||||
#define MAX_EMSESP_DEVICE_SIZE 1280
|
||||
#define MAX_EMSESP_DEVICE_SIZE 1536
|
||||
|
||||
#define EMSESP_DEVICES_SERVICE_PATH "/rest/allDevices"
|
||||
#define SCAN_DEVICES_SERVICE_PATH "/rest/scanDevices"
|
||||
@@ -33,8 +32,6 @@
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
using namespace std::placeholders; // for `_1`
|
||||
|
||||
class EMSESPDevicesService {
|
||||
public:
|
||||
EMSESPDevicesService(AsyncWebServer * server, SecurityManager * securityManager);
|
||||
|
||||
@@ -51,10 +51,11 @@ StateUpdateResult EMSESPSettings::update(JsonObject & root, EMSESPSettings & set
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
|
||||
// this is called after the settings have been persisted to the filesystem
|
||||
// this is called after any of the settings have been persisted to the filesystem
|
||||
// either via the Web UI or via the Console
|
||||
void EMSESPSettingsService::onUpdate() {
|
||||
EMSESP::shower_.start();
|
||||
EMSESP::system_.syslog_init();
|
||||
// EMSESP::system_.syslog_init(); // changing SysLog will require a restart
|
||||
EMSESP::reset_tx();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#include "emsesp.h"
|
||||
#include "version.h"
|
||||
|
||||
#ifdef EMSESP_STANDALONE
|
||||
#if defined(EMSESP_DEBUG)
|
||||
#include "test/test.h"
|
||||
#endif
|
||||
|
||||
@@ -140,6 +140,11 @@ void EMSESPShell::add_console_commands() {
|
||||
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::ADMIN,
|
||||
@@ -180,21 +185,22 @@ void EMSESPShell::add_console_commands() {
|
||||
EMSESP::emsespSettingsService.update(
|
||||
[&](EMSESPSettings & settings) {
|
||||
settings.tx_mode = tx_mode;
|
||||
shell.printfln(F_(tx_mode_fmt), settings.tx_mode);
|
||||
return StateUpdateResult::CHANGED;
|
||||
},
|
||||
"local");
|
||||
EMSESP::reset_tx(); // reset counters and set tx_mode
|
||||
});
|
||||
|
||||
commands->add_command(ShellContext::MAIN,
|
||||
CommandFlags::USER,
|
||||
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::send_read_request(EMSdevice::EMS_TYPE_UBADevices, EMSdevice::EMS_DEVICE_ID_BOILER);
|
||||
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
|
||||
@@ -283,13 +289,17 @@ 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) {
|
||||
#ifdef EMSESP_STANDALONE
|
||||
#if defined(EMSESP_DEBUG)
|
||||
EMSESPShell::commands->add_command(context,
|
||||
CommandFlags::ADMIN,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(test)},
|
||||
flash_string_vector{F_(name_mandatory)},
|
||||
flash_string_vector{F_(name_optional)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
|
||||
Test::run_test(shell, arguments.front());
|
||||
if (arguments.size() == 0) {
|
||||
Test::run_test(shell, "default");
|
||||
} else {
|
||||
Test::run_test(shell, arguments.front());
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
@@ -307,8 +317,17 @@ void Console::load_standard_commands(unsigned int context) {
|
||||
shell.printfln(F_(invalid_log_level));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
shell.print(F("levels: "));
|
||||
std::vector<std::string> v = uuid::log::levels_lowercase();
|
||||
size_t i = v.size();
|
||||
while (i--) {
|
||||
shell.printf(v[i].c_str());
|
||||
shell.print(' ');
|
||||
}
|
||||
shell.println();
|
||||
}
|
||||
shell.printfln(F_(log_level_fmt), uuid::log::format_level_uppercase(shell.log_level()));
|
||||
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();
|
||||
@@ -362,7 +381,7 @@ void Console::load_standard_commands(unsigned int context) {
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(context,
|
||||
CommandFlags::USER,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(send), F_(telegram)},
|
||||
flash_string_vector{F_(data_mandatory)},
|
||||
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
|
||||
@@ -372,29 +391,32 @@ void Console::load_standard_commands(unsigned int context) {
|
||||
EMSESPShell::commands->add_command(context,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(watch)},
|
||||
flash_string_vector{F_(watch_format_mandatory), F_(watchid_optional)},
|
||||
flash_string_vector{F_(watch_format_optional), F_(watchid_optional)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
// get raw/pretty
|
||||
if (arguments[0] == read_flash_string(F_(raw))) {
|
||||
emsesp::EMSESP::watch(EMSESP::WATCH_RAW); // raw
|
||||
} else if (arguments[0] == read_flash_string(F_(on))) {
|
||||
emsesp::EMSESP::watch(EMSESP::WATCH_ON); // on
|
||||
} else if (arguments[0] == read_flash_string(F_(off))) {
|
||||
emsesp::EMSESP::watch(EMSESP::WATCH_OFF); // off
|
||||
} else {
|
||||
shell.printfln(F_(invalid_watch));
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t watch_id;
|
||||
if (arguments.size() == 2) {
|
||||
// get the watch_id if its set
|
||||
watch_id = Helpers::hextoint(arguments[1].c_str());
|
||||
} else {
|
||||
watch_id = WATCH_ID_NONE;
|
||||
}
|
||||
|
||||
emsesp::EMSESP::watch_id(watch_id);
|
||||
if (!arguments.empty()) {
|
||||
// get raw/pretty
|
||||
if (arguments[0] == read_flash_string(F_(raw))) {
|
||||
emsesp::EMSESP::watch(EMSESP::WATCH_RAW); // raw
|
||||
} else if (arguments[0] == read_flash_string(F_(on))) {
|
||||
emsesp::EMSESP::watch(EMSESP::WATCH_ON); // on
|
||||
} else if (arguments[0] == read_flash_string(F_(off))) {
|
||||
emsesp::EMSESP::watch(EMSESP::WATCH_OFF); // off
|
||||
} else {
|
||||
shell.printfln(F_(invalid_watch));
|
||||
return;
|
||||
}
|
||||
|
||||
if (arguments.size() == 2) {
|
||||
// get the watch_id if its set
|
||||
watch_id = Helpers::hextoint(arguments[1].c_str());
|
||||
} else {
|
||||
watch_id = WATCH_ID_NONE;
|
||||
}
|
||||
|
||||
emsesp::EMSESP::watch_id(watch_id);
|
||||
}
|
||||
|
||||
uint8_t watch = emsesp::EMSESP::watch();
|
||||
if (watch == EMSESP::WATCH_OFF) {
|
||||
@@ -516,9 +538,11 @@ void Console::start() {
|
||||
shell->start();
|
||||
#endif
|
||||
|
||||
#ifndef ESP8266
|
||||
#if defined(EMSESP_DEBUG)
|
||||
shell->log_level(uuid::log::Level::DEBUG); // order is: err, warning, notice, info, debug, trace, all
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
// always start in su/admin mode when running tests
|
||||
|
||||
@@ -50,69 +50,8 @@ using uuid::log::Level;
|
||||
#define F_(string_name) FPSTR(__pstr__##string_name)
|
||||
// clang-format on
|
||||
|
||||
// common words
|
||||
MAKE_PSTR_WORD(exit)
|
||||
MAKE_PSTR_WORD(help)
|
||||
MAKE_PSTR_WORD(settings)
|
||||
MAKE_PSTR_WORD(log)
|
||||
MAKE_PSTR_WORD(logout)
|
||||
MAKE_PSTR_WORD(enabled)
|
||||
MAKE_PSTR_WORD(disabled)
|
||||
MAKE_PSTR_WORD(yes)
|
||||
MAKE_PSTR_WORD(no)
|
||||
MAKE_PSTR_WORD(set)
|
||||
MAKE_PSTR_WORD(show)
|
||||
MAKE_PSTR_WORD(on)
|
||||
MAKE_PSTR_WORD(off)
|
||||
MAKE_PSTR_WORD(su)
|
||||
MAKE_PSTR_WORD(name)
|
||||
MAKE_PSTR_WORD(auto)
|
||||
MAKE_PSTR_WORD(scan)
|
||||
MAKE_PSTR_WORD(password)
|
||||
MAKE_PSTR_WORD(read)
|
||||
MAKE_PSTR_WORD(version)
|
||||
MAKE_PSTR_WORD(values)
|
||||
MAKE_PSTR_WORD(system)
|
||||
MAKE_PSTR_WORD(fetch)
|
||||
MAKE_PSTR_WORD(restart)
|
||||
MAKE_PSTR_WORD(format)
|
||||
MAKE_PSTR_WORD(raw)
|
||||
MAKE_PSTR_WORD(watch)
|
||||
MAKE_PSTR_WORD(mqtt)
|
||||
MAKE_PSTR_WORD(send)
|
||||
MAKE_PSTR_WORD(telegram)
|
||||
MAKE_PSTR_WORD(bus_id)
|
||||
MAKE_PSTR_WORD(tx_mode)
|
||||
MAKE_PSTR_WORD(ems)
|
||||
MAKE_PSTR_WORD(devices)
|
||||
|
||||
MAKE_PSTR(deep_optional, "[deep]")
|
||||
MAKE_PSTR(tx_mode_fmt, "Tx mode = %d")
|
||||
MAKE_PSTR(bus_id_fmt, "Bus ID = %02X")
|
||||
MAKE_PSTR(watchid_optional, "[ID]")
|
||||
MAKE_PSTR(watch_format_mandatory, "<off | on | raw>")
|
||||
MAKE_PSTR(invalid_watch, "Invalid watch type")
|
||||
MAKE_PSTR(data_mandatory, "<\"XX XX ...\">")
|
||||
MAKE_PSTR(percent, "%")
|
||||
MAKE_PSTR(degrees, "°C")
|
||||
MAKE_PSTR(degrees_mandatory, "<degrees>")
|
||||
MAKE_PSTR(asterisks, "********")
|
||||
MAKE_PSTR(n_mandatory, "<n>")
|
||||
MAKE_PSTR(n_optional, "[n]")
|
||||
MAKE_PSTR(bool_mandatory, "<on | off>")
|
||||
MAKE_PSTR(typeid_mandatory, "<type ID>")
|
||||
MAKE_PSTR(deviceid_mandatory, "<device ID>")
|
||||
MAKE_PSTR(deviceid_optional, "[device ID]")
|
||||
MAKE_PSTR(invalid_log_level, "Invalid log level")
|
||||
MAKE_PSTR(port_mandatory, "<port>")
|
||||
MAKE_PSTR(log_level_fmt, "Log level = %s")
|
||||
MAKE_PSTR(log_level_optional, "[level]")
|
||||
MAKE_PSTR(name_mandatory, "<name>")
|
||||
MAKE_PSTR(name_optional, "[name]")
|
||||
MAKE_PSTR(new_password_prompt1, "Enter new password: ")
|
||||
MAKE_PSTR(new_password_prompt2, "Retype new password: ")
|
||||
MAKE_PSTR(password_prompt, "Password: ")
|
||||
MAKE_PSTR(unset, "<unset>")
|
||||
// localizations
|
||||
#include "locale_EN.h"
|
||||
|
||||
#ifdef LOCAL
|
||||
#undef LOCAL
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
{115, DeviceType::BOILER, F("Topline/GB162"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{122, DeviceType::BOILER, F("Proline"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{123, DeviceType::BOILER, F("GBx72/Trendline/Cerapur/Greenstar Si/27i"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{131, DeviceType::BOILER, F("GB212"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{133, DeviceType::BOILER, F("GB125/Logamatic MC110"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{170, DeviceType::BOILER, F("Logano GB212"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{172, DeviceType::BOILER, F("Enviline/Compress 6000AW"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
@@ -39,11 +40,28 @@
|
||||
{208, DeviceType::BOILER, F("Logamax Plus/GB192/Condens GC9000"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{234, DeviceType::BOILER, F("Logamax Plus GB122"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
|
||||
// Controllers - 0x09 / 0x10 / 0x50
|
||||
{ 68, DeviceType::CONTROLLER, F("BC10/RFM20"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{ 89, DeviceType::CONTROLLER, F("BC10 GB142"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{ 95, DeviceType::CONTROLLER, F("HT3"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{114, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{125, DeviceType::CONTROLLER, F("BC25"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{152, DeviceType::CONTROLLER, F("Controller"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{169, DeviceType::CONTROLLER, F("BC40"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{190, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{194, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{207, DeviceType::CONTROLLER, F("Sense II/CS200"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x10
|
||||
{209, DeviceType::CONTROLLER, F("ErP"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{218, DeviceType::CONTROLLER, F("M200/RFM200"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x50
|
||||
{224, DeviceType::CONTROLLER, F("9000i"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{230, DeviceType::CONTROLLER, F("BC Base"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{241, DeviceType::CONTROLLER, F("Condens 5000i"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
|
||||
// Thermostat - not currently supporting write operations, like the Easy/100 types - 0x18
|
||||
{202, DeviceType::THERMOSTAT, F("Logamatic TC100/Moduline Easy"), DeviceFlags::EMS_DEVICE_FLAG_EASY | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write
|
||||
{203, DeviceType::THERMOSTAT, F("EasyControl CT200"), DeviceFlags::EMS_DEVICE_FLAG_EASY | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write
|
||||
|
||||
// Thermostat - Common for Buderus/Nefit/Bosch specific - 0x17 / 0x10 / 0x18
|
||||
// Thermostat - Buderus/Nefit/Bosch specific - 0x17 / 0x10 / 0x18 / 0x19
|
||||
{ 67, DeviceType::THERMOSTAT, F("RC30"), DeviceFlags::EMS_DEVICE_FLAG_RC30_1},// 0x10 - based on RC35
|
||||
{ 77, DeviceType::THERMOSTAT, F("RC20/Moduline 300"), DeviceFlags::EMS_DEVICE_FLAG_RC20},// 0x17
|
||||
{ 78, DeviceType::THERMOSTAT, F("Moduline 400"), DeviceFlags::EMS_DEVICE_FLAG_RC30}, // 0x10
|
||||
@@ -51,6 +69,7 @@
|
||||
{ 80, DeviceType::THERMOSTAT, F("Moduline 200"), DeviceFlags::EMS_DEVICE_FLAG_RC10}, // 0x17
|
||||
{ 86, DeviceType::THERMOSTAT, F("RC35"), DeviceFlags::EMS_DEVICE_FLAG_RC35}, // 0x10
|
||||
{ 93, DeviceType::THERMOSTAT, F("RC20RF"), DeviceFlags::EMS_DEVICE_FLAG_RC20}, // 0x19
|
||||
{ 94, DeviceType::THERMOSTAT, F("RFM20 Remote"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x18
|
||||
{157, DeviceType::THERMOSTAT, F("RC200/CW100"), DeviceFlags::EMS_DEVICE_FLAG_RC100}, // 0x18
|
||||
{158, DeviceType::THERMOSTAT, F("RC300/RC310/Moduline 3000/CW400/Sense II"), DeviceFlags::EMS_DEVICE_FLAG_RC300}, // 0x10
|
||||
{165, DeviceType::THERMOSTAT, F("RC100/Moduline 1000/1010"), DeviceFlags::EMS_DEVICE_FLAG_RC100}, // 0x18, 0x38
|
||||
@@ -87,22 +106,6 @@
|
||||
{200, DeviceType::HEATPUMP, F("HP Module"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{252, DeviceType::HEATPUMP, F("HP Module"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
|
||||
// Controllers - 0x09 / 0x10 / 0x50
|
||||
{ 68, DeviceType::CONTROLLER, F("BC10/RFM20"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{ 89, DeviceType::CONTROLLER, F("BC10 GB142"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{ 95, DeviceType::CONTROLLER, F("HT3"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{114, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{125, DeviceType::CONTROLLER, F("BC25"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{152, DeviceType::CONTROLLER, F("Controller"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{169, DeviceType::CONTROLLER, F("BC40"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{190, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{207, DeviceType::CONTROLLER, F("Sense II/CS200"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x10
|
||||
{209, DeviceType::CONTROLLER, F("ErP"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{218, DeviceType::CONTROLLER, F("M200/RFM200"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x50
|
||||
{224, DeviceType::CONTROLLER, F("9000i"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{230, DeviceType::CONTROLLER, F("BC Base"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{241, DeviceType::CONTROLLER, F("Condens 5000i"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
|
||||
// Connect devices - 0x02
|
||||
{171, DeviceType::CONNECT, F("OpenTherm Converter"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{205, DeviceType::CONNECT, F("Moduline Easy Connect"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
@@ -111,9 +114,7 @@
|
||||
// Switches - 0x11
|
||||
{ 71, DeviceType::SWITCH, F("WM10"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
|
||||
// Gateways - 0x48 / 0x18
|
||||
{ 94, DeviceType::GATEWAY, F("RFM20 Remote"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x18
|
||||
{189, DeviceType::GATEWAY, F("KM200/MB LAN 2"), DeviceFlags::EMS_DEVICE_FLAG_NONE} // 0x48
|
||||
|
||||
// Gateways - 0x48
|
||||
{189, DeviceType::GATEWAY, F("KM200/MB LAN 2"), DeviceFlags::EMS_DEVICE_FLAG_NONE}
|
||||
|
||||
// clang-format on
|
||||
|
||||
@@ -18,65 +18,52 @@
|
||||
|
||||
#include "boiler.h"
|
||||
|
||||
MAKE_PSTR_WORD(boiler)
|
||||
MAKE_PSTR_WORD(wwtemp)
|
||||
MAKE_PSTR_WORD(flowtemp)
|
||||
MAKE_PSTR_WORD(wwactive)
|
||||
MAKE_PSTR_WORD(wwonetime)
|
||||
MAKE_PSTR_WORD(wwcirculation)
|
||||
MAKE_PSTR_WORD(comfort)
|
||||
MAKE_PSTR_WORD(eco)
|
||||
MAKE_PSTR_WORD(intelligent)
|
||||
MAKE_PSTR_WORD(hot)
|
||||
MAKE_PSTR_WORD(maxpower)
|
||||
MAKE_PSTR_WORD(minpower)
|
||||
|
||||
MAKE_PSTR(comfort_mandatory, "<hot | eco | intelligent>")
|
||||
|
||||
// shower
|
||||
MAKE_PSTR_WORD(shower)
|
||||
MAKE_PSTR_WORD(timer)
|
||||
MAKE_PSTR_WORD(alert)
|
||||
MAKE_PSTR(shower_timer_fmt, "Shower Timer is %s")
|
||||
MAKE_PSTR(shower_alert_fmt, "Shower Alert is %s")
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
REGISTER_FACTORY(Boiler, EMSdevice::DeviceType::BOILER)
|
||||
MAKE_PSTR(logger_name, "boiler")
|
||||
uuid::log::Logger Boiler::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
|
||||
uuid::log::Logger Boiler::logger_{F_(boiler), uuid::log::Facility::CONSOLE};
|
||||
|
||||
Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
|
||||
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
|
||||
LOG_DEBUG(F("Registering new Boiler with device ID 0x%02X"), device_id);
|
||||
this->reserve_mem(20); // reserve some space for the telegram registries, to avoid memory fragmentation
|
||||
|
||||
LOG_DEBUG(F("Adding new Boiler with device ID 0x%02X"), device_id);
|
||||
|
||||
// the telegram handlers...
|
||||
register_telegram_type(0x10, F("UBAErrorMessage1"), false, std::bind(&Boiler::process_UBAErrorMessage, this, _1));
|
||||
register_telegram_type(0x11, F("UBAErrorMessage2"), false, std::bind(&Boiler::process_UBAErrorMessage, this, _1));
|
||||
register_telegram_type(0x18, F("UBAMonitorFast"), false, std::bind(&Boiler::process_UBAMonitorFast, this, _1));
|
||||
register_telegram_type(0x19, F("UBAMonitorSlow"), true, std::bind(&Boiler::process_UBAMonitorSlow, this, _1));
|
||||
register_telegram_type(0x34, F("UBAMonitorWW"), false, std::bind(&Boiler::process_UBAMonitorWW, this, _1));
|
||||
register_telegram_type(0x1C, F("UBAMaintenanceStatus"), false, std::bind(&Boiler::process_UBAMaintenanceStatus, this, _1));
|
||||
register_telegram_type(0x2A, F("MC10Status"), false, std::bind(&Boiler::process_MC10Status, this, _1));
|
||||
register_telegram_type(0x33, F("UBAParameterWW"), true, std::bind(&Boiler::process_UBAParameterWW, this, _1));
|
||||
register_telegram_type(0x14, F("UBATotalUptime"), false, std::bind(&Boiler::process_UBATotalUptime, this, _1));
|
||||
register_telegram_type(0x35, F("UBAFlags"), false, std::bind(&Boiler::process_UBAFlags, this, _1));
|
||||
register_telegram_type(0x15, F("UBAMaintenanceData"), false, std::bind(&Boiler::process_UBAMaintenanceData, this, _1));
|
||||
register_telegram_type(0x16, F("UBAParameters"), true, std::bind(&Boiler::process_UBAParameters, this, _1));
|
||||
register_telegram_type(0x1A, F("UBASetPoints"), false, std::bind(&Boiler::process_UBASetPoints, this, _1));
|
||||
register_telegram_type(0xD1, F("UBAOutdoorTemp"), false, std::bind(&Boiler::process_UBAOutdoorTemp, this, _1));
|
||||
register_telegram_type(0xE3, F("UBAMonitorSlowPlus"), false, std::bind(&Boiler::process_UBAMonitorSlowPlus2, this, _1));
|
||||
register_telegram_type(0xE4, F("UBAMonitorFastPlus"), false, std::bind(&Boiler::process_UBAMonitorFastPlus, this, _1));
|
||||
register_telegram_type(0xE5, F("UBAMonitorSlowPlus"), false, std::bind(&Boiler::process_UBAMonitorSlowPlus, this, _1));
|
||||
register_telegram_type(0x10, F("UBAErrorMessage1"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAErrorMessage(t); });
|
||||
register_telegram_type(0x11, F("UBAErrorMessage2"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAErrorMessage(t); });
|
||||
register_telegram_type(0x18, F("UBAMonitorFast"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAMonitorFast(t); });
|
||||
register_telegram_type(0x19, F("UBAMonitorSlow"), true, [&](std::shared_ptr<const Telegram> t) { process_UBAMonitorSlow(t); });
|
||||
register_telegram_type(0x34, F("UBAMonitorWW"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAMonitorWW(t); });
|
||||
register_telegram_type(0x1C, F("UBAMaintenanceStatus"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAMaintenanceStatus(t); });
|
||||
register_telegram_type(0x2A, F("MC10Status"), false, [&](std::shared_ptr<const Telegram> t) { process_MC10Status(t); });
|
||||
register_telegram_type(0x33, F("UBAParameterWW"), true, [&](std::shared_ptr<const Telegram> t) { process_UBAParameterWW(t); });
|
||||
register_telegram_type(0x14, F("UBATotalUptime"), false, [&](std::shared_ptr<const Telegram> t) { process_UBATotalUptime(t); });
|
||||
register_telegram_type(0x35, F("UBAFlags"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAFlags(t); });
|
||||
register_telegram_type(0x15, F("UBAMaintenanceData"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAMaintenanceData(t); });
|
||||
register_telegram_type(0x16, F("UBAParameters"), true, [&](std::shared_ptr<const Telegram> t) { process_UBAParameters(t); });
|
||||
register_telegram_type(0x1A, F("UBASetPoints"), false, [&](std::shared_ptr<const Telegram> t) { process_UBASetPoints(t); });
|
||||
register_telegram_type(0xD1, F("UBAOutdoorTemp"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAOutdoorTemp(t); });
|
||||
register_telegram_type(0xE3, F("UBAMonitorSlowPlus"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAMonitorSlowPlus2(t); });
|
||||
register_telegram_type(0xE4, F("UBAMonitorFastPlus"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAMonitorFastPlus(t); });
|
||||
register_telegram_type(0xE5, F("UBAMonitorSlowPlus"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAMonitorSlowPlus(t); });
|
||||
register_telegram_type(0xE9, F("UBADHWStatus"), false, [&](std::shared_ptr<const Telegram> t) { process_UBADHWStatus(t); });
|
||||
|
||||
register_telegram_type(0xE9, F("UBADHWStatus"), false, std::bind(&Boiler::process_UBADHWStatus, this, _1));
|
||||
|
||||
// MQTT callbacks
|
||||
register_mqtt_topic("boiler_cmd", std::bind(&Boiler::boiler_cmd, this, _1));
|
||||
register_mqtt_topic("boiler_cmd_wwactivated", std::bind(&Boiler::boiler_cmd_wwactivated, this, _1));
|
||||
register_mqtt_topic("boiler_cmd_wwonetime", std::bind(&Boiler::boiler_cmd_wwonetime, this, _1));
|
||||
register_mqtt_topic("boiler_cmd_wwcirculation", std::bind(&Boiler::boiler_cmd_wwcirculation, this, _1));
|
||||
register_mqtt_topic("boiler_cmd_wwtemp", std::bind(&Boiler::boiler_cmd_wwtemp, this, _1));
|
||||
// MQTT commands for boiler_cmd topic
|
||||
register_mqtt_cmd(F("comfort"), [&](const char * value, const int8_t id) { set_warmwater_mode(value, id); });
|
||||
register_mqtt_cmd(F("wwactivated"), [&](const char * value, const int8_t id) { set_warmwater_activated(value, id); });
|
||||
register_mqtt_cmd(F("wwtapactivated"), [&](const char * value, const int8_t id) { set_tapwarmwater_activated(value, id); });
|
||||
register_mqtt_cmd(F("wwonetime"), [&](const char * value, const int8_t id) { set_warmwater_onetime(value, id); });
|
||||
register_mqtt_cmd(F("wwcirculation"), [&](const char * value, const int8_t id) { set_warmwater_circulation(value, id); });
|
||||
register_mqtt_cmd(F("flowtemp"), [&](const char * value, const int8_t id) { set_flow_temp(value, id); });
|
||||
register_mqtt_cmd(F("wwtemp"), [&](const char * value, const int8_t id) { set_warmwater_temp(value, id); });
|
||||
register_mqtt_cmd(F("burnmaxpower"), [&](const char * value, const int8_t id) { set_max_power(value, id); });
|
||||
register_mqtt_cmd(F("burnminpower"), [&](const char * value, const int8_t id) { set_min_power(value, id); });
|
||||
register_mqtt_cmd(F("boilhyston"), [&](const char * value, const int8_t id) { set_hyst_on(value, id); });
|
||||
register_mqtt_cmd(F("boilhystoff"), [&](const char * value, const int8_t id) { set_hyst_off(value, id); });
|
||||
register_mqtt_cmd(F("burnperiod"), [&](const char * value, const int8_t id) { set_burn_period(value, id); });
|
||||
register_mqtt_cmd(F("pumpdelay"), [&](const char * value, const int8_t id) { set_pump_delay(value, id); });
|
||||
}
|
||||
|
||||
// add submenu context
|
||||
@@ -86,166 +73,24 @@ void Boiler::add_context_menu() {
|
||||
flash_string_vector{F_(boiler)},
|
||||
[&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
|
||||
Boiler::console_commands(shell, ShellContext::BOILER);
|
||||
add_context_commands(ShellContext::BOILER);
|
||||
});
|
||||
}
|
||||
|
||||
// boiler_cmd topic
|
||||
void Boiler::boiler_cmd(const char * message) {
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
|
||||
DeserializationError error = deserializeJson(doc, message);
|
||||
if (error) {
|
||||
LOG_DEBUG(F("MQTT error: payload %s, error %s"), message, error.c_str());
|
||||
return;
|
||||
}
|
||||
if (nullptr != doc["flowtemp"]) {
|
||||
uint8_t t = doc["flowtemp"];
|
||||
set_flow_temp(t);
|
||||
}
|
||||
if (nullptr != doc["wwtemp"]) {
|
||||
uint8_t t = doc["wwtemp"];
|
||||
set_warmwater_temp(t);
|
||||
}
|
||||
if (nullptr != doc["boilhyston"]) {
|
||||
int8_t t = doc["boilhyston"];
|
||||
set_hyst_on(t);
|
||||
}
|
||||
if (nullptr != doc["boilhystoff"]) {
|
||||
uint8_t t = doc["boilhystoff"];
|
||||
set_hyst_off(t);
|
||||
}
|
||||
if (nullptr != doc["burnperiod"]) {
|
||||
uint8_t t = doc["burnperiod"];
|
||||
set_burn_period(t);
|
||||
}
|
||||
if (nullptr != doc["burnminpower"]) {
|
||||
uint8_t p = doc["burnminpower"];
|
||||
set_min_power(p);
|
||||
}
|
||||
if (nullptr != doc["burnmaxpower"]) {
|
||||
uint8_t p = doc["burnmaxpower"];
|
||||
set_max_power(p);
|
||||
}
|
||||
if (nullptr != doc["pumpdelay"]) {
|
||||
uint8_t t = doc["pumpdelay"];
|
||||
set_pump_delay(t);
|
||||
}
|
||||
|
||||
if (nullptr != doc["comfort"]) {
|
||||
const char * data = doc["comfort"];
|
||||
if (strcmp((char *)data, "hot") == 0) {
|
||||
set_warmwater_mode(1);
|
||||
} else if (strcmp((char *)data, "eco") == 0) {
|
||||
set_warmwater_mode(2);
|
||||
} else if (strcmp((char *)data, "intelligent") == 0) {
|
||||
set_warmwater_mode(3);
|
||||
}
|
||||
}
|
||||
|
||||
const char * command = doc["cmd"];
|
||||
if (command == nullptr || doc["data"] == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// boiler ww comfort setting
|
||||
if (strcmp(command, "comfort") == 0) {
|
||||
const char * data = doc["data"];
|
||||
if (strcmp((char *)data, "hot") == 0) {
|
||||
set_warmwater_mode(1);
|
||||
} else if (strcmp((char *)data, "eco") == 0) {
|
||||
set_warmwater_mode(2);
|
||||
} else if (strcmp((char *)data, "intelligent") == 0) {
|
||||
set_warmwater_mode(3);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// boiler flowtemp setting
|
||||
if (strcmp(command, "flowtemp") == 0) {
|
||||
uint8_t t = doc["data"];
|
||||
set_flow_temp(t);
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "wwtemp") == 0) {
|
||||
uint8_t t = doc["data"];
|
||||
set_warmwater_temp(t);
|
||||
return;
|
||||
}
|
||||
// boiler max power setting
|
||||
if (strcmp(command, "burnmaxpower") == 0) {
|
||||
uint8_t p = doc["data"];
|
||||
set_max_power(p);
|
||||
return;
|
||||
}
|
||||
|
||||
// boiler min power setting
|
||||
if (strcmp(command, "burnminpower") == 0) {
|
||||
uint8_t p = doc["data"];
|
||||
set_min_power(p);
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "boilhyston") == 0) {
|
||||
int8_t t = doc["data"];
|
||||
set_hyst_on(t);
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "boilhystoff") == 0) {
|
||||
uint8_t t = doc["data"];
|
||||
set_hyst_off(t);
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "burnperiod") == 0) {
|
||||
uint8_t t = doc["data"];
|
||||
set_burn_period(t);
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "pumpdelay") == 0) {
|
||||
uint8_t t = doc["data"];
|
||||
set_pump_delay(t);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Boiler::boiler_cmd_wwactivated(const char * message) {
|
||||
if ((message[0] == '1' || strcmp(message, "on") == 0) || (strcmp(message, "auto") == 0)) {
|
||||
set_warmwater_activated(true);
|
||||
} else if (message[0] == '0' || strcmp(message, "off") == 0) {
|
||||
set_warmwater_activated(false);
|
||||
}
|
||||
}
|
||||
|
||||
void Boiler::boiler_cmd_wwonetime(const char * message) {
|
||||
if (message[0] == '1' || strcmp(message, "on") == 0) {
|
||||
set_warmwater_onetime(true);
|
||||
} else if (message[0] == '0' || strcmp(message, "off") == 0) {
|
||||
set_warmwater_onetime(false);
|
||||
}
|
||||
}
|
||||
|
||||
void Boiler::boiler_cmd_wwcirculation(const char * message) {
|
||||
if (message[0] == '1' || strcmp(message, "on") == 0) {
|
||||
set_warmwater_circulation(true);
|
||||
} else if (message[0] == '0' || strcmp(message, "off") == 0) {
|
||||
set_warmwater_circulation(false);
|
||||
}
|
||||
}
|
||||
|
||||
void Boiler::boiler_cmd_wwtemp(const char * message) {
|
||||
uint8_t t = atoi((char *)message);
|
||||
if (t) {
|
||||
set_warmwater_temp(t);
|
||||
}
|
||||
}
|
||||
|
||||
void Boiler::device_info(JsonArray & root) {
|
||||
JsonObject dataElement;
|
||||
|
||||
dataElement = root.createNestedObject();
|
||||
dataElement["name"] = F("Hot tap water");
|
||||
dataElement["value"] = tap_water_active_ ? F("running") : F("off");
|
||||
if (Helpers::hasValue(tap_water_active_, EMS_VALUE_BOOL)) {
|
||||
dataElement = root.createNestedObject();
|
||||
dataElement["name"] = F("Hot tap water");
|
||||
dataElement["value"] = tap_water_active_ ? F("running") : F("off");
|
||||
}
|
||||
|
||||
dataElement = root.createNestedObject();
|
||||
dataElement["name"] = F("Central heating");
|
||||
dataElement["value"] = heating_active_ ? F("active") : F("off");
|
||||
if (Helpers::hasValue(heating_active_, EMS_VALUE_BOOL)) {
|
||||
dataElement = root.createNestedObject();
|
||||
dataElement["name"] = F("Central heating");
|
||||
dataElement["value"] = heating_active_ ? F("active") : F("off");
|
||||
}
|
||||
|
||||
render_value_json(root, "", F("Selected flow temperature"), selFlowTemp_, F_(degrees));
|
||||
render_value_json(root, "", F("Current flow temperature"), curFlowTemp_, F_(degrees), 10);
|
||||
@@ -256,8 +101,9 @@ void Boiler::device_info(JsonArray & root) {
|
||||
|
||||
// publish values via MQTT
|
||||
void Boiler::publish_values() {
|
||||
const size_t capacity = JSON_OBJECT_SIZE(56); // must recalculate if more objects addded https://arduinojson.org/v6/assistant/
|
||||
DynamicJsonDocument doc(capacity);
|
||||
// const size_t capacity = JSON_OBJECT_SIZE(56); // must recalculate if more objects addded https://arduinojson.org/v6/assistant/
|
||||
// DynamicJsonDocument doc(capacity);
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc;
|
||||
|
||||
char s[10]; // for formatting strings
|
||||
|
||||
@@ -295,16 +141,16 @@ void Boiler::publish_values() {
|
||||
if (Helpers::hasValue(pumpMod2_)) {
|
||||
doc["pumpMod2"] = pumpMod2_;
|
||||
}
|
||||
if (Helpers::hasValue(wWCircPump_, VALUE_BOOL)) {
|
||||
if (Helpers::hasValue(wWCircPump_, EMS_VALUE_BOOL)) {
|
||||
doc["wWCircPump"] = Helpers::render_value(s, wWCircPump_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(wWCircPumpType_, VALUE_BOOL)) {
|
||||
if (Helpers::hasValue(wWCircPumpType_, EMS_VALUE_BOOL)) {
|
||||
doc["wWCiPuType"] = wWCircPumpType_ ? "valve" : "pump";
|
||||
}
|
||||
if (Helpers::hasValue(wWCircPumpMode_)) {
|
||||
doc["wWCiPuMode"] = wWCircPumpMode_;
|
||||
}
|
||||
if (Helpers::hasValue(wWCirc_, VALUE_BOOL)) {
|
||||
if (Helpers::hasValue(wWCirc_, EMS_VALUE_BOOL)) {
|
||||
doc["wWCirc"] = Helpers::render_value(s, wWCirc_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(extTemp_)) {
|
||||
@@ -340,43 +186,43 @@ void Boiler::publish_values() {
|
||||
if (Helpers::hasValue(exhaustTemp_)) {
|
||||
doc["exhaustTemp"] = (float)exhaustTemp_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(wWActivated_, VALUE_BOOL)) {
|
||||
if (Helpers::hasValue(wWActivated_, EMS_VALUE_BOOL)) {
|
||||
doc["wWActivated"] = Helpers::render_value(s, wWActivated_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(wWOneTime_, VALUE_BOOL)) {
|
||||
if (Helpers::hasValue(wWOneTime_, EMS_VALUE_BOOL)) {
|
||||
doc["wWOnetime"] = Helpers::render_value(s, wWOneTime_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(wWDesinfecting_, VALUE_BOOL)) {
|
||||
if (Helpers::hasValue(wWDesinfecting_, EMS_VALUE_BOOL)) {
|
||||
doc["wWDesinfecting"] = Helpers::render_value(s, wWDesinfecting_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(wWReadiness_, VALUE_BOOL)) {
|
||||
if (Helpers::hasValue(wWReadiness_, EMS_VALUE_BOOL)) {
|
||||
doc["wWReady"] = Helpers::render_value(s, wWReadiness_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(wWRecharging_, VALUE_BOOL)) {
|
||||
if (Helpers::hasValue(wWRecharging_, EMS_VALUE_BOOL)) {
|
||||
doc["wWRecharge"] = Helpers::render_value(s, wWRecharging_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(wWTemperatureOK_, VALUE_BOOL)) {
|
||||
if (Helpers::hasValue(wWTemperatureOK_, EMS_VALUE_BOOL)) {
|
||||
doc["wWTempOK"] = Helpers::render_value(s, wWTemperatureOK_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(wWCirc_, VALUE_BOOL)) {
|
||||
if (Helpers::hasValue(wWCirc_, EMS_VALUE_BOOL)) {
|
||||
doc["wWCirc"] = Helpers::render_value(s, wWCirc_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(burnGas_, VALUE_BOOL)) {
|
||||
if (Helpers::hasValue(burnGas_, EMS_VALUE_BOOL)) {
|
||||
doc["burnGas"] = Helpers::render_value(s, burnGas_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(flameCurr_)) {
|
||||
doc["flameCurr"] = (float)(int16_t)flameCurr_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(heatPmp_, VALUE_BOOL)) {
|
||||
if (Helpers::hasValue(heatPmp_, EMS_VALUE_BOOL)) {
|
||||
doc["heatPump"] = Helpers::render_value(s, heatPmp_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(fanWork_, VALUE_BOOL)) {
|
||||
if (Helpers::hasValue(fanWork_, EMS_VALUE_BOOL)) {
|
||||
doc["fanWork"] = Helpers::render_value(s, fanWork_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(ignWork_, VALUE_BOOL)) {
|
||||
if (Helpers::hasValue(ignWork_, EMS_VALUE_BOOL)) {
|
||||
doc["ignWork"] = Helpers::render_value(s, ignWork_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(wWHeat_, VALUE_BOOL)) {
|
||||
if (Helpers::hasValue(wWHeat_, EMS_VALUE_BOOL)) {
|
||||
doc["wWHeat"] = Helpers::render_value(s, wWHeat_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(heating_temp_)) {
|
||||
@@ -450,16 +296,16 @@ bool Boiler::updated_values() {
|
||||
void Boiler::show_values(uuid::console::Shell & shell) {
|
||||
EMSdevice::show_values(shell); // for showing the header
|
||||
|
||||
if (Helpers::hasValue(tap_water_active_, VALUE_BOOL)) {
|
||||
if (Helpers::hasValue(tap_water_active_, EMS_VALUE_BOOL)) {
|
||||
print_value(shell, 2, F("Hot tap water"), tap_water_active_ ? F("running") : F("off"));
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(heating_active_, VALUE_BOOL)) {
|
||||
if (Helpers::hasValue(heating_active_, EMS_VALUE_BOOL)) {
|
||||
print_value(shell, 2, F("Central heating"), heating_active_ ? F("active") : F("off"));
|
||||
}
|
||||
|
||||
print_value(shell, 2, F("Warm Water activated"), wWActivated_, nullptr, EMS_VALUE_BOOL);
|
||||
if (Helpers::hasValue(wWCircPumpType_, VALUE_BOOL)) {
|
||||
if (Helpers::hasValue(wWCircPumpType_, EMS_VALUE_BOOL)) {
|
||||
print_value(shell, 2, F("Warm Water charging type"), wWCircPumpType_ ? F("3-way valve") : F("charge pump"));
|
||||
}
|
||||
print_value(shell, 2, F("Warm Water circulation pump available"), wWCircPump_, nullptr, EMS_VALUE_BOOL);
|
||||
@@ -557,6 +403,8 @@ void Boiler::show_values(uuid::console::Shell & shell) {
|
||||
if (Helpers::hasValue(UBAuptime_)) {
|
||||
shell.printfln(F(" Total UBA working time: %d days %d hours %d minutes"), UBAuptime_ / 1440, (UBAuptime_ % 1440) / 60, UBAuptime_ % 60);
|
||||
}
|
||||
|
||||
shell.println();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -579,7 +427,7 @@ void Boiler::check_active() {
|
||||
|
||||
// see if the heating or hot tap water has changed, if so send
|
||||
// last_boilerActive stores heating in bit 1 and tap water in bit 2
|
||||
if (Helpers::hasValue(tap_water_active_, VALUE_BOOL) && Helpers::hasValue(heating_active_, VALUE_BOOL)) {
|
||||
if (Helpers::hasValue(tap_water_active_, EMS_VALUE_BOOL) && Helpers::hasValue(heating_active_, EMS_VALUE_BOOL)) {
|
||||
uint8_t latest_boilerState = (tap_water_active_ << 1) + heating_active_;
|
||||
if (latest_boilerState != last_boilerState) {
|
||||
last_boilerState = latest_boilerState;
|
||||
@@ -650,12 +498,12 @@ void Boiler::process_UBATotalUptime(std::shared_ptr<const Telegram> telegram) {
|
||||
*/
|
||||
void Boiler::process_UBAParameters(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(heating_temp_, 1);
|
||||
telegram->read_value(burnPowermax_,2);
|
||||
telegram->read_value(burnPowermin_,3);
|
||||
telegram->read_value(boilTemp_off_,4);
|
||||
telegram->read_value(boilTemp_on_,5);
|
||||
telegram->read_value(burnPeriod_,6);
|
||||
telegram->read_value(pumpDelay_,8);
|
||||
telegram->read_value(burnPowermax_, 2);
|
||||
telegram->read_value(burnPowermin_, 3);
|
||||
telegram->read_value(boilTemp_off_, 4);
|
||||
telegram->read_value(boilTemp_on_, 5);
|
||||
telegram->read_value(burnPeriod_, 6);
|
||||
telegram->read_value(pumpDelay_, 8);
|
||||
telegram->read_value(pump_mod_max_, 9);
|
||||
telegram->read_value(pump_mod_min_, 10);
|
||||
}
|
||||
@@ -804,7 +652,7 @@ void Boiler::process_UBAMaintenanceStatus(std::shared_ptr<const Telegram> telegr
|
||||
// 0x10, 0x11, 0x12
|
||||
// not yet implemented
|
||||
void Boiler::process_UBAErrorMessage(std::shared_ptr<const Telegram> telegram) {
|
||||
// data: displaycode(2), errornumner(2), year, month, hour, day, minute, duration(2), src-addr
|
||||
// data: displaycode(2), errornumber(2), year, month, hour, day, minute, duration(2), src-addr
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
@@ -818,67 +666,113 @@ void Boiler::process_UBAMaintenanceData(std::shared_ptr<const Telegram> telegram
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Commands
|
||||
*/
|
||||
|
||||
|
||||
// Set the warm water temperature 0x33
|
||||
void Boiler::set_warmwater_temp(const uint8_t temperature) {
|
||||
LOG_INFO(F("Setting boiler warm water temperature to %d C"), temperature);
|
||||
write_command(EMS_TYPE_UBAParameterWW, 2, temperature);
|
||||
write_command(EMS_TYPE_UBAFlags, 3, temperature); // for i9000, see #397
|
||||
void Boiler::set_warmwater_temp(const char * value, const int8_t id) {
|
||||
int v = 0;
|
||||
if (!Helpers::value2number(value, v)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(F("Setting boiler warm water temperature to %d C"), v);
|
||||
write_command(EMS_TYPE_UBAParameterWW, 2, v);
|
||||
write_command(EMS_TYPE_UBAFlags, 3, v); // for i9000, see #397
|
||||
}
|
||||
|
||||
// flow temp
|
||||
void Boiler::set_flow_temp(const uint8_t temperature) {
|
||||
LOG_INFO(F("Setting boiler flow temperature to %d C"), temperature);
|
||||
write_command(EMS_TYPE_UBASetPoints, 0, temperature);
|
||||
void Boiler::set_flow_temp(const char * value, const int8_t id) {
|
||||
int v = 0;
|
||||
if (!Helpers::value2number(value, v)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(F("Setting boiler flow temperature to %d C"), v);
|
||||
write_command(EMS_TYPE_UBASetPoints, 0, v);
|
||||
}
|
||||
|
||||
// set min boiler output
|
||||
void Boiler::set_min_power(const uint8_t power) {
|
||||
LOG_INFO(F("Setting boiler min power to "), power);
|
||||
write_command(EMS_TYPE_UBAParameters, 3, power);
|
||||
void Boiler::set_min_power(const char * value, const int8_t id) {
|
||||
int v = 0;
|
||||
if (!Helpers::value2number(value, v)) {
|
||||
return;
|
||||
}
|
||||
LOG_INFO(F("Setting boiler min power to "), v);
|
||||
write_command(EMS_TYPE_UBAParameters, 3, v);
|
||||
}
|
||||
|
||||
// set max temp
|
||||
void Boiler::set_max_power(const uint8_t power) {
|
||||
LOG_INFO(F("Setting boiler max power to %d C"), power);
|
||||
write_command(EMS_TYPE_UBAParameters, 2, power);
|
||||
void Boiler::set_max_power(const char * value, const int8_t id) {
|
||||
int v = 0;
|
||||
if (!Helpers::value2number(value, v)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(F("Setting boiler max power to %d C"), v);
|
||||
write_command(EMS_TYPE_UBAParameters, 2, v);
|
||||
}
|
||||
|
||||
// set oiler on hysteresis
|
||||
void Boiler::set_hyst_on(const uint8_t temp) {
|
||||
LOG_INFO(F("Setting boiler hysteresis on to %d C"), temp);
|
||||
write_command(EMS_TYPE_UBAParameters, 5, temp);
|
||||
void Boiler::set_hyst_on(const char * value, const int8_t id) {
|
||||
int v = 0;
|
||||
if (!Helpers::value2number(value, v)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(F("Setting boiler hysteresis on to %d C"), v);
|
||||
write_command(EMS_TYPE_UBAParameters, 5, v);
|
||||
}
|
||||
|
||||
// set boiler off hysteresis
|
||||
void Boiler::set_hyst_off(const uint8_t temp) {
|
||||
LOG_INFO(F("Setting boiler hysteresis off to %d C"), temp);
|
||||
write_command(EMS_TYPE_UBAParameters, 4, temp);
|
||||
void Boiler::set_hyst_off(const char * value, const int8_t id) {
|
||||
int v = 0;
|
||||
if (!Helpers::value2number(value, v)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(F("Setting boiler hysteresis off to %d C"), v);
|
||||
write_command(EMS_TYPE_UBAParameters, 4, v);
|
||||
}
|
||||
|
||||
// set min burner period
|
||||
void Boiler::set_burn_period(const uint8_t t) {
|
||||
LOG_INFO(F("Setting burner min. period to %d min"), t);
|
||||
write_command(EMS_TYPE_UBAParameters, 6, t);
|
||||
void Boiler::set_burn_period(const char * value, const int8_t id) {
|
||||
int v = 0;
|
||||
if (!Helpers::value2number(value, v)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(F("Setting burner min. period to %d min"), v);
|
||||
write_command(EMS_TYPE_UBAParameters, 6, v);
|
||||
}
|
||||
|
||||
// set pump delay
|
||||
void Boiler::set_pump_delay(const uint8_t t) {
|
||||
LOG_INFO(F("Setting boiler pump delay to %d min"), t);
|
||||
write_command(EMS_TYPE_UBAParameters, 8, t);
|
||||
void Boiler::set_pump_delay(const char * value, const int8_t id) {
|
||||
int v = 0;
|
||||
if (!Helpers::value2number(value, v)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(F("Setting boiler pump delay to %d min"), v);
|
||||
write_command(EMS_TYPE_UBAParameters, 8, v);
|
||||
}
|
||||
|
||||
// 1=hot, 2=eco, 3=intelligent
|
||||
// note some boilers do not have this setting, than it's done by thermostat
|
||||
// on a RC35 it's by EMSESP::send_write_request(0x37, 0x10, 2, &set, 1, 0); (set is 1,2,3)
|
||||
void Boiler::set_warmwater_mode(const uint8_t comfort) {
|
||||
// on a RC35 it's by EMSESP::send_write_request(0x37, 0x10, 2, &set, 1, 0); (set is 1,2,3) 1=hot, 2=eco, 3=intelligent
|
||||
void Boiler::set_warmwater_mode(const char * value, const int8_t id) {
|
||||
if (value == nullptr) {
|
||||
return;
|
||||
}
|
||||
uint8_t set;
|
||||
if (comfort == 1) {
|
||||
if (strcmp(value, "hot") == 0) {
|
||||
LOG_INFO(F("Setting boiler warm water to Hot"));
|
||||
set = 0x00;
|
||||
} else if (comfort == 2) {
|
||||
} else if (strcmp(value, "eco") == 0) {
|
||||
LOG_INFO(F("Setting boiler warm water to Eco"));
|
||||
set = 0xD8;
|
||||
} else if (comfort == 3) {
|
||||
} else if (strcmp(value, "intelligent") == 0) {
|
||||
LOG_INFO(F("Setting boiler warm water to Intelligent"));
|
||||
set = 0xEC;
|
||||
} else {
|
||||
@@ -888,24 +782,33 @@ void Boiler::set_warmwater_mode(const uint8_t comfort) {
|
||||
}
|
||||
|
||||
// turn on/off warm water
|
||||
void Boiler::set_warmwater_activated(const bool activated) {
|
||||
LOG_INFO(F("Setting boiler warm water %s"), activated ? "on" : "off");
|
||||
uint8_t value;
|
||||
void Boiler::set_warmwater_activated(const char * value, const int8_t id) {
|
||||
bool v = false;
|
||||
if (!Helpers::value2bool(value, v)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(F("Setting boiler warm water %s"), v ? "on" : "off");
|
||||
|
||||
// https://github.com/proddy/EMS-ESP/issues/268
|
||||
uint8_t n;
|
||||
if (EMSbus::is_ht3()) {
|
||||
value = (activated ? 0x08 : 0x00); // 0x08 is on, 0x00 is off
|
||||
n = (v ? 0x08 : 0x00); // 0x08 is on, 0x00 is off
|
||||
} else {
|
||||
value = (activated ? 0xFF : 0x00); // 0xFF is on, 0x00 is off
|
||||
n = (v ? 0xFF : 0x00); // 0xFF is on, 0x00 is off
|
||||
}
|
||||
write_command(EMS_TYPE_UBAParameterWW, 1, value);
|
||||
write_command(EMS_TYPE_UBAParameterWW, 1, n);
|
||||
}
|
||||
|
||||
// Activate / De-activate the Warm Tap Water
|
||||
// true = on, false = off
|
||||
// Note: Using the type 0x1D to put the boiler into Test mode. This may be shown on the boiler with a flashing 'T'
|
||||
void Boiler::set_tapwarmwater_activated(const bool activated) {
|
||||
LOG_INFO(F("Setting boiler warm tap water %s"), activated ? "on" : "off");
|
||||
void Boiler::set_tapwarmwater_activated(const char * value, const int8_t id) {
|
||||
bool v = false;
|
||||
if (!Helpers::value2bool(value, v)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(F("Setting tap warm tap water %s"), v ? "on" : "off");
|
||||
uint8_t message_data[EMS_MAX_TELEGRAM_MESSAGE_LENGTH];
|
||||
for (uint8_t i = 0; i < sizeof(message_data); i++) {
|
||||
message_data[i] = 0x00;
|
||||
@@ -914,7 +817,7 @@ void Boiler::set_tapwarmwater_activated(const bool activated) {
|
||||
// we use the special test mode 0x1D for this. Setting the first data to 5A puts the system into test mode and
|
||||
// a setting of 0x00 puts it back into normal operating mode
|
||||
// when in test mode we're able to mess around with the 3-way valve settings
|
||||
if (!activated) {
|
||||
if (!v) {
|
||||
// on
|
||||
message_data[0] = 0x5A; // test mode on
|
||||
message_data[1] = 0x00; // burner output 0%
|
||||
@@ -932,16 +835,26 @@ void Boiler::set_tapwarmwater_activated(const bool activated) {
|
||||
// Activate / De-activate One Time warm water 0x35
|
||||
// true = on, false = off
|
||||
// See also https://github.com/proddy/EMS-ESP/issues/341#issuecomment-596245458 for Junkers
|
||||
void Boiler::set_warmwater_onetime(const bool activated) {
|
||||
LOG_INFO(F("Setting boiler warm water OneTime loading %s"), activated ? "on" : "off");
|
||||
write_command(EMS_TYPE_UBAFlags, 0, (activated ? 0x22 : 0x02));
|
||||
void Boiler::set_warmwater_onetime(const char * value, const int8_t id) {
|
||||
bool v = false;
|
||||
if (!Helpers::value2bool(value, v)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(F("Setting boiler warm water OneTime loading %s"), v ? "on" : "off");
|
||||
write_command(EMS_TYPE_UBAFlags, 0, (v ? 0x22 : 0x02));
|
||||
}
|
||||
|
||||
// Activate / De-activate circulation of warm water 0x35
|
||||
// true = on, false = off
|
||||
void Boiler::set_warmwater_circulation(const bool activated) {
|
||||
LOG_INFO(F("Setting boiler warm water circulation %s"), activated ? "on" : "off");
|
||||
write_command(EMS_TYPE_UBAFlags, 1, (activated ? 0x22 : 0x02));
|
||||
void Boiler::set_warmwater_circulation(const char * value, const int8_t id) {
|
||||
bool v = false;
|
||||
if (!Helpers::value2bool(value, v)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(F("Setting boiler warm water circulation %s"), v ? "on" : "off");
|
||||
write_command(EMS_TYPE_UBAFlags, 1, (v ? 0x22 : 0x02));
|
||||
}
|
||||
|
||||
// add console commands
|
||||
@@ -952,118 +865,10 @@ void Boiler::console_commands(Shell & shell, unsigned int context) {
|
||||
flash_string_vector{F_(typeid_mandatory)},
|
||||
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
|
||||
uint16_t type_id = Helpers::hextoint(arguments.front().c_str());
|
||||
EMSESP::send_read_request(type_id, device_id());
|
||||
EMSESP::set_read_id(type_id);
|
||||
EMSESP::send_read_request(type_id, get_device_id());
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::BOILER,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(wwtemp)},
|
||||
flash_string_vector{F_(degrees_mandatory)},
|
||||
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
|
||||
set_warmwater_temp(Helpers::atoint(arguments.front().c_str()));
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::BOILER,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(flowtemp)},
|
||||
flash_string_vector{F_(degrees_mandatory)},
|
||||
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
|
||||
set_flow_temp(Helpers::atoint(arguments.front().c_str()));
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::BOILER,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(maxpower)},
|
||||
flash_string_vector{F_(n_mandatory)},
|
||||
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
|
||||
set_max_power(Helpers::atoint(arguments.front().c_str()));
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::BOILER,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(minpower)},
|
||||
flash_string_vector{F_(n_mandatory)},
|
||||
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
|
||||
set_min_power(Helpers::atoint(arguments.front().c_str()));
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(
|
||||
ShellContext::BOILER,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(wwactive)},
|
||||
flash_string_vector{F_(bool_mandatory)},
|
||||
[=](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
if (arguments[0] == read_flash_string(F_(on))) {
|
||||
set_warmwater_activated(true);
|
||||
} else if (arguments[0] == read_flash_string(F_(off))) {
|
||||
set_warmwater_activated(false);
|
||||
} else {
|
||||
shell.println(F("Must be on or off"));
|
||||
return;
|
||||
}
|
||||
},
|
||||
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> const std::vector<std::string> {
|
||||
return std::vector<std::string>{read_flash_string(F_(on)), read_flash_string(F_(off))};
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(
|
||||
ShellContext::BOILER,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(wwonetime)},
|
||||
flash_string_vector{F_(bool_mandatory)},
|
||||
[=](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
if (arguments[0] == read_flash_string(F_(on))) {
|
||||
set_warmwater_onetime(true);
|
||||
} else if (arguments[0] == read_flash_string(F_(off))) {
|
||||
set_warmwater_onetime(false);
|
||||
} else {
|
||||
shell.println(F("Must be on or off"));
|
||||
return;
|
||||
}
|
||||
},
|
||||
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> const std::vector<std::string> {
|
||||
return std::vector<std::string>{read_flash_string(F_(on)), read_flash_string(F_(off))};
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(
|
||||
ShellContext::BOILER,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(wwcirculation)},
|
||||
flash_string_vector{F_(bool_mandatory)},
|
||||
[=](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
if (arguments[0] == read_flash_string(F_(on))) {
|
||||
set_warmwater_circulation(true);
|
||||
} else if (arguments[0] == read_flash_string(F_(off))) {
|
||||
set_warmwater_circulation(false);
|
||||
} else {
|
||||
shell.println(F("Must be on or off"));
|
||||
return;
|
||||
}
|
||||
},
|
||||
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> const std::vector<std::string> {
|
||||
return std::vector<std::string>{read_flash_string(F_(on)), read_flash_string(F_(off))};
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(
|
||||
ShellContext::BOILER,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(comfort)},
|
||||
flash_string_vector{F_(comfort_mandatory)},
|
||||
[=](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
if (arguments[0] == read_flash_string(F_(hot))) {
|
||||
set_warmwater_mode(1);
|
||||
} else if (arguments[0] == read_flash_string(F_(eco))) {
|
||||
set_warmwater_mode(2);
|
||||
} else if (arguments[0] == read_flash_string(F_(intelligent))) {
|
||||
set_warmwater_mode(3);
|
||||
} else {
|
||||
shell.println(F("Invalid value. Must be hot, eco or intelligent"));
|
||||
}
|
||||
},
|
||||
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> const std::vector<std::string> {
|
||||
return std::vector<std::string>{read_flash_string(F_(hot)), read_flash_string(F_(eco)), read_flash_string(F_(intelligent))};
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::BOILER,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(show)},
|
||||
|
||||
@@ -124,7 +124,7 @@ class Boiler : public EMSdevice {
|
||||
uint8_t burnPowermin_ = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t burnPowermax_ = EMS_VALUE_UINT_NOTSET;
|
||||
int8_t boilTemp_off_ = EMS_VALUE_INT_NOTSET;
|
||||
int8_t boilTemp_on_ = EMS_VALUE_UINT_NOTSET;
|
||||
int8_t boilTemp_on_ = EMS_VALUE_INT_NOTSET;
|
||||
uint8_t burnPeriod_ = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t pumpDelay_ = EMS_VALUE_UINT_NOTSET;
|
||||
|
||||
@@ -160,27 +160,20 @@ class Boiler : public EMSdevice {
|
||||
|
||||
void check_active();
|
||||
|
||||
void set_warmwater_temp(const uint8_t temperature);
|
||||
void set_flow_temp(const uint8_t temperature);
|
||||
void set_warmwater_mode(const uint8_t comfort);
|
||||
void set_warmwater_activated(const bool activated);
|
||||
void set_tapwarmwater_activated(const bool activated);
|
||||
void set_warmwater_onetime(const bool activated);
|
||||
void set_warmwater_circulation(const bool activated);
|
||||
void set_min_power(const uint8_t power);
|
||||
void set_max_power(const uint8_t power);
|
||||
void set_hyst_on(const uint8_t temp);
|
||||
void set_hyst_off(const uint8_t temp);
|
||||
void set_burn_period(const uint8_t t);
|
||||
void set_pump_delay(const uint8_t t);
|
||||
|
||||
|
||||
// mqtt callbacks
|
||||
void boiler_cmd(const char * message);
|
||||
void boiler_cmd_wwactivated(const char * message);
|
||||
void boiler_cmd_wwonetime(const char * message);
|
||||
void boiler_cmd_wwcirculation(const char * message);
|
||||
void boiler_cmd_wwtemp(const char * message);
|
||||
// commands - none of these use the additional id parameter
|
||||
void set_warmwater_mode(const char * value, const int8_t id);
|
||||
void set_warmwater_activated(const char * value, const int8_t id);
|
||||
void set_tapwarmwater_activated(const char * value, const int8_t id);
|
||||
void set_warmwater_onetime(const char * value, const int8_t id);
|
||||
void set_warmwater_circulation(const char * value, const int8_t id);
|
||||
void set_warmwater_temp(const char * value, const int8_t id);
|
||||
void set_flow_temp(const char * value, const int8_t id);
|
||||
void set_min_power(const char * value, const int8_t id);
|
||||
void set_max_power(const char * value, const int8_t id);
|
||||
void set_hyst_on(const char * value, const int8_t id);
|
||||
void set_hyst_off(const char * value, const int8_t id);
|
||||
void set_burn_period(const char * value, const int8_t id);
|
||||
void set_pump_delay(const char * value, const int8_t id);
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -22,16 +22,10 @@ namespace emsesp {
|
||||
|
||||
REGISTER_FACTORY(Connect, EMSdevice::DeviceType::CONNECT);
|
||||
|
||||
MAKE_PSTR(logger_name, "connect")
|
||||
uuid::log::Logger Connect::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
uuid::log::Logger Connect::logger_{F_(connect), uuid::log::Facility::CONSOLE};
|
||||
|
||||
Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
|
||||
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
|
||||
// telegram handlers
|
||||
// register_telegram_type(EMS_TYPE_XX, "XX", false, std::bind(&Controller::process_XX, this, _1));
|
||||
|
||||
// MQTT callbacks
|
||||
// register_mqtt_topic("topic", std::bind(&Connect::cmd, this, _1));
|
||||
}
|
||||
|
||||
void Connect::device_info(JsonArray & root) {
|
||||
@@ -42,7 +36,7 @@ void Connect::add_context_menu() {
|
||||
|
||||
// display all values into the shell console
|
||||
void Connect::show_values(uuid::console::Shell & shell) {
|
||||
EMSdevice::show_values(shell); // always call this to show header
|
||||
// EMSdevice::show_values(shell); // always call this to show header
|
||||
}
|
||||
|
||||
// publish values via MQTT
|
||||
|
||||
@@ -18,22 +18,14 @@
|
||||
|
||||
#include "controller.h"
|
||||
|
||||
// MAKE_PSTR_WORD(controller)
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
REGISTER_FACTORY(Controller, EMSdevice::DeviceType::CONTROLLER);
|
||||
|
||||
MAKE_PSTR(logger_name, "controller")
|
||||
uuid::log::Logger Controller::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
uuid::log::Logger Controller::logger_{F_(controller), uuid::log::Facility::CONSOLE};
|
||||
|
||||
Controller::Controller(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
|
||||
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
|
||||
// telegram handlers
|
||||
// register_telegram_type(EMS_TYPE_XX, "XX", false, std::bind(&Controller::process_XX, this, _1));
|
||||
|
||||
// MQTT callbacks
|
||||
// register_mqtt_topic("topic", std::bind(&Controller::cmd, this, _1));
|
||||
}
|
||||
|
||||
void Controller::add_context_menu() {
|
||||
@@ -44,7 +36,7 @@ void Controller::device_info(JsonArray & root) {
|
||||
|
||||
// display all values into the shell console
|
||||
void Controller::show_values(uuid::console::Shell & shell) {
|
||||
EMSdevice::show_values(shell); // always call this to show header
|
||||
// EMSdevice::show_values(shell); // always call this to show header
|
||||
}
|
||||
|
||||
// publish values via MQTT
|
||||
|
||||
@@ -18,22 +18,14 @@
|
||||
|
||||
#include "gateway.h"
|
||||
|
||||
// MAKE_PSTR_WORD(gateway)
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
REGISTER_FACTORY(Gateway, EMSdevice::DeviceType::GATEWAY);
|
||||
|
||||
MAKE_PSTR(logger_name, "gateway")
|
||||
uuid::log::Logger Gateway::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
uuid::log::Logger Gateway::logger_{F_(gateway), uuid::log::Facility::CONSOLE};
|
||||
|
||||
Gateway::Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
|
||||
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
|
||||
// telegram handlers
|
||||
// register_telegram_type(EMS_TYPE_XX, "XX", false, std::bind(&Controller::process_XX, this, _1));
|
||||
|
||||
// MQTT callbacks
|
||||
// register_mqtt_topic("topic", std::bind(&Gateway::cmd, this, _1));
|
||||
}
|
||||
|
||||
void Gateway::add_context_menu() {
|
||||
|
||||
@@ -18,32 +18,19 @@
|
||||
|
||||
#include "heatpump.h"
|
||||
|
||||
// MAKE_PSTR_WORD(heatpump)
|
||||
|
||||
/*
|
||||
example telegrams 0x32B, 0x37B
|
||||
"38 10 FF 00 03 7B 08 24 00 4B",
|
||||
"38 10 FF 00 03 2B 00 C7 07 C3 01",
|
||||
"38 10 FF 00 03 2B 00 D1 08 2A 01",
|
||||
*/
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
REGISTER_FACTORY(Heatpump, EMSdevice::DeviceType::HEATPUMP);
|
||||
|
||||
MAKE_PSTR(logger_name, "heatpump")
|
||||
uuid::log::Logger Heatpump::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
uuid::log::Logger Heatpump::logger_{F_(heatpump), uuid::log::Facility::CONSOLE};
|
||||
|
||||
Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
|
||||
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
|
||||
LOG_DEBUG(F("Registering new Heat Pump module with device ID 0x%02X"), device_id);
|
||||
LOG_DEBUG(F("Adding new Heat Pump module with device ID 0x%02X"), device_id);
|
||||
|
||||
// telegram handlers
|
||||
register_telegram_type(0x047B, F("HP1"), true, std::bind(&Heatpump::process_HPMonitor1, this, _1));
|
||||
register_telegram_type(0x042B, F("HP2"), true, std::bind(&Heatpump::process_HPMonitor2, this, _1));
|
||||
|
||||
// MQTT callbacks
|
||||
// register_mqtt_topic("topic", std::bind(&Heatpump::cmd, this, _1));
|
||||
register_telegram_type(0x047B, F("HP1"), true, [&](std::shared_ptr<const Telegram> t) { process_HPMonitor1(t); });
|
||||
register_telegram_type(0x042B, F("HP2"), true, [&](std::shared_ptr<const Telegram> t) { process_HPMonitor2(t); });
|
||||
}
|
||||
|
||||
// context submenu
|
||||
@@ -55,7 +42,7 @@ void Heatpump::device_info(JsonArray & root) {
|
||||
|
||||
// display all values into the shell console
|
||||
void Heatpump::show_values(uuid::console::Shell & shell) {
|
||||
EMSdevice::show_values(shell); // always call this to show header
|
||||
// EMSdevice::show_values(shell); // always call this to show header
|
||||
}
|
||||
|
||||
// publish values via MQTT
|
||||
|
||||
@@ -22,35 +22,37 @@ namespace emsesp {
|
||||
|
||||
REGISTER_FACTORY(Mixing, EMSdevice::DeviceType::MIXING);
|
||||
|
||||
MAKE_PSTR(logger_name, "mixing")
|
||||
uuid::log::Logger Mixing::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
uuid::log::Logger Mixing::logger_{F_(mixing), uuid::log::Facility::CONSOLE};
|
||||
|
||||
Mixing::Mixing(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
|
||||
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
|
||||
LOG_DEBUG(F("Registering new Mixing module with device ID 0x%02X"), device_id);
|
||||
LOG_DEBUG(F("Adding new Mixing module with device ID 0x%02X"), device_id);
|
||||
|
||||
if (flags == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) {
|
||||
if (device_id <= 0x27) {
|
||||
// telegram handlers 0x20 - 0x27 for HC
|
||||
register_telegram_type(device_id - 0x20 + 0x02D7, F("MMPLUSStatusMessage_HC"), true, std::bind(&Mixing::process_MMPLUSStatusMessage_HC, this, _1));
|
||||
register_telegram_type(device_id - 0x20 + 0x02D7, F("MMPLUSStatusMessage_HC"), true, [&](std::shared_ptr<const Telegram> t) {
|
||||
process_MMPLUSStatusMessage_HC(t);
|
||||
});
|
||||
} else {
|
||||
// telegram handlers for warm water/DHW 0x28, 0x29
|
||||
register_telegram_type(device_id - 0x28 + 0x0331, F("MMPLUSStatusMessage_WWC"), true, std::bind(&Mixing::process_MMPLUSStatusMessage_WWC, this, _1));
|
||||
register_telegram_type(device_id - 0x28 + 0x0331, F("MMPLUSStatusMessage_WWC"), true, [&](std::shared_ptr<const Telegram> t) {
|
||||
process_MMPLUSStatusMessage_WWC(t);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// EMS 1.0
|
||||
if (flags == EMSdevice::EMS_DEVICE_FLAG_MM10) {
|
||||
register_telegram_type(0x00AA, F("MMConfigMessage"), false, std::bind(&Mixing::process_MMConfigMessage, this, _1));
|
||||
register_telegram_type(0x00AB, F("MMStatusMessage"), true, std::bind(&Mixing::process_MMStatusMessage, this, _1));
|
||||
register_telegram_type(0x00AC, F("MMSetMessage"), false, std::bind(&Mixing::process_MMSetMessage, this, _1));
|
||||
}
|
||||
// HT3
|
||||
if (flags == EMSdevice::EMS_DEVICE_FLAG_IPM) {
|
||||
register_telegram_type(0x010C, F("IPMSetMessage"), false, std::bind(&Mixing::process_IPMStatusMessage, this, _1));
|
||||
register_telegram_type(0x00AA, F("MMConfigMessage"), false, [&](std::shared_ptr<const Telegram> t) { process_MMConfigMessage(t); });
|
||||
register_telegram_type(0x00AB, F("MMStatusMessage"), true, [&](std::shared_ptr<const Telegram> t) { process_MMStatusMessage(t); });
|
||||
register_telegram_type(0x00AC, F("MMSetMessage"), false, [&](std::shared_ptr<const Telegram> t) { process_MMSetMessage(t); });
|
||||
}
|
||||
|
||||
// MQTT callbacks
|
||||
// register_mqtt_topic("topic", std::bind(&Mixing::cmd, this, _1));
|
||||
// HT3
|
||||
if (flags == EMSdevice::EMS_DEVICE_FLAG_IPM) {
|
||||
register_telegram_type(0x010C, F("IPMSetMessage"), false, [&](std::shared_ptr<const Telegram> t) { process_IPMStatusMessage(t); });
|
||||
}
|
||||
}
|
||||
|
||||
// add context submenu
|
||||
@@ -68,11 +70,11 @@ void Mixing::device_info(JsonArray & root) {
|
||||
} else {
|
||||
render_value_json(root, "", F("Heating Circuit"), hc_, nullptr);
|
||||
}
|
||||
|
||||
render_value_json(root, "", F("Current flow temperature"), flowTemp_, F_(degrees), 10);
|
||||
render_value_json(root, "", F("Setpoint flow temperature"), flowSetTemp_, F_(degrees));
|
||||
render_value_json(root, "", F("Current pump modulation"), pumpMod_, F_(percent));
|
||||
render_value_json(root, "", F("Current valve status"), status_, nullptr);
|
||||
|
||||
}
|
||||
|
||||
// check to see if values have been updated
|
||||
@@ -97,16 +99,19 @@ void Mixing::show_values(uuid::console::Shell & shell) {
|
||||
} else {
|
||||
print_value(shell, 2, F("Heating Circuit"), hc_, nullptr);
|
||||
}
|
||||
|
||||
print_value(shell, 4, F("Current flow temperature"), flowTemp_, F_(degrees), 10);
|
||||
print_value(shell, 4, F("Setpoint flow temperature"), flowSetTemp_, F_(degrees));
|
||||
print_value(shell, 4, F("Current pump modulation"), pumpMod_, F_(percent));
|
||||
print_value(shell, 4, F("Current valve status"), status_, nullptr);
|
||||
|
||||
shell.println();
|
||||
}
|
||||
|
||||
// publish values via MQTT
|
||||
// ideally we should group up all the mixing units together into a nested JSON but for now we'll send them individually
|
||||
void Mixing::publish_values() {
|
||||
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_SMALL);
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
|
||||
|
||||
switch (type_) {
|
||||
case Type::HC:
|
||||
@@ -139,7 +144,7 @@ void Mixing::publish_values() {
|
||||
char topic[30];
|
||||
char s[3]; // for formatting strings
|
||||
strlcpy(topic, "mixing_data", 30);
|
||||
strlcat(topic, Helpers::itoa(s, device_id() - 0x20 + 1), 30); // append hc to topic
|
||||
strlcat(topic, Helpers::itoa(s, get_device_id() - 0x20 + 1), 30); // append hc to topic
|
||||
Mqtt::publish(topic, doc);
|
||||
}
|
||||
|
||||
@@ -171,7 +176,7 @@ void Mixing::process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> tel
|
||||
// A1 00 FF 00 00 0C 02 04 00 01 1D 00 82
|
||||
void Mixing::process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram) {
|
||||
type_ = Type::HC;
|
||||
hc_ = device_id() - 0x20 + 1;
|
||||
hc_ = get_device_id() - 0x20 + 1;
|
||||
uint8_t ismixed = 0;
|
||||
telegram->read_value(ismixed, 0); // check if circuit is active, 0-off, 1-unmixed, 2-mixed
|
||||
if (ismixed == 0) {
|
||||
@@ -198,7 +203,7 @@ void Mixing::process_MMStatusMessage(std::shared_ptr<const Telegram> telegram) {
|
||||
// the heating circuit is determine by which device_id it is, 0x20 - 0x23
|
||||
// 0x21 is position 2. 0x20 is typically reserved for the WM10 switch module
|
||||
// see https://github.com/proddy/EMS-ESP/issues/270 and https://github.com/proddy/EMS-ESP/issues/386#issuecomment-629610918
|
||||
hc_ = device_id() - 0x20 + 1;
|
||||
hc_ = get_device_id() - 0x20 + 1;
|
||||
telegram->read_value(flowTemp_, 1); // is * 10
|
||||
telegram->read_value(pumpMod_, 3);
|
||||
telegram->read_value(flowSetTemp_, 0);
|
||||
@@ -210,7 +215,7 @@ void Mixing::process_MMStatusMessage(std::shared_ptr<const Telegram> telegram) {
|
||||
// Mixing on a MM10 - 0xAA
|
||||
// e.g. Thermostat -> Mixing Module, type 0xAA, telegram: 10 21 AA 00 FF 0C 0A 11 0A 32 xx
|
||||
void Mixing::process_MMConfigMessage(std::shared_ptr<const Telegram> telegram) {
|
||||
hc_ = device_id() - 0x20 + 1;
|
||||
hc_ = get_device_id() - 0x20 + 1;
|
||||
// pos 0: active FF = on
|
||||
// pos 1: valve runtime 0C = 120 sec in units of 10 sec
|
||||
}
|
||||
@@ -218,7 +223,7 @@ void Mixing::process_MMConfigMessage(std::shared_ptr<const Telegram> telegram) {
|
||||
// Mixing on a MM10 - 0xAC
|
||||
// e.g. Thermostat -> Mixing Module, type 0xAC, telegram: 10 21 AC 00 1E 64 01 AB
|
||||
void Mixing::process_MMSetMessage(std::shared_ptr<const Telegram> telegram) {
|
||||
hc_ = device_id() - 0x20 + 1;
|
||||
hc_ = get_device_id() - 0x20 + 1;
|
||||
// pos 0: flowtemp setpoint 1E = 30°C
|
||||
// pos 1: position in %
|
||||
}
|
||||
|
||||
@@ -18,40 +18,35 @@
|
||||
|
||||
#include "solar.h"
|
||||
|
||||
MAKE_PSTR(kwh, "kWh")
|
||||
MAKE_PSTR(wh, "Wh")
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
REGISTER_FACTORY(Solar, EMSdevice::DeviceType::SOLAR);
|
||||
|
||||
MAKE_PSTR(logger_name, "solar")
|
||||
uuid::log::Logger Solar::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
uuid::log::Logger Solar::logger_{F_(solar), uuid::log::Facility::CONSOLE};
|
||||
|
||||
Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
|
||||
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
|
||||
LOG_DEBUG(F("Registering new Solar module with device ID 0x%02X"), device_id);
|
||||
LOG_DEBUG(F("Adding new Solar module with device ID 0x%02X"), device_id);
|
||||
|
||||
// telegram handlers
|
||||
if (flags == EMSdevice::EMS_DEVICE_FLAG_SM10) {
|
||||
register_telegram_type(0x0097, F("SM10Monitor"), true, std::bind(&Solar::process_SM10Monitor, this, _1));
|
||||
register_telegram_type(0x0097, F("SM10Monitor"), true, [&](std::shared_ptr<const Telegram> t) { process_SM10Monitor(t); });
|
||||
}
|
||||
|
||||
if (flags == EMSdevice::EMS_DEVICE_FLAG_SM100) {
|
||||
register_telegram_type(0x0362, F("SM100Monitor"), true, std::bind(&Solar::process_SM100Monitor, this, _1));
|
||||
register_telegram_type(0x0363, F("SM100Monitor2"), true, std::bind(&Solar::process_SM100Monitor2, this, _1));
|
||||
register_telegram_type(0x0366, F("SM100Config"), true, std::bind(&Solar::process_SM100Config, this, _1));
|
||||
register_telegram_type(0x0362, F("SM100Monitor"), true, [&](std::shared_ptr<const Telegram> t) { process_SM100Monitor(t); });
|
||||
register_telegram_type(0x0363, F("SM100Monitor2"), true, [&](std::shared_ptr<const Telegram> t) { process_SM100Monitor2(t); });
|
||||
register_telegram_type(0x0366, F("SM100Config"), true, [&](std::shared_ptr<const Telegram> t) { process_SM100Config(t); });
|
||||
|
||||
register_telegram_type(0x0364, F("SM100Status"), false, std::bind(&Solar::process_SM100Status, this, _1));
|
||||
register_telegram_type(0x036A, F("SM100Status2"), false, std::bind(&Solar::process_SM100Status2, this, _1));
|
||||
register_telegram_type(0x038E, F("SM100Energy"), true, std::bind(&Solar::process_SM100Energy, this, _1));
|
||||
register_telegram_type(0x0364, F("SM100Status"), false, [&](std::shared_ptr<const Telegram> t) { process_SM100Status(t); });
|
||||
register_telegram_type(0x036A, F("SM100Status2"), false, [&](std::shared_ptr<const Telegram> t) { process_SM100Status2(t); });
|
||||
register_telegram_type(0x038E, F("SM100Energy"), true, [&](std::shared_ptr<const Telegram> t) { process_SM100Energy(t); });
|
||||
}
|
||||
|
||||
if (flags == EMSdevice::EMS_DEVICE_FLAG_ISM) {
|
||||
register_telegram_type(0x0103, F("ISM1StatusMessage"), true, std::bind(&Solar::process_ISM1StatusMessage, this, _1));
|
||||
register_telegram_type(0x0101, F("ISM1Set"), false, std::bind(&Solar::process_ISM1Set, this, _1));
|
||||
register_telegram_type(0x0103, F("ISM1StatusMessage"), true, [&](std::shared_ptr<const Telegram> t) { process_ISM1StatusMessage(t); });
|
||||
register_telegram_type(0x0101, F("ISM1Set"), false, [&](std::shared_ptr<const Telegram> t) { process_ISM1Set(t); });
|
||||
}
|
||||
|
||||
// MQTT callbacks
|
||||
// register_mqtt_topic("topic", std::bind(&Solar::cmd, this, _1));
|
||||
}
|
||||
|
||||
// context submenu
|
||||
@@ -113,7 +108,7 @@ void Solar::show_values(uuid::console::Shell & shell) {
|
||||
|
||||
// publish values via MQTT
|
||||
void Solar::publish_values() {
|
||||
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_MEDIUM);
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
|
||||
|
||||
char s[10]; // for formatting strings
|
||||
|
||||
@@ -141,11 +136,11 @@ void Solar::publish_values() {
|
||||
doc["cylinderPumpModulation"] = cylinderPumpModulation_;
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(solarPump_, VALUE_BOOL)) {
|
||||
if (Helpers::hasValue(solarPump_, EMS_VALUE_BOOL)) {
|
||||
doc["solarPump"] = Helpers::render_value(s, solarPump_, EMS_VALUE_BOOL);
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(valveStatus_, VALUE_BOOL)) {
|
||||
if (Helpers::hasValue(valveStatus_, EMS_VALUE_BOOL)) {
|
||||
doc["valveStatus"] = Helpers::render_value(s, valveStatus_, EMS_VALUE_BOOL);
|
||||
}
|
||||
|
||||
@@ -153,11 +148,11 @@ void Solar::publish_values() {
|
||||
doc["pumpWorkMin"] = (float)pumpWorkMin_;
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(tankHeated_, VALUE_BOOL)) {
|
||||
if (Helpers::hasValue(tankHeated_, EMS_VALUE_BOOL)) {
|
||||
doc["tankHeated"] = Helpers::render_value(s, tankHeated_, EMS_VALUE_BOOL);
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(collectorShutdown_, VALUE_BOOL)) {
|
||||
if (Helpers::hasValue(collectorShutdown_, EMS_VALUE_BOOL)) {
|
||||
doc["collectorShutdown"] = Helpers::render_value(s, collectorShutdown_, EMS_VALUE_BOOL);
|
||||
}
|
||||
|
||||
@@ -187,8 +182,8 @@ void Solar::console_commands() {
|
||||
|
||||
// SM10Monitor - type 0x97
|
||||
void Solar::process_SM10Monitor(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(collectorTemp_, 2); // collector temp from SM10, is *10
|
||||
telegram->read_value(tankBottomTemp_, 5); // bottom temp from SM10, is *10
|
||||
telegram->read_value(collectorTemp_, 2); // collector temp from SM10, is *10
|
||||
telegram->read_value(tankBottomTemp_, 5); // bottom temp from SM10, is *10
|
||||
telegram->read_value(solarPumpModulation_, 4); // modulation solar pump
|
||||
telegram->read_bitvalue(solarPump_, 7, 1);
|
||||
telegram->read_value(pumpWorkMin_, 8);
|
||||
@@ -196,7 +191,6 @@ void Solar::process_SM10Monitor(std::shared_ptr<const Telegram> telegram) {
|
||||
|
||||
/*
|
||||
* SM100Monitor - type 0x0362 EMS+ - for MS/SM100 and MS/SM200
|
||||
* e.g. 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
|
||||
* e.g. B0 0B FF 00 02 62 00 77 01 D4 80 00 80 00 80 00 80 00 80 00 80 00 80 00 80 00 00 F9 80 00 80 9E - for heat exchanger temp
|
||||
* e.g, 30 00 FF 00 02 62 01 AC
|
||||
* 30 00 FF 18 02 62 80 00
|
||||
@@ -237,21 +231,21 @@ void Solar::process_SM100Config(std::shared_ptr<const Telegram> telegram) {
|
||||
- PS1: Solar circuit pump for collector array 1
|
||||
- PS5: Cylinder primary pump when using an external heat exchanger
|
||||
* e.g. 30 00 FF 09 02 64 64 = 100%
|
||||
* 30 00 FF 09 02 64 1E = 30%
|
||||
*/
|
||||
void Solar::process_SM100Status(std::shared_ptr<const Telegram> telegram) {
|
||||
uint8_t solarpumpmod = solarPumpModulation_;
|
||||
uint8_t solarpumpmod = solarPumpModulation_;
|
||||
uint8_t cylinderpumpmod = cylinderPumpModulation_;
|
||||
telegram->read_value(cylinderPumpModulation_, 8);
|
||||
telegram->read_value(solarPumpModulation_, 9);
|
||||
|
||||
if (solarpumpmod == 0 && solarPumpModulation_ == 100) { // mask out boosts
|
||||
solarPumpModulation_ = 15; // set to minimum
|
||||
}
|
||||
|
||||
if (cylinderpumpmod == 0 && cylinderPumpModulation_ == 100) { // mask out boosts
|
||||
cylinderPumpModulation_ = 15; // set to minimum
|
||||
}
|
||||
|
||||
telegram->read_bitvalue(tankHeated_, 3, 1); // issue #422
|
||||
telegram->read_bitvalue(tankHeated_, 3, 1); // issue #422
|
||||
telegram->read_bitvalue(collectorShutdown_, 3, 0); // collector shutdown
|
||||
}
|
||||
|
||||
@@ -281,22 +275,23 @@ void Solar::process_SM100Energy(std::shared_ptr<const Telegram> telegram) {
|
||||
* e.g. B0 00 FF 00 00 03 32 00 00 00 00 13 00 D6 00 00 00 FB D0 F0
|
||||
*/
|
||||
void Solar::process_ISM1StatusMessage(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(collectorTemp_, 4); // Collector Temperature
|
||||
telegram->read_value(tankBottomTemp_, 6); // Temperature Bottom of Solar Boiler
|
||||
telegram->read_value(collectorTemp_, 4); // Collector Temperature
|
||||
telegram->read_value(tankBottomTemp_, 6); // Temperature Bottom of Solar Boiler
|
||||
uint16_t Wh = 0xFFFF;
|
||||
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
|
||||
}
|
||||
telegram->read_bitvalue(solarPump_, 8, 0); // PS1 Solar pump on (1) or off (0)
|
||||
telegram->read_value(pumpWorkMin_, 10, 3); // force to 3 bytes
|
||||
telegram->read_bitvalue(collectorShutdown_, 9, 0); // collector shutdown on/off
|
||||
telegram->read_bitvalue(tankHeated_, 9, 2); // tank full
|
||||
|
||||
telegram->read_bitvalue(solarPump_, 8, 0); // PS1 Solar pump on (1) or off (0)
|
||||
telegram->read_value(pumpWorkMin_, 10, 3); // force to 3 bytes
|
||||
telegram->read_bitvalue(collectorShutdown_, 9, 0); // collector shutdown on/off
|
||||
telegram->read_bitvalue(tankHeated_, 9, 2); // tank full
|
||||
}
|
||||
|
||||
/*
|
||||
* Junkers ISM1 Solar Module - type 0x0101 EMS+ for setting values
|
||||
* e.g. 90 30 FF 06 00 01 50
|
||||
*/
|
||||
void Solar::process_ISM1Set(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(setpoint_maxBottomTemp_, 6);
|
||||
|
||||
@@ -18,22 +18,14 @@
|
||||
|
||||
#include "switch.h"
|
||||
|
||||
// MAKE_PSTR_WORD(switch)
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
REGISTER_FACTORY(Switch, EMSdevice::DeviceType::SWITCH);
|
||||
|
||||
MAKE_PSTR(logger_name, "switch")
|
||||
uuid::log::Logger Switch::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
uuid::log::Logger Switch::logger_{F_(switch), uuid::log::Facility::CONSOLE};
|
||||
|
||||
Switch::Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
|
||||
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
|
||||
// telegram handlers
|
||||
// register_telegram_type(EMS_TYPE_XX, "XX", false, std::bind(&Controller::process_XX, this, _1));
|
||||
|
||||
// MQTT callbacks
|
||||
// register_mqtt_topic("topic", std::bind(&Switch::cmd, this, _1));
|
||||
}
|
||||
|
||||
void Switch::add_context_menu() {
|
||||
@@ -44,7 +36,7 @@ void Switch::device_info(JsonArray & root) {
|
||||
|
||||
// display all values into the shell console
|
||||
void Switch::show_values(uuid::console::Shell & shell) {
|
||||
EMSdevice::show_values(shell); // always call this to show header
|
||||
// EMSdevice::show_values(shell); // always call this to show header
|
||||
}
|
||||
|
||||
// publish values via MQTT
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -91,7 +91,7 @@ class Thermostat : public EMSdevice {
|
||||
uint16_t set_typeid_;
|
||||
};
|
||||
|
||||
std::string mode_tostring(uint8_t mode) const;
|
||||
static std::string mode_tostring(uint8_t mode);
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
@@ -99,21 +99,17 @@ class Thermostat : public EMSdevice {
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
bool can_write() const {
|
||||
return ((flags() & EMSdevice::EMS_DEVICE_FLAG_NO_WRITE) == EMSdevice::EMS_DEVICE_FLAG_NO_WRITE);
|
||||
}
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
void console_commands(Shell & shell, unsigned int context);
|
||||
void add_commands();
|
||||
|
||||
// each thermostat has a list of heating controller type IDs for reading and writing
|
||||
std::vector<uint16_t> monitor_typeids;
|
||||
std::vector<uint16_t> set_typeids;
|
||||
std::vector<uint16_t> timer_typeids;
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
void console_commands(Shell & shell, unsigned int context);
|
||||
void init_mqtt();
|
||||
|
||||
std::string datetime_; // date and time stamp
|
||||
|
||||
uint8_t mqtt_format_; // single, nested or ha
|
||||
@@ -210,6 +206,7 @@ class Thermostat : public EMSdevice {
|
||||
// Installation settings
|
||||
static constexpr uint8_t EMS_TYPE_IBASettings = 0xA5; // installation settings
|
||||
static constexpr uint8_t EMS_TYPE_wwSettings = 0x37; // ww settings
|
||||
static constexpr uint8_t EMS_TYPE_time = 0x06; // time
|
||||
|
||||
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);
|
||||
@@ -220,55 +217,65 @@ class Thermostat : public EMSdevice {
|
||||
void process_IBASettings(std::shared_ptr<const Telegram> telegram);
|
||||
void process_RCTime(std::shared_ptr<const Telegram> telegram);
|
||||
void process_RC35wwSettings(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_RC35Monitor(std::shared_ptr<const Telegram> telegram);
|
||||
void process_RC35Set(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_RC30Monitor(std::shared_ptr<const Telegram> telegram);
|
||||
void process_RC30Set(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_RC20Monitor(std::shared_ptr<const Telegram> telegram);
|
||||
void process_RC20Set(std::shared_ptr<const Telegram> telegram);
|
||||
void process_RC20Remote(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_RC20Monitor_2(std::shared_ptr<const Telegram> telegram);
|
||||
void process_RC20Set_2(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_RC10Monitor(std::shared_ptr<const Telegram> telegram);
|
||||
void process_RC10Set(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_RC300Monitor(std::shared_ptr<const Telegram> telegram);
|
||||
void process_RC300Set(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_JunkersMonitor(std::shared_ptr<const Telegram> telegram);
|
||||
void process_JunkersSet(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_JunkersSet2(std::shared_ptr<const Telegram> telegram);
|
||||
void process_EasyMonitor(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_RC300WWmode(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
// set functions
|
||||
void set_settings_minexttemp(const int8_t mt);
|
||||
void set_settings_calinttemp(const int8_t ct);
|
||||
void set_settings_clockoffset(const int8_t co);
|
||||
void set_settings_display(const uint8_t ds);
|
||||
void set_settings_building(const uint8_t bg);
|
||||
void set_settings_language(const uint8_t lg);
|
||||
void set_control(const uint8_t ctrl, const uint8_t hc_num);
|
||||
void set_ww_mode(const std::string & mode);
|
||||
void set_holiday(const char * hd, const uint8_t hc_num);
|
||||
void set_datetime(const char * dt);
|
||||
void set_pause(const uint8_t hrs, const uint8_t hc_num);
|
||||
void set_party(const uint8_t hrs, const uint8_t hc_num);
|
||||
void set_mode(const uint8_t mode, const uint8_t hc_num);
|
||||
void set_mode(const std::string & mode, const uint8_t hc_num);
|
||||
// internal helper functions
|
||||
void set_mode_n(const uint8_t mode, const uint8_t hc_num);
|
||||
|
||||
void set_temperature_value(const char * value, const int8_t id, const uint8_t mode);
|
||||
void set_temperature(const float temperature, const std::string & mode, const uint8_t hc_num);
|
||||
void set_temperature(const float temperature, const uint8_t mode, const uint8_t hc_num);
|
||||
|
||||
// MQTT functions
|
||||
void thermostat_cmd(const char * message);
|
||||
// for HA specifically. MQTT functions.
|
||||
void thermostat_cmd_temp(const char * message);
|
||||
void thermostat_cmd_mode(const char * message);
|
||||
|
||||
// set functions - these use the id/hc
|
||||
void set_mode(const char * value, const int8_t id);
|
||||
void set_control(const char * value, const int8_t id);
|
||||
void set_holiday(const char * value, const int8_t id);
|
||||
void set_pause(const char * value, const int8_t id);
|
||||
void set_party(const char * value, const int8_t id);
|
||||
|
||||
void set_temp(const char * value, const int8_t id);
|
||||
void set_nighttemp(const char * value, const int8_t id);
|
||||
void set_daytemp(const char * value, const int8_t id);
|
||||
void set_nofrosttemp(const char * value, const int8_t id);
|
||||
void set_ecotemp(const char * value, const int8_t id);
|
||||
void set_heattemp(const char * value, const int8_t id);
|
||||
void set_summertemp(const char * value, const int8_t id);
|
||||
void set_designtemp(const char * value, const int8_t id);
|
||||
void set_offsettemp(const char * value, const int8_t id);
|
||||
void set_holidaytemp(const char * value, const int8_t id);
|
||||
|
||||
void set_remotetemp(const char * value, const int8_t id);
|
||||
|
||||
// set functions - these don't use the id/hc, the parameters are ignored
|
||||
void set_wwmode(const char * value, const int8_t id);
|
||||
void set_datetime(const char * value, const int8_t id);
|
||||
void set_minexttemp(const char * value, const int8_t id);
|
||||
void set_clockoffset(const char * value, const int8_t id);
|
||||
void set_calinttemp(const char * value, const int8_t id);
|
||||
void set_display(const char * value, const int8_t id);
|
||||
void set_building(const char * value, const int8_t id);
|
||||
void set_language(const char * value, const int8_t id);
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -20,11 +20,9 @@
|
||||
#include "emsesp.h"
|
||||
#include "mqtt.h" // for the mqtt_function_p
|
||||
|
||||
MAKE_PSTR(logger_name, "emsdevice")
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
uuid::log::Logger EMSdevice::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
uuid::log::Logger EMSdevice::logger_{F_(emsesp), uuid::log::Facility::CONSOLE};
|
||||
|
||||
std::string EMSdevice::brand_to_string() const {
|
||||
switch (brand_) {
|
||||
@@ -55,6 +53,39 @@ std::string EMSdevice::brand_to_string() const {
|
||||
return std::string{};
|
||||
}
|
||||
|
||||
// returns the name of the MQTT topic to use for a specific device
|
||||
std::string EMSdevice::device_type_topic_name(const uint8_t device_type) {
|
||||
switch (device_type) {
|
||||
case DeviceType::SERVICEKEY:
|
||||
return read_flash_string(F("system_cmd"));
|
||||
break;
|
||||
|
||||
case DeviceType::BOILER:
|
||||
return read_flash_string(F("boiler_cmd"));
|
||||
break;
|
||||
|
||||
case DeviceType::THERMOSTAT:
|
||||
return read_flash_string(F("thermostat_cmd"));
|
||||
break;
|
||||
|
||||
case DeviceType::HEATPUMP:
|
||||
return read_flash_string(F("heatpump_cmd"));
|
||||
break;
|
||||
|
||||
case DeviceType::SOLAR:
|
||||
return read_flash_string(F("solar_cmd"));
|
||||
break;
|
||||
|
||||
case DeviceType::MIXING:
|
||||
return read_flash_string(F("mixing_cmd"));
|
||||
break;
|
||||
|
||||
default:
|
||||
return std::string{};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string EMSdevice::device_type_name() const {
|
||||
switch (device_type_) {
|
||||
case DeviceType::BOILER:
|
||||
@@ -171,7 +202,7 @@ void EMSdevice::show_values(uuid::console::Shell & shell) {
|
||||
|
||||
// for each telegram that has the fetch value set (true) do a read request
|
||||
void EMSdevice::fetch_values() {
|
||||
LOG_DEBUG(F("Fetching values for device ID 0x%02X"), device_id());
|
||||
LOG_DEBUG(F("Fetching values for device ID 0x%02X"), get_device_id());
|
||||
|
||||
for (const auto & tf : telegram_functions_) {
|
||||
if (tf.fetch_) {
|
||||
@@ -182,7 +213,7 @@ void EMSdevice::fetch_values() {
|
||||
|
||||
// toggle on/off automatic fetch for a telegram id
|
||||
void EMSdevice::toggle_fetch(uint16_t telegram_id, bool toggle) {
|
||||
LOG_DEBUG(F("Toggling fetch for device ID 0x%02X, telegram ID 0x%02X to %d"), device_id(), telegram_id, toggle);
|
||||
LOG_DEBUG(F("Toggling fetch for device ID 0x%02X, telegram ID 0x%02X to %d"), get_device_id(), telegram_id, toggle);
|
||||
|
||||
for (auto & tf : telegram_functions_) {
|
||||
if (tf.telegram_type_id_ == telegram_id) {
|
||||
@@ -206,22 +237,16 @@ void EMSdevice::show_telegram_handlers(uuid::console::Shell & shell) {
|
||||
|
||||
// list all the mqtt handlers for this device
|
||||
void EMSdevice::show_mqtt_handlers(uuid::console::Shell & shell) {
|
||||
Mqtt::show_topic_handlers(shell, this->device_id_);
|
||||
Mqtt::show_topic_handlers(shell, this->device_type_);
|
||||
}
|
||||
|
||||
void EMSdevice::register_mqtt_topic(const std::string & topic, mqtt_subfunction_p f) {
|
||||
LOG_DEBUG(F("Registering MQTT topic %s for device ID %02X"), topic.c_str(), this->device_id_);
|
||||
Mqtt::subscribe(this->device_id_, topic, f);
|
||||
LOG_DEBUG(F("Registering MQTT topic %s for device ID %02X and type %s"), topic.c_str(), this->device_id_, this->device_type_name().c_str());
|
||||
Mqtt::subscribe(this->device_type_, topic, f);
|
||||
}
|
||||
|
||||
EMSdevice::TelegramFunction::TelegramFunction(uint16_t telegram_type_id,
|
||||
const __FlashStringHelper * telegram_type_name,
|
||||
bool fetch,
|
||||
process_function_p process_function)
|
||||
: telegram_type_id_(telegram_type_id)
|
||||
, telegram_type_name_(telegram_type_name)
|
||||
, fetch_(fetch)
|
||||
, process_function_(process_function) {
|
||||
void EMSdevice::register_mqtt_cmd(const __FlashStringHelper * cmd, mqtt_cmdfunction_p f) {
|
||||
Mqtt::add_command(this->device_type_, this->device_id_, cmd, f);
|
||||
}
|
||||
|
||||
// register a call back function for a specific telegram type
|
||||
@@ -239,7 +264,7 @@ std::string EMSdevice::telegram_type_name(std::shared_ptr<const Telegram> telegr
|
||||
}
|
||||
|
||||
for (const auto & tf : telegram_functions_) {
|
||||
if ((tf.telegram_type_id_ == telegram->type_id) && ((telegram->type_id & 0x0F0) != 0xF0)) {
|
||||
if ((tf.telegram_type_id_ == telegram->type_id) && ((telegram->type_id & 0xF0) != 0xF0)) {
|
||||
return uuid::read_flash_string(tf.telegram_type_name_);
|
||||
}
|
||||
}
|
||||
@@ -270,24 +295,22 @@ bool EMSdevice::handle_telegram(std::shared_ptr<const Telegram> telegram) {
|
||||
|
||||
// send Tx write with a data block
|
||||
void EMSdevice::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) {
|
||||
EMSESP::send_write_request(type_id, device_id(), offset, message_data, message_length, validate_typeid);
|
||||
EMSESP::send_write_request(type_id, this->get_device_id(), offset, message_data, message_length, validate_typeid);
|
||||
}
|
||||
|
||||
// send Tx write with a single value
|
||||
void EMSdevice::write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid) {
|
||||
uint8_t message_data[1];
|
||||
message_data[0] = value;
|
||||
EMSESP::send_write_request(type_id, device_id(), offset, message_data, 1, validate_typeid);
|
||||
EMSESP::send_write_request(type_id, this->get_device_id(), offset, value, validate_typeid);
|
||||
}
|
||||
|
||||
// send Tx write with a single value, with no post validation
|
||||
void EMSdevice::write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value) {
|
||||
write_command(type_id, offset, value, 0);
|
||||
EMSESP::send_write_request(type_id, this->get_device_id(), offset, value, 0);
|
||||
}
|
||||
|
||||
// send Tx read command to the device
|
||||
void EMSdevice::read_command(const uint16_t type_id) {
|
||||
EMSESP::send_read_request(type_id, device_id());
|
||||
EMSESP::send_read_request(type_id, get_device_id());
|
||||
}
|
||||
|
||||
// prints a string value to the console
|
||||
@@ -295,6 +318,7 @@ void EMSdevice::print_value(uuid::console::Shell & shell, uint8_t padding, const
|
||||
print_value(shell, padding, name, uuid::read_flash_string(value).c_str());
|
||||
}
|
||||
|
||||
// print string value, value is not in flash
|
||||
void EMSdevice::print_value(uuid::console::Shell & shell, uint8_t padding, const __FlashStringHelper * name, const char * value) {
|
||||
uint8_t i = padding;
|
||||
while (i-- > 0) {
|
||||
@@ -304,4 +328,60 @@ void EMSdevice::print_value(uuid::console::Shell & shell, uint8_t padding, const
|
||||
shell.printfln(PSTR("%s: %s"), uuid::read_flash_string(name).c_str(), value);
|
||||
}
|
||||
|
||||
// given a context, automatically add the commands taken them from the MQTT registry for "<device_type>_cmd" topics
|
||||
void EMSdevice::add_context_commands(unsigned int context) {
|
||||
// if we're adding commands for a thermostat or mixing, then include an additional optional paramter called heating circuit
|
||||
flash_string_vector params;
|
||||
if ((context == ShellContext::THERMOSTAT) || (context == ShellContext::MIXING)) {
|
||||
params = flash_string_vector{F_(cmd_optional), F_(data_optional), F_(hc_optional)};
|
||||
} else {
|
||||
params = flash_string_vector{F_(cmd_optional), F_(data_optional)};
|
||||
}
|
||||
|
||||
EMSESPShell::commands->add_command(
|
||||
context,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(call)},
|
||||
params,
|
||||
[&](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
uint8_t device_type_ = device_type();
|
||||
if (arguments.empty()) {
|
||||
// list options
|
||||
shell.print("Available commands:");
|
||||
for (const auto & cf : Mqtt::commands()) {
|
||||
if (cf.device_type_ == device_type_) {
|
||||
shell.printf(" %s", uuid::read_flash_string(cf.cmd_).c_str());
|
||||
}
|
||||
}
|
||||
shell.println();
|
||||
return;
|
||||
}
|
||||
|
||||
const char * cmd = arguments[0].c_str();
|
||||
if (arguments.size() == 1) {
|
||||
// no value specified
|
||||
Mqtt::call_command(device_type_, cmd, nullptr, -1);
|
||||
} else if (arguments.size() == 2) {
|
||||
// has a value but no id
|
||||
Mqtt::call_command(device_type_, cmd, arguments.back().c_str(), -1);
|
||||
} else {
|
||||
// use value, which could be an id or hc
|
||||
Mqtt::call_command(device_type_, cmd, arguments[1].c_str(), atoi(arguments[2].c_str()));
|
||||
}
|
||||
},
|
||||
[&](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) -> std::vector<std::string> {
|
||||
if (arguments.size() > 0) {
|
||||
return {};
|
||||
}
|
||||
std::vector<std::string> commands;
|
||||
for (const auto & cf : Mqtt::commands()) {
|
||||
if (cf.device_type_ == device_type()) {
|
||||
commands.emplace_back(uuid::read_flash_string(cf.cmd_));
|
||||
}
|
||||
}
|
||||
return commands;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -30,10 +30,10 @@
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
using namespace std::placeholders; // for `_1`
|
||||
|
||||
class EMSdevice {
|
||||
public:
|
||||
static constexpr uint8_t EMS_DEVICES_MAX_TELEGRAMS = 20;
|
||||
|
||||
// device_type defines which derived class to use, e.g. BOILER, THERMOSTAT etc..
|
||||
EMSdevice(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
|
||||
: device_type_(device_type)
|
||||
@@ -47,11 +47,12 @@ class EMSdevice {
|
||||
|
||||
virtual ~EMSdevice() = default; // destructor of base class must always be virtual because it's a polymorphic class
|
||||
|
||||
inline uint8_t device_id() const {
|
||||
inline uint8_t get_device_id() const {
|
||||
return device_id_;
|
||||
}
|
||||
|
||||
std::string device_type_name() const;
|
||||
std::string device_type_name() const;
|
||||
static std::string device_type_topic_name(const uint8_t device_type);
|
||||
|
||||
inline uint8_t product_id() const {
|
||||
return product_id_;
|
||||
@@ -129,7 +130,10 @@ class EMSdevice {
|
||||
|
||||
void read_command(const uint16_t type_id);
|
||||
|
||||
void add_context_commands(unsigned int context);
|
||||
|
||||
void register_mqtt_topic(const std::string & topic, mqtt_subfunction_p f);
|
||||
void register_mqtt_cmd(const __FlashStringHelper * cmd, mqtt_cmdfunction_p f);
|
||||
|
||||
// virtual functions overrules by derived classes
|
||||
virtual void show_values(uuid::console::Shell & shell) = 0;
|
||||
@@ -143,6 +147,10 @@ class EMSdevice {
|
||||
void fetch_values();
|
||||
void toggle_fetch(uint16_t telegram_id, bool toggle);
|
||||
|
||||
void reserve_mem(size_t n) {
|
||||
telegram_functions_.reserve(n);
|
||||
}
|
||||
|
||||
// prints a ems device value to the console, handling the correct rendering of the type
|
||||
// padding is # white space
|
||||
// name is the name of the parameter
|
||||
@@ -221,8 +229,7 @@ class EMSdevice {
|
||||
};
|
||||
|
||||
enum DeviceType : uint8_t {
|
||||
UNKNOWN = 0,
|
||||
SERVICEKEY,
|
||||
SERVICEKEY = 0, // this is us
|
||||
BOILER,
|
||||
THERMOSTAT,
|
||||
MIXING,
|
||||
@@ -272,25 +279,29 @@ class EMSdevice {
|
||||
|
||||
private:
|
||||
uint8_t unique_id_;
|
||||
uint8_t device_type_ = DeviceType::UNKNOWN;
|
||||
uint8_t device_type_ = DeviceType::SERVICEKEY;
|
||||
uint8_t device_id_ = 0;
|
||||
uint8_t product_id_ = 0;
|
||||
std::string version_;
|
||||
std::string name_; // the long name of the EMS model
|
||||
std::string name_; // the long name for the EMS model
|
||||
uint8_t flags_ = 0;
|
||||
uint8_t brand_ = Brand::NO_BRAND;
|
||||
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
class TelegramFunction {
|
||||
public:
|
||||
TelegramFunction(uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, process_function_p process_function);
|
||||
~TelegramFunction() = default;
|
||||
|
||||
struct TelegramFunction {
|
||||
uint16_t telegram_type_id_; // it's type_id
|
||||
const __FlashStringHelper * telegram_type_name_; // e.g. RC20Message
|
||||
bool fetch_; // if this type_id be queried automatically
|
||||
process_function_p process_function_;
|
||||
|
||||
process_function_p process_function_;
|
||||
|
||||
TelegramFunction(uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, process_function_p process_function)
|
||||
: telegram_type_id_(telegram_type_id)
|
||||
, telegram_type_name_(telegram_type_name)
|
||||
, fetch_(fetch)
|
||||
, process_function_(process_function) {
|
||||
}
|
||||
};
|
||||
std::vector<TelegramFunction> telegram_functions_; // each EMS device has its own set of registered telegram types
|
||||
};
|
||||
|
||||
152
src/emsesp.cpp
152
src/emsesp.cpp
@@ -18,13 +18,8 @@
|
||||
|
||||
#include "emsesp.h"
|
||||
|
||||
MAKE_PSTR(logger_name, "emsesp")
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
using DeviceFlags = emsesp::EMSdevice;
|
||||
using DeviceType = emsesp::EMSdevice::DeviceType;
|
||||
|
||||
AsyncWebServer webServer(80);
|
||||
|
||||
#if defined(ESP32)
|
||||
@@ -42,10 +37,12 @@ EMSESPSettingsService EMSESP::emsespSettingsService = EMSESPSettingsService(&web
|
||||
EMSESPStatusService EMSESP::emsespStatusService = EMSESPStatusService(&webServer, EMSESP::esp8266React.getSecurityManager());
|
||||
EMSESPDevicesService EMSESP::emsespDevicesService = EMSESPDevicesService(&webServer, EMSESP::esp8266React.getSecurityManager());
|
||||
|
||||
using DeviceFlags = emsesp::EMSdevice;
|
||||
using DeviceType = emsesp::EMSdevice::DeviceType;
|
||||
std::vector<std::unique_ptr<EMSdevice>> EMSESP::emsdevices; // array of all the detected EMS devices
|
||||
std::vector<emsesp::EMSESP::Device_record> EMSESP::device_library_; // libary of all our known EMS devices so far
|
||||
|
||||
uuid::log::Logger EMSESP::logger_{FPSTR(__pstr__logger_name), uuid::log::Facility::KERN};
|
||||
uuid::log::Logger EMSESP::logger_{F_(emsesp), uuid::log::Facility::KERN};
|
||||
|
||||
// The services
|
||||
RxService EMSESP::rxservice_; // incoming Telegram Rx handler
|
||||
@@ -60,7 +57,8 @@ Shower EMSESP::shower_; // Shower logic
|
||||
uint8_t EMSESP::actual_master_thermostat_ = EMSESP_DEFAULT_MASTER_THERMOSTAT; // which thermostat leads when multiple found
|
||||
uint16_t EMSESP::watch_id_ = WATCH_ID_NONE; // for when log is TRACE. 0 means no trace set
|
||||
uint8_t EMSESP::watch_ = 0; // trace off
|
||||
bool EMSESP::tap_water_active_ = false; // for when Boiler states we having running warm water. used in Shower()
|
||||
uint16_t EMSESP::read_id_ = WATCH_ID_NONE;
|
||||
bool EMSESP::tap_water_active_ = false; // for when Boiler states we having running warm water. used in Shower()
|
||||
uint32_t EMSESP::last_fetch_ = 0;
|
||||
uint8_t EMSESP::unique_id_count_ = 0;
|
||||
|
||||
@@ -79,6 +77,11 @@ void EMSESP::fetch_device_values(const uint8_t device_id) {
|
||||
}
|
||||
}
|
||||
|
||||
// clears list of recognized devices
|
||||
void EMSESP::clear_all_devices() {
|
||||
emsdevices.clear(); // or use empty to release memory too
|
||||
}
|
||||
|
||||
// return number of devices of a known type
|
||||
uint8_t EMSESP::count_devices(const uint8_t device_type) {
|
||||
uint8_t count = 0;
|
||||
@@ -90,6 +93,33 @@ uint8_t EMSESP::count_devices(const uint8_t device_type) {
|
||||
return count;
|
||||
}
|
||||
|
||||
// scans for new devices
|
||||
void EMSESP::scan_devices() {
|
||||
EMSESP::clear_all_devices();
|
||||
EMSESP::send_read_request(EMSdevice::EMS_TYPE_UBADevices, EMSdevice::EMS_DEVICE_ID_BOILER);
|
||||
}
|
||||
|
||||
/**
|
||||
* if thermostat master is 0x18 it handles only ww and hc1, hc2..hc4 handled by devices 0x19..0x1B
|
||||
* we send to right device and match all reads to 0x18
|
||||
*/
|
||||
uint8_t EMSESP::check_master_device(const uint8_t device_id, const uint16_t type_id, const bool read) {
|
||||
uint16_t mon_id[4] = {0x02A5, 0x02A6, 0x02A7, 0x02A8};
|
||||
uint16_t set_id[4] = {0x02B9, 0x02BA, 0x02BB, 0x02BC};
|
||||
if (actual_master_thermostat_ == 0x18) {
|
||||
for (uint8_t i = 0; i < 4; i++) {
|
||||
if (type_id == mon_id[i] || type_id == set_id[i]) {
|
||||
if (read) {
|
||||
return 0x18;
|
||||
} else {
|
||||
return 0x18 + i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return device_id;
|
||||
}
|
||||
|
||||
void EMSESP::actual_master_thermostat(const uint8_t device_id) {
|
||||
actual_master_thermostat_ = device_id;
|
||||
}
|
||||
@@ -110,6 +140,7 @@ void EMSESP::watch_id(uint16_t watch_id) {
|
||||
|
||||
// change the tx_mode
|
||||
// resets all counters and bumps the UART
|
||||
// this is called when the tx_mode is persisted in the FS either via Web UI or the console
|
||||
void EMSESP::reset_tx() {
|
||||
// get the tx_mode
|
||||
uint8_t tx_mode;
|
||||
@@ -182,25 +213,12 @@ void EMSESP::show_ems(uuid::console::Shell & shell) {
|
||||
shell.printfln(F(" #telegrams received: %d"), rxservice_.telegram_count());
|
||||
shell.printfln(F(" #read requests sent: %d"), txservice_.telegram_read_count());
|
||||
shell.printfln(F(" #write requests sent: %d"), txservice_.telegram_write_count());
|
||||
shell.printfln(F(" #corrupted telegrams: %d (%d%%)"), rxservice_.telegram_error_count(), success_rate);
|
||||
shell.printfln(F(" #incomplete telegrams: %d (%d%%)"), rxservice_.telegram_error_count(), success_rate);
|
||||
shell.printfln(F(" #tx fails (after %d retries): %d"), TxService::MAXIMUM_TX_RETRIES, txservice_.telegram_fail_count());
|
||||
}
|
||||
|
||||
shell.println();
|
||||
|
||||
// Rx queue
|
||||
auto rx_telegrams = rxservice_.queue();
|
||||
if (rx_telegrams.empty()) {
|
||||
shell.printfln(F("Rx Queue is empty"));
|
||||
} else {
|
||||
shell.printfln(F("Rx Queue (%ld telegram%s):"), rx_telegrams.size(), rx_telegrams.size() == 1 ? "" : "s");
|
||||
for (const auto & it : rx_telegrams) {
|
||||
shell.printfln(F(" [%02d] %s"), it.id_, pretty_telegram(it.telegram_).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
shell.println();
|
||||
|
||||
// Tx queue
|
||||
auto tx_telegrams = txservice_.queue();
|
||||
if (tx_telegrams.empty()) {
|
||||
@@ -208,7 +226,7 @@ void EMSESP::show_ems(uuid::console::Shell & shell) {
|
||||
} else {
|
||||
shell.printfln(F("Tx Queue (%ld telegram%s):"), tx_telegrams.size(), tx_telegrams.size() == 1 ? "" : "s");
|
||||
|
||||
std::string op;
|
||||
std::string op(10, '\0');
|
||||
for (const auto & it : tx_telegrams) {
|
||||
if ((it.telegram_->operation) == Telegram::Operation::TX_RAW) {
|
||||
op = read_flash_string(F("RAW "));
|
||||
@@ -217,12 +235,7 @@ void EMSESP::show_ems(uuid::console::Shell & shell) {
|
||||
} else if ((it.telegram_->operation) == Telegram::Operation::TX_WRITE) {
|
||||
op = read_flash_string(F("WRITE"));
|
||||
}
|
||||
shell.printfln(F(" [%02d%c] %s %s (offset %d)"),
|
||||
it.id_,
|
||||
((it.retry_) ? '*' : ' '),
|
||||
op.c_str(),
|
||||
pretty_telegram(it.telegram_).c_str(),
|
||||
it.telegram_->offset);
|
||||
shell.printfln(F(" [%02d%c] %s %s"), it.id_, ((it.retry_) ? '*' : ' '), op.c_str(), pretty_telegram(it.telegram_).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,7 +255,6 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) {
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
if ((emsdevice) && (emsdevice->device_type() == device_class.first)) {
|
||||
emsdevice->show_values(shell);
|
||||
shell.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,16 +303,16 @@ std::string EMSESP::device_tostring(const uint8_t device_id) {
|
||||
}
|
||||
|
||||
// created a pretty print telegram as a text string
|
||||
// e.g. Boiler(0x08) -> Me(0x0B), Version(0x02), data: 7B 06 01 00 00 00 00 00 00 04 (#data=10)
|
||||
// e.g. Boiler(0x08) -> Me(0x0B), Version(0x02), data: 7B 06 01 00 00 00 00 00 00 04 (offset 1)
|
||||
std::string EMSESP::pretty_telegram(std::shared_ptr<const Telegram> telegram) {
|
||||
uint8_t src = telegram->src & 0x7F;
|
||||
uint8_t dest = telegram->dest & 0x7F;
|
||||
uint8_t offset = telegram->offset;
|
||||
|
||||
// find name for src and dest by looking up known devices
|
||||
std::string src_name;
|
||||
std::string dest_name;
|
||||
std::string type_name;
|
||||
std::string src_name(20, '\0');
|
||||
std::string dest_name(20, '\0');
|
||||
std::string type_name(20, '\0');
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
if (emsdevice) {
|
||||
// get src & dest
|
||||
@@ -340,14 +352,14 @@ std::string EMSESP::pretty_telegram(std::shared_ptr<const Telegram> telegram) {
|
||||
if (offset) {
|
||||
snprintf_P(&str[0],
|
||||
str.capacity() + 1,
|
||||
PSTR("%s(0x%02X) -> %s(0x%02X), %s(0x%02X), data: %s @offset %d"),
|
||||
PSTR("%s(0x%02X) -> %s(0x%02X), %s(0x%02X), data: %s (offset %d)"),
|
||||
src_name.c_str(),
|
||||
src,
|
||||
dest_name.c_str(),
|
||||
dest,
|
||||
type_name.c_str(),
|
||||
telegram->type_id,
|
||||
telegram->to_string().c_str(),
|
||||
telegram->to_string_message().c_str(),
|
||||
offset);
|
||||
} else {
|
||||
snprintf_P(&str[0],
|
||||
@@ -359,7 +371,7 @@ std::string EMSESP::pretty_telegram(std::shared_ptr<const Telegram> telegram) {
|
||||
dest,
|
||||
type_name.c_str(),
|
||||
telegram->type_id,
|
||||
telegram->to_string().c_str());
|
||||
telegram->to_string_message().c_str());
|
||||
}
|
||||
|
||||
return str;
|
||||
@@ -444,7 +456,10 @@ void EMSESP::process_version(std::shared_ptr<const Telegram> telegram) {
|
||||
// returns false if there are none found
|
||||
bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
|
||||
// if watching...
|
||||
if (watch() == WATCH_ON) {
|
||||
if (telegram->type_id == read_id_) {
|
||||
LOG_NOTICE(pretty_telegram(telegram).c_str());
|
||||
read_id_ = WATCH_ID_NONE;
|
||||
} else if (watch() == WATCH_ON) {
|
||||
if ((watch_id_ == WATCH_ID_NONE) || (telegram->src == watch_id_) || (telegram->dest == watch_id_) || (telegram->type_id == watch_id_)) {
|
||||
LOG_NOTICE(pretty_telegram(telegram).c_str());
|
||||
}
|
||||
@@ -544,16 +559,13 @@ void EMSESP::show_devices(uuid::console::Shell & shell) {
|
||||
// shell.printf(F("[factory ID: %d] "), device_class.first);
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
if ((emsdevice) && (emsdevice->device_type() == device_class.first)) {
|
||||
#if defined(EMSESP_DEBUG)
|
||||
shell.printf(F("[id=%d] "), emsdevice->unique_id());
|
||||
#endif
|
||||
shell.printf(F("%s: %s"), emsdevice->device_type_name().c_str(), emsdevice->to_string().c_str());
|
||||
if ((emsdevice->device_type() == EMSdevice::DeviceType::THERMOSTAT) && (emsdevice->device_id() == actual_master_thermostat())) {
|
||||
if ((emsdevice->device_type() == EMSdevice::DeviceType::THERMOSTAT) && (emsdevice->get_device_id() == actual_master_thermostat())) {
|
||||
shell.printf(F(" ** master device **"));
|
||||
}
|
||||
shell.println();
|
||||
emsdevice->show_telegram_handlers(shell);
|
||||
emsdevice->show_mqtt_handlers(shell);
|
||||
// emsdevice->show_mqtt_handlers(shell);
|
||||
shell.println();
|
||||
}
|
||||
}
|
||||
@@ -568,11 +580,12 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// first check to see if we already have it, if so update the record
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
if (emsdevice) {
|
||||
if (emsdevice->is_device_id(device_id)) {
|
||||
LOG_DEBUG(F("Updating details to already existing device ID 0x%02X"), device_id);
|
||||
LOG_DEBUG(F("Updating details on already existing device ID 0x%02X"), device_id);
|
||||
emsdevice->product_id(product_id);
|
||||
emsdevice->version(version);
|
||||
// only set brand if it doesn't already exist
|
||||
@@ -592,6 +605,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// look up the rest of the details using the product_id and create the new device object
|
||||
Device_record * device_p = nullptr;
|
||||
for (auto & device : device_library_) {
|
||||
@@ -616,12 +630,11 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::
|
||||
LOG_NOTICE(F("Unrecognized EMS device with device ID 0x%02X with product ID %d. Please report on GitHub."), device_id, product_id);
|
||||
return false; // not found
|
||||
} else {
|
||||
emsdevices.push_back(
|
||||
EMSFactory::add(device_p->device_type, device_id, device_p->product_id, version, uuid::read_flash_string(device_p->name), device_p->flags, brand));
|
||||
std::string name = uuid::read_flash_string(device_p->name);
|
||||
emsdevices.push_back(EMSFactory::add(device_p->device_type, device_id, device_p->product_id, version, name, device_p->flags, brand));
|
||||
emsdevices.back()->unique_id(++unique_id_count_);
|
||||
LOG_DEBUG(F("Adding new device with device ID 0x%02X with product ID %d and version %s"), device_id, product_id, version.c_str());
|
||||
// go and fetch its data,
|
||||
fetch_device_values(device_id);
|
||||
fetch_device_values(device_id); // go and fetch its data,
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -644,6 +657,17 @@ void EMSESP::send_write_request(const uint16_t type_id,
|
||||
txservice_.set_post_send_query(validate_typeid); // store which type_id to send Tx read after a write
|
||||
}
|
||||
|
||||
void EMSESP::send_write_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t value) {
|
||||
send_write_request(type_id, dest, offset, value, 0);
|
||||
}
|
||||
|
||||
// send Tx write with a single value
|
||||
void EMSESP::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) {
|
||||
uint8_t message_data[1];
|
||||
message_data[0] = value;
|
||||
EMSESP::send_write_request(type_id, dest, offset, message_data, 1, validate_typeid);
|
||||
}
|
||||
|
||||
// this is main entry point when data is received on the Rx line, via emsuart library
|
||||
// we check if its a complete telegram or just a single byte (which could be a poll or a return status)
|
||||
// the CRC check is not done here, only when it's added to the Rx queue with add()
|
||||
@@ -743,9 +767,11 @@ void EMSESP::send_raw_telegram(const char * data) {
|
||||
txservice_.send_raw(data);
|
||||
}
|
||||
|
||||
// kick off the party, start all the services
|
||||
// start all the core services
|
||||
// the services must be loaded in the correct order
|
||||
void EMSESP::start() {
|
||||
// Load our library of known devices
|
||||
// Load our library of known devices. Names are stored in Flash mem.
|
||||
device_library_.reserve(100);
|
||||
device_library_ = {
|
||||
#include "device_library.h"
|
||||
};
|
||||
@@ -758,17 +784,22 @@ void EMSESP::start() {
|
||||
|
||||
esp8266React.begin(); // loads system settings (wifi, mqtt, etc)
|
||||
emsespSettingsService.begin(); // load EMS-ESP specific settings
|
||||
|
||||
// system_.check_upgrade(); // see if we need to migrate from previous versions
|
||||
|
||||
console_.start(); // telnet and serial console
|
||||
system_.start(); // starts syslog, uart, sets version, initializes LED. Requires pre-loaded settings.
|
||||
mqtt_.start(); // mqtt init
|
||||
system_.start(); // starts syslog, uart, sets version, initializes LED. Requires pre-loaded settings.
|
||||
shower_.start(); // initialize shower timer and shower alert
|
||||
txservice_.start(); // sets bus ID, sends out request for EMS devices
|
||||
sensors_.start(); // dallas external sensors
|
||||
webServer.begin(); // start web server
|
||||
|
||||
webServer.begin(); // start web server
|
||||
emsdevices.reserve(5); // reserve space for initially 5 devices to avoid mem
|
||||
|
||||
LOG_INFO("EMS Device library loaded with %d records", device_library_.size());
|
||||
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
mqtt_.on_connect(); // simulate an MQTT connection
|
||||
#endif
|
||||
}
|
||||
|
||||
// main loop calling all services
|
||||
@@ -777,18 +808,19 @@ void EMSESP::loop() {
|
||||
|
||||
// if we're doing an OTA upload, skip MQTT and EMS
|
||||
if (system_.upload_status()) {
|
||||
/*
|
||||
#if defined(ESP32)
|
||||
delay(10); // slow down OTA update to avoid getting killed by task watchdog (task_wdt)
|
||||
#endif
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
system_.loop(); // does LED and checks system health, and syslog service
|
||||
rxservice_.loop(); // process what ever is in the rx queue
|
||||
shower_.loop(); // check for shower on/off
|
||||
sensors_.loop(); // this will also send out via MQTT
|
||||
mqtt_.loop(); // sends out anything in the queue via MQTT
|
||||
console_.loop(); // telnet/serial console
|
||||
system_.loop(); // does LED and checks system health, and syslog service
|
||||
shower_.loop(); // check for shower on/off
|
||||
sensors_.loop(); // this will also send out via MQTT
|
||||
mqtt_.loop(); // sends out anything in the queue via MQTT
|
||||
console_.loop(); // telnet/serial console
|
||||
|
||||
// force a query on the EMS devices to fetch latest data at a set interval (1 min)
|
||||
if ((uuid::get_uptime() - last_fetch_ > EMS_FETCH_FREQUENCY)) {
|
||||
@@ -796,7 +828,9 @@ void EMSESP::loop() {
|
||||
fetch_device_values();
|
||||
}
|
||||
|
||||
#if defined(ESP8266)
|
||||
delay(1);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
10
src/emsesp.h
10
src/emsesp.h
@@ -78,6 +78,9 @@ class EMSESP {
|
||||
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);
|
||||
|
||||
static void send_raw_telegram(const char * data);
|
||||
static bool device_exists(const uint8_t device_id);
|
||||
|
||||
@@ -87,6 +90,7 @@ class EMSESP {
|
||||
|
||||
static uint8_t actual_master_thermostat();
|
||||
static void actual_master_thermostat(const uint8_t device_id);
|
||||
static uint8_t check_master_device(const uint8_t device_id, const uint16_t type_id, const bool read);
|
||||
|
||||
static void show_device_values(uuid::console::Shell & shell);
|
||||
static void show_sensor_values(uuid::console::Shell & shell);
|
||||
@@ -119,6 +123,9 @@ class EMSESP {
|
||||
static uint8_t watch() {
|
||||
return watch_;
|
||||
}
|
||||
static void set_read_id(uint16_t id) {
|
||||
read_id_ = id;
|
||||
}
|
||||
|
||||
enum Bus_status : uint8_t { BUS_STATUS_CONNECTED = 0, BUS_STATUS_TX_ERRORS, BUS_STATUS_OFFLINE };
|
||||
static uint8_t bus_status();
|
||||
@@ -134,6 +141,8 @@ class EMSESP {
|
||||
static void fetch_device_values(const uint8_t device_id = 0);
|
||||
|
||||
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();
|
||||
static void clear_all_devices();
|
||||
|
||||
static std::vector<std::unique_ptr<EMSdevice>> emsdevices;
|
||||
|
||||
@@ -181,6 +190,7 @@ class EMSESP {
|
||||
static uint8_t actual_master_thermostat_;
|
||||
static uint16_t watch_id_;
|
||||
static uint8_t watch_;
|
||||
static uint16_t read_id_;
|
||||
static bool tap_water_active_;
|
||||
|
||||
static uint8_t unique_id_count_;
|
||||
|
||||
@@ -55,12 +55,12 @@ class EMSFactory {
|
||||
}
|
||||
|
||||
// Construct derived class returning an unique ptr
|
||||
static auto add(const uint8_t device_type, uint8_t device_id, uint8_t product_id, std::string version, std::string name, uint8_t flags, uint8_t brand)
|
||||
static auto add(const uint8_t device_type, uint8_t device_id, uint8_t product_id, std::string &version, std::string &name, uint8_t flags, uint8_t brand)
|
||||
-> std::unique_ptr<EMSdevice> {
|
||||
return std::unique_ptr<EMSdevice>(EMSFactory::makeRaw(device_type, device_id, product_id, version, name, flags, brand));
|
||||
}
|
||||
|
||||
virtual auto construct(uint8_t device_type, uint8_t device_id, uint8_t product_id, std::string version, std::string name, uint8_t flags, uint8_t brand) const
|
||||
virtual auto construct(uint8_t device_type, uint8_t device_id, uint8_t product_id, std::string &version, std::string &name, uint8_t flags, uint8_t brand) const
|
||||
-> EMSdevice * = 0;
|
||||
|
||||
private:
|
||||
@@ -72,7 +72,7 @@ class EMSFactory {
|
||||
|
||||
// Construct derived class returning a raw pointer
|
||||
// find which EMS device it is and use that class
|
||||
static auto makeRaw(const uint8_t device_type, uint8_t device_id, uint8_t product_id, std::string version, std::string name, uint8_t flags, uint8_t brand)
|
||||
static auto makeRaw(const uint8_t device_type, uint8_t device_id, uint8_t product_id, std::string &version, std::string &name, uint8_t flags, uint8_t brand)
|
||||
-> EMSdevice * {
|
||||
auto it = EMSFactory::getRegister().find(device_type);
|
||||
if (it != EMSFactory::getRegister().end()) {
|
||||
@@ -90,7 +90,7 @@ class ConcreteEMSFactory : EMSFactory {
|
||||
EMSFactory::registerFactory(device_type, this);
|
||||
}
|
||||
|
||||
auto construct(uint8_t device_type, uint8_t device_id, uint8_t product_id, std::string version, std::string name, uint8_t flags, uint8_t brand) const
|
||||
auto construct(uint8_t device_type, uint8_t device_id, uint8_t product_id, std::string &version, std::string &name, uint8_t flags, uint8_t brand) const
|
||||
-> EMSdevice * {
|
||||
return new DerivedClass(device_type, device_id, product_id, version, name, flags, brand);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
|
||||
#include "helpers.h"
|
||||
#include "telegram.h" // for EMS_VALUE_* settings
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
@@ -187,7 +186,7 @@ char * Helpers::render_value(char * result, const int16_t value, const uint8_t f
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// just print it if mo conversion required (format = 0)
|
||||
// just print it if no conversion required (format = 0)
|
||||
if (!format) {
|
||||
itoa(result, value, 10);
|
||||
return result;
|
||||
@@ -329,9 +328,9 @@ bool Helpers::check_abs(const int32_t i) {
|
||||
return ((i < 0 ? -i : i) != 0xFFFFFF);
|
||||
}
|
||||
|
||||
// for booleans, use isBool true (VALUE_BOOL)
|
||||
bool Helpers::hasValue(const uint8_t v, bool isBool) {
|
||||
if (isBool) {
|
||||
// for booleans, use isBool true (EMS_VALUE_BOOL)
|
||||
bool Helpers::hasValue(const uint8_t v, const uint8_t isBool) {
|
||||
if (isBool == EMS_VALUE_BOOL) {
|
||||
return (v != EMS_VALUE_BOOL_NOTSET);
|
||||
}
|
||||
return (v != EMS_VALUE_UINT_NOTSET);
|
||||
@@ -354,4 +353,63 @@ bool Helpers::hasValue(const uint32_t v) {
|
||||
return (v != EMS_VALUE_ULONG_NOTSET);
|
||||
}
|
||||
|
||||
// checks if we can convert a char string to an int value
|
||||
bool Helpers::value2number(const char * v, int & value) {
|
||||
if ((v == nullptr) || (strlen(v) == 0)) {
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
value = atoi((char *)v);
|
||||
return true;
|
||||
}
|
||||
|
||||
// checks if we can convert a char string to a float value
|
||||
bool Helpers::value2float(const char * v, float & value) {
|
||||
if ((v == nullptr) || (strlen(v) == 0)) {
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
value = atof((char *)v);
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/313970/how-to-convert-stdstring-to-lower-case
|
||||
std::string Helpers::toLower(std::string const & s) {
|
||||
std::string lc = s;
|
||||
std::transform(lc.begin(), lc.end(), lc.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||
return lc;
|
||||
}
|
||||
|
||||
// checks if we can convert a chat string to an int value
|
||||
bool Helpers::value2string(const char * v, std::string & value) {
|
||||
if ((v == nullptr) || (strlen(v) == 0)) {
|
||||
value = {};
|
||||
return false;
|
||||
}
|
||||
value = toLower(v);
|
||||
return true;
|
||||
}
|
||||
|
||||
// checks to see if a string (usually a command or payload cmd) looks like a boolean
|
||||
// on, off, true, false, 1, 0
|
||||
bool Helpers::value2bool(const char * v, bool & value) {
|
||||
if ((v == nullptr) || (strlen(v) == 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string bool_str = toLower(v); // convert to lower case
|
||||
|
||||
if ((bool_str == "on") || (bool_str == "1") or (bool_str == "true")) {
|
||||
value = true;
|
||||
return true; // is a bool
|
||||
}
|
||||
|
||||
if ((bool_str == "off") || (bool_str == "0") or (bool_str == "false")) {
|
||||
value = false;
|
||||
return true; // is a bool
|
||||
}
|
||||
|
||||
return false; // not a bool
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#include <Arduino.h>
|
||||
#include <uuid/common.h>
|
||||
|
||||
#include "telegram.h" // for EMS_VALUE_* settings
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class Helpers {
|
||||
@@ -48,11 +50,18 @@ class Helpers {
|
||||
static char * ultostr(char * ptr, uint32_t value, const uint8_t base);
|
||||
#endif
|
||||
|
||||
static bool hasValue(const uint8_t v, bool isBool = false); // use isBool=true for bool's
|
||||
static bool hasValue(const uint8_t v, const uint8_t isBool = 0);
|
||||
static bool hasValue(const int8_t v);
|
||||
static bool hasValue(const int16_t v);
|
||||
static bool hasValue(const uint16_t v);
|
||||
static bool hasValue(const uint32_t v);
|
||||
|
||||
static std::string toLower(std::string const & s);
|
||||
|
||||
static bool value2number(const char * v, int & value);
|
||||
static bool value2float(const char * v, float & value);
|
||||
static bool value2bool(const char * v, bool & value);
|
||||
static bool value2string(const char * v, std::string & value);
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
120
src/locale_EN.h
Normal file
120
src/locale_EN.h
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 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/>.
|
||||
*/
|
||||
|
||||
// common words
|
||||
MAKE_PSTR_WORD(exit)
|
||||
MAKE_PSTR_WORD(help)
|
||||
MAKE_PSTR_WORD(settings)
|
||||
MAKE_PSTR_WORD(log)
|
||||
MAKE_PSTR_WORD(logout)
|
||||
MAKE_PSTR_WORD(enabled)
|
||||
MAKE_PSTR_WORD(disabled)
|
||||
MAKE_PSTR_WORD(set)
|
||||
MAKE_PSTR_WORD(show)
|
||||
MAKE_PSTR_WORD(on)
|
||||
MAKE_PSTR_WORD(off)
|
||||
MAKE_PSTR_WORD(su)
|
||||
MAKE_PSTR_WORD(name)
|
||||
MAKE_PSTR_WORD(auto)
|
||||
MAKE_PSTR_WORD(scan)
|
||||
MAKE_PSTR_WORD(password)
|
||||
MAKE_PSTR_WORD(read)
|
||||
MAKE_PSTR_WORD(version)
|
||||
MAKE_PSTR_WORD(values)
|
||||
MAKE_PSTR_WORD(system)
|
||||
MAKE_PSTR_WORD(fetch)
|
||||
MAKE_PSTR_WORD(restart)
|
||||
MAKE_PSTR_WORD(format)
|
||||
MAKE_PSTR_WORD(raw)
|
||||
MAKE_PSTR_WORD(watch)
|
||||
MAKE_PSTR_WORD(send)
|
||||
MAKE_PSTR_WORD(telegram)
|
||||
MAKE_PSTR_WORD(bus_id)
|
||||
MAKE_PSTR_WORD(tx_mode)
|
||||
MAKE_PSTR_WORD(ems)
|
||||
MAKE_PSTR_WORD(devices)
|
||||
MAKE_PSTR_WORD(shower)
|
||||
MAKE_PSTR_WORD(mqtt)
|
||||
MAKE_PSTR_WORD(emsesp)
|
||||
MAKE_PSTR_WORD(connected)
|
||||
MAKE_PSTR_WORD(disconnected)
|
||||
MAKE_PSTR_WORD(passwd)
|
||||
MAKE_PSTR_WORD(hostname)
|
||||
MAKE_PSTR_WORD(wifi)
|
||||
MAKE_PSTR_WORD(reconnect)
|
||||
MAKE_PSTR_WORD(ssid)
|
||||
MAKE_PSTR_WORD(heartbeat)
|
||||
MAKE_PSTR_WORD(users)
|
||||
MAKE_PSTR_WORD(master)
|
||||
MAKE_PSTR_WORD(test)
|
||||
MAKE_PSTR_WORD(pin)
|
||||
|
||||
// for commands
|
||||
MAKE_PSTR_WORD(call)
|
||||
|
||||
// devices
|
||||
MAKE_PSTR_WORD(boiler)
|
||||
MAKE_PSTR_WORD(thermostat)
|
||||
MAKE_PSTR_WORD(switch)
|
||||
MAKE_PSTR_WORD(solar)
|
||||
MAKE_PSTR_WORD(mixing)
|
||||
MAKE_PSTR_WORD(gateway)
|
||||
MAKE_PSTR_WORD(controller)
|
||||
MAKE_PSTR_WORD(connect)
|
||||
MAKE_PSTR_WORD(heatpump)
|
||||
|
||||
// dallas sensors
|
||||
MAKE_PSTR_WORD(sensors)
|
||||
|
||||
MAKE_PSTR(kwh, "kWh")
|
||||
MAKE_PSTR(wh, "Wh")
|
||||
MAKE_PSTR(hc_optional, "[heating circuit]")
|
||||
MAKE_PSTR(master_thermostat_fmt, "Master Thermostat Device ID = %s")
|
||||
MAKE_PSTR(host_fmt, "Host = %s")
|
||||
MAKE_PSTR(hostname_fmt, "WiFi Hostname = %s")
|
||||
MAKE_PSTR(mark_interval_fmt, "Mark interval = %lus")
|
||||
MAKE_PSTR(wifi_ssid_fmt, "WiFi SSID = %s")
|
||||
MAKE_PSTR(wifi_password_fmt, "WiFi Password = %S")
|
||||
MAKE_PSTR(system_heartbeat_fmt, "MQTT Heartbeat is %s")
|
||||
MAKE_PSTR(cmd_optional, "[cmd]")
|
||||
MAKE_PSTR(deep_optional, "[deep]")
|
||||
MAKE_PSTR(tx_mode_fmt, "Tx mode = %d")
|
||||
MAKE_PSTR(bus_id_fmt, "Bus ID = %02X")
|
||||
MAKE_PSTR(watchid_optional, "[ID]")
|
||||
MAKE_PSTR(watch_format_optional, "[off | on | raw]")
|
||||
MAKE_PSTR(invalid_watch, "Invalid watch type")
|
||||
MAKE_PSTR(data_mandatory, "<\"XX XX ...\">")
|
||||
MAKE_PSTR(percent, "%")
|
||||
MAKE_PSTR(degrees, "°C")
|
||||
MAKE_PSTR(asterisks, "********")
|
||||
MAKE_PSTR(n_mandatory, "<n>")
|
||||
MAKE_PSTR(n_optional, "[n]")
|
||||
MAKE_PSTR(gpio_mandatory, "<gpio>")
|
||||
MAKE_PSTR(data_optional, "[data]")
|
||||
MAKE_PSTR(typeid_mandatory, "<type ID>")
|
||||
MAKE_PSTR(deviceid_mandatory, "<device ID>")
|
||||
MAKE_PSTR(deviceid_optional, "[device ID]")
|
||||
MAKE_PSTR(invalid_log_level, "Invalid log level")
|
||||
MAKE_PSTR(log_level_fmt, "Log level = %s")
|
||||
MAKE_PSTR(log_level_optional, "[level]")
|
||||
MAKE_PSTR(name_mandatory, "<name>")
|
||||
MAKE_PSTR(name_optional, "[name]")
|
||||
MAKE_PSTR(new_password_prompt1, "Enter new password: ")
|
||||
MAKE_PSTR(new_password_prompt2, "Retype new password: ")
|
||||
MAKE_PSTR(password_prompt, "Password: ")
|
||||
MAKE_PSTR(unset, "<unset>")
|
||||
234
src/mqtt.cpp
234
src/mqtt.cpp
@@ -20,12 +20,6 @@
|
||||
#include "emsesp.h"
|
||||
#include "version.h"
|
||||
|
||||
MAKE_PSTR_WORD(connected)
|
||||
MAKE_PSTR_WORD(disconnected)
|
||||
MAKE_PSTR(system_heartbeat_fmt, "Heartbeat is %s")
|
||||
|
||||
MAKE_PSTR(logger_name, "mqtt")
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
AsyncMqttClient * Mqtt::mqttClient_;
|
||||
@@ -34,58 +28,59 @@ AsyncMqttClient * Mqtt::mqttClient_;
|
||||
std::string Mqtt::hostname_;
|
||||
uint8_t Mqtt::mqtt_qos_;
|
||||
uint16_t Mqtt::publish_time_;
|
||||
uint8_t Mqtt::bus_id_;
|
||||
|
||||
std::vector<Mqtt::MQTTSubFunction> Mqtt::mqtt_subfunctions_;
|
||||
uint16_t Mqtt::mqtt_publish_fails_ = 0;
|
||||
size_t Mqtt::maximum_mqtt_messages_ = Mqtt::MAX_MQTT_MESSAGES;
|
||||
uint16_t Mqtt::mqtt_message_id_ = 0;
|
||||
std::deque<Mqtt::QueuedMqttMessage> Mqtt::mqtt_messages_;
|
||||
char will_topic_[Mqtt::MQTT_TOPIC_MAX_SIZE]; // because MQTT library keeps only char pointer
|
||||
std::vector<Mqtt::MQTTSubFunction> Mqtt::mqtt_subfunctions_;
|
||||
std::vector<Mqtt::MQTTCmdFunction> Mqtt::mqtt_cmdfunctions_;
|
||||
|
||||
uuid::log::Logger Mqtt::logger_{F_(logger_name), uuid::log::Facility::DAEMON};
|
||||
uint16_t Mqtt::mqtt_publish_fails_ = 0;
|
||||
size_t Mqtt::maximum_mqtt_messages_ = Mqtt::MAX_MQTT_MESSAGES;
|
||||
uint16_t Mqtt::mqtt_message_id_ = 0;
|
||||
std::list<Mqtt::QueuedMqttMessage> Mqtt::mqtt_messages_;
|
||||
char will_topic_[Mqtt::MQTT_TOPIC_MAX_SIZE]; // because MQTT library keeps only char pointer
|
||||
|
||||
Mqtt::QueuedMqttMessage::QueuedMqttMessage(uint16_t id, std::shared_ptr<MqttMessage> && content)
|
||||
: id_(id)
|
||||
, content_(std::move(content)) {
|
||||
retry_count_ = 0;
|
||||
packet_id_ = 0;
|
||||
}
|
||||
|
||||
MqttMessage::MqttMessage(const uint8_t operation, const std::string & topic, const std::string & payload, bool retain)
|
||||
: operation(operation)
|
||||
, topic(topic)
|
||||
, payload(payload)
|
||||
, retain(retain) {
|
||||
}
|
||||
|
||||
Mqtt::MQTTSubFunction::MQTTSubFunction(const uint8_t device_id, const std::string && topic, mqtt_subfunction_p mqtt_subfunction)
|
||||
: device_id_(device_id)
|
||||
, topic_(topic)
|
||||
, mqtt_subfunction_(mqtt_subfunction) {
|
||||
}
|
||||
uuid::log::Logger Mqtt::logger_{F_(mqtt), uuid::log::Facility::DAEMON};
|
||||
|
||||
// subscribe to an MQTT topic, and store the associated callback function
|
||||
void Mqtt::subscribe(const uint8_t device_id, const std::string & topic, mqtt_subfunction_p cb) {
|
||||
auto message = queue_subscribe_message(topic); // add subscription to queue. The hostname will automatically be appended
|
||||
|
||||
if (message == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// the message will contain the full topic, with the hostname prefixed
|
||||
// only if it already hasn't been added
|
||||
void Mqtt::subscribe(const uint8_t device_type, const std::string & topic, mqtt_subfunction_p cb) {
|
||||
// check if we already have the topic subscribed, if so don't add it again
|
||||
bool exists = false;
|
||||
if (!mqtt_subfunctions_.empty()) {
|
||||
for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
|
||||
if ((mqtt_subfunction.device_id_ == device_id) && (strcmp(mqtt_subfunction.topic_.c_str(), message->topic.c_str()) == 0)) {
|
||||
exists = true;
|
||||
if ((mqtt_subfunction.device_type_ == device_type) && (strcmp(mqtt_subfunction.topic_.c_str(), topic.c_str()) == 0)) {
|
||||
return; // it exists, exit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!exists) {
|
||||
mqtt_subfunctions_.emplace_back(device_id, std::move(message->topic), cb); // register a call back function for a specific telegram type
|
||||
// add to MQTT queue as a subscribe operation
|
||||
auto message = queue_subscribe_message(topic);
|
||||
|
||||
// register in our libary with the callback function.
|
||||
// We store both the original topic and the fully-qualified
|
||||
mqtt_subfunctions_.emplace_back(device_type, std::move(topic), std::move(message->topic), std::move(cb));
|
||||
}
|
||||
|
||||
// adds a command and callback function for a specific device
|
||||
void Mqtt::add_command(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, mqtt_cmdfunction_p cb) {
|
||||
// subscribe to the command topic if it doesn't exist yet
|
||||
std::string cmd_topic = EMSdevice::device_type_topic_name(device_type); // cmd topic for a device like "<device_type>_cmd" e.g. "boiler_cmd"
|
||||
|
||||
bool exists = false;
|
||||
if (!mqtt_subfunctions_.empty()) {
|
||||
for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
|
||||
if ((mqtt_subfunction.device_type_ == device_type) && (strcmp(mqtt_subfunction.topic_.c_str(), cmd_topic.c_str()) == 0)) {
|
||||
exists = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!exists) {
|
||||
Mqtt::subscribe(device_type, cmd_topic, nullptr); // use an empty function handler to signal this is a command function
|
||||
}
|
||||
|
||||
LOG_DEBUG(F("Registering MQTT cmd %s with topic %s"), uuid::read_flash_string(cmd).c_str(), EMSdevice::device_type_topic_name(device_type).c_str());
|
||||
|
||||
mqtt_cmdfunctions_.emplace_back(device_type, device_id, cmd, cb);
|
||||
}
|
||||
|
||||
// subscribe to an MQTT topic, and store the associated callback function. For generic functions not tied to a specific device
|
||||
@@ -100,7 +95,7 @@ void Mqtt::resubscribe() {
|
||||
}
|
||||
|
||||
for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
|
||||
queue_message(Operation::SUBSCRIBE, mqtt_subfunction.topic_, "", false, true); // no payload, no topic prefixing
|
||||
queue_subscribe_message(mqtt_subfunction.topic_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +137,19 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) {
|
||||
// show subscriptions
|
||||
shell.printfln(F("MQTT subscriptions:"));
|
||||
for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
|
||||
shell.printfln(F(" %s"), mqtt_subfunction.topic_.c_str());
|
||||
// don't show commands if its homeassistant
|
||||
if ((strncmp(mqtt_subfunction.full_topic_.c_str(), "homeassistant/", 13) == 0)) {
|
||||
shell.printf(F(" topic: %s"), mqtt_subfunction.full_topic_.c_str());
|
||||
} else {
|
||||
// show the commands associated with this subscription
|
||||
shell.printf(F(" topic: %s, [cmd]:"), mqtt_subfunction.full_topic_.c_str());
|
||||
for (const auto & mqtt_cmdfunction : mqtt_cmdfunctions_) {
|
||||
if (EMSdevice::device_type_topic_name(mqtt_cmdfunction.device_type_) == mqtt_subfunction.topic_) {
|
||||
shell.printf(F(" %s"), uuid::read_flash_string(mqtt_cmdfunction.cmd_).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
shell.println();
|
||||
}
|
||||
shell.println();
|
||||
|
||||
@@ -183,15 +190,42 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) {
|
||||
}
|
||||
}
|
||||
shell.println();
|
||||
}
|
||||
} // namespace emsesp
|
||||
|
||||
#if defined(EMSESP_DEBUG)
|
||||
// simulate receiving a MQTT message, used only for testing
|
||||
void Mqtt::incoming(char * topic, char * payload) {
|
||||
void Mqtt::incoming(const char * topic, const char * payload) {
|
||||
on_message(topic, payload, strlen(payload));
|
||||
}
|
||||
#endif
|
||||
|
||||
// calls a command, context is the device_type
|
||||
// id may be used to represent a heating circuit for example
|
||||
bool Mqtt::call_command(const uint8_t device_type, const char * cmd, const char * value, const int8_t id) {
|
||||
#ifdef EMSESP_DEBUG
|
||||
if (id == -1) {
|
||||
LOG_DEBUG(F("[DEBUG] Calling command %s, value %s, id is default"), cmd, value);
|
||||
} else {
|
||||
LOG_DEBUG(F("[DEBUG] Calling command %s, value %s, id is %d"), cmd, value, id);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!mqtt_cmdfunctions_.empty()) {
|
||||
for (const auto & cf : mqtt_cmdfunctions_) {
|
||||
if (cf.device_type_ == device_type) {
|
||||
const char * cf_cmd = uuid::read_flash_string(cf.cmd_).c_str();
|
||||
if (strcmp(cf_cmd, cmd) == 0) {
|
||||
(cf.mqtt_cmdfunction_)(value, id); // call function, data needs to be a string and can be null
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// received an MQTT message that we subscribed too
|
||||
void Mqtt::on_message(char * topic, char * payload, size_t len) {
|
||||
void Mqtt::on_message(const char * topic, const char * payload, size_t len) {
|
||||
if (len == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -199,33 +233,76 @@ void Mqtt::on_message(char * topic, char * payload, size_t len) {
|
||||
// convert payload to a null-terminated char string
|
||||
char message[len + 2];
|
||||
strlcpy(message, payload, len + 1);
|
||||
|
||||
#ifdef EMSESP_DEBUG
|
||||
LOG_DEBUG(F("[DEBUG] Received %s => %s (length %d)"), topic, message, len);
|
||||
#endif
|
||||
|
||||
// see if we have this topic in our subscription list, then call its callback handler
|
||||
// note: this will pick the first topic that matches, so for multiple devices of the same type it's gonna fail. Not sure if this is going to be an issue?
|
||||
for (const auto & mf : mqtt_subfunctions_) {
|
||||
if (strcmp(topic, mf.topic_.c_str()) == 0) {
|
||||
(mf.mqtt_subfunction_)(message);
|
||||
return;
|
||||
if (strcmp(topic, mf.full_topic_.c_str()) == 0) {
|
||||
if (mf.mqtt_subfunction_) {
|
||||
(mf.mqtt_subfunction_)(message); // matching function, call it
|
||||
return;
|
||||
} else {
|
||||
// empty function. It's a command then. Find the command from the json and call it directly.
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
|
||||
DeserializationError error = deserializeJson(doc, message);
|
||||
if (error) {
|
||||
LOG_ERROR(F("MQTT error: payload %s, error %s"), message, error.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
const char * command = doc["cmd"];
|
||||
if (command == nullptr) {
|
||||
LOG_ERROR(F("MQTT error: invalid payload cmd format. message=%s"), message);
|
||||
return;
|
||||
}
|
||||
|
||||
// check for hc and id, and convert to int
|
||||
int8_t n = -1; // no value
|
||||
if (doc.containsKey("hc")) {
|
||||
n = doc["hc"];
|
||||
} else if (doc.containsKey("id")) {
|
||||
n = doc["id"];
|
||||
}
|
||||
|
||||
bool cmd_known = false;
|
||||
JsonVariant data = doc["data"];
|
||||
if (data.is<char *>()) {
|
||||
cmd_known = call_command(mf.device_type_, command, data.as<char *>(), n);
|
||||
} else if (data.is<int>()) {
|
||||
char data_str[10];
|
||||
cmd_known = call_command(mf.device_type_, command, Helpers::itoa(data_str, (int16_t)data.as<int>()), n);
|
||||
} else if (data.is<float>()) {
|
||||
char data_str[10];
|
||||
cmd_known = call_command(mf.device_type_, command, Helpers::render_value(data_str, (float)data.as<float>(), 2), n);
|
||||
}
|
||||
|
||||
if (!cmd_known) {
|
||||
LOG_ERROR(F("MQTT: no matching cmd or invalid data: %s"), command);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we got here we didn't find a topic match
|
||||
LOG_DEBUG(F("No responding handler found for topic %s"), topic);
|
||||
LOG_ERROR(F("No MQTT handler found for topic %s and payload %s"), topic, message);
|
||||
}
|
||||
|
||||
// print all the topics related to a specific device_id
|
||||
void Mqtt::show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_id) {
|
||||
// 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_id == mqtt_subfunction.device_id_; })
|
||||
[=](MQTTSubFunction const & mqtt_subfunction) { return device_type == mqtt_subfunction.device_type_; })
|
||||
== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
shell.print(F(" Subscribed MQTT topics: "));
|
||||
for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
|
||||
if (mqtt_subfunction.device_id_ == device_id) {
|
||||
if (mqtt_subfunction.device_type_ == device_type) {
|
||||
shell.printf(F("%s "), mqtt_subfunction.topic_.c_str());
|
||||
}
|
||||
}
|
||||
@@ -269,6 +346,8 @@ void Mqtt::start() {
|
||||
mqtt_qos_ = mqttSettings.mqtt_qos;
|
||||
});
|
||||
|
||||
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { bus_id_ = settings.ems_bus_id; });
|
||||
|
||||
mqttClient_->onConnect([this](bool sessionPresent) { on_connect(); });
|
||||
|
||||
mqttClient_->onDisconnect([this](AsyncMqttClientDisconnectReason reason) {
|
||||
@@ -297,9 +376,18 @@ void Mqtt::start() {
|
||||
mqttClient_->setWill(will_topic, 1, true, "offline"); // with qos 1, retain true
|
||||
|
||||
mqttClient_->onMessage([this](char * topic, char * payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
|
||||
// receiving mqtt
|
||||
on_message(topic, payload, len);
|
||||
mqttClient_->onPublish([this](uint16_t packetId) { on_publish(packetId); });
|
||||
});
|
||||
|
||||
mqttClient_->onPublish([this](uint16_t packetId) {
|
||||
// publish
|
||||
on_publish(packetId);
|
||||
});
|
||||
|
||||
// create space for command buffer, to avoid heap memory fragmentation
|
||||
mqtt_cmdfunctions_.reserve(40); // current count with boiler+thermostat is 37
|
||||
mqtt_subfunctions_.reserve(10);
|
||||
}
|
||||
|
||||
void Mqtt::set_publish_time(uint16_t publish_time) {
|
||||
@@ -327,31 +415,31 @@ void Mqtt::on_connect() {
|
||||
|
||||
resubscribe(); // in case this is a reconnect, re-subscribe again to all MQTT topics
|
||||
|
||||
// add the system MQTT subscriptions, only if its a fresh start with no previous subscriptions
|
||||
if (mqtt_subfunctions_.empty()) {
|
||||
Mqtt::subscribe("cmd", System::mqtt_commands);
|
||||
}
|
||||
// these commands respond to the topic "system_cmd" and take a payload like {cmd:"", data:"", id:""}
|
||||
add_command(EMSdevice::DeviceType::SERVICEKEY, bus_id_, F("pin"), System::mqtt_command_pin);
|
||||
add_command(EMSdevice::DeviceType::SERVICEKEY, bus_id_, F("send"), System::mqtt_command_send);
|
||||
|
||||
LOG_INFO(F("MQTT connected"));
|
||||
}
|
||||
|
||||
// add sub or pub task to the queue. When the message is created, the topic will have
|
||||
// automatically the hostname prefixed.
|
||||
std::shared_ptr<const MqttMessage>
|
||||
Mqtt::queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, const bool retain, bool no_prefix) {
|
||||
// add sub or pub task to the queue.
|
||||
// a fully-qualified topic is created by prefixing the hostname, unless it's HA
|
||||
// returns a pointer to the message created
|
||||
std::shared_ptr<const MqttMessage> Mqtt::queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, const bool retain) {
|
||||
if (topic.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// take the topic and prefix the hostname, unless its for HA
|
||||
std::shared_ptr<MqttMessage> message;
|
||||
if ((strncmp(topic.c_str(), "homeassistant/", 13) == 0) || no_prefix) {
|
||||
if ((strncmp(topic.c_str(), "homeassistant/", 13) == 0)) {
|
||||
// leave topic as it is
|
||||
message = std::make_shared<MqttMessage>(operation, topic, payload, retain);
|
||||
message = std::make_shared<MqttMessage>(operation, topic, std::move(payload), retain);
|
||||
} else {
|
||||
// prefix the hostname
|
||||
std::string full_topic = Mqtt::hostname_ + "/" + topic;
|
||||
message = std::make_shared<MqttMessage>(operation, full_topic, payload, retain);
|
||||
std::string full_topic(50, '\0');
|
||||
snprintf_P(&full_topic[0], full_topic.capacity() + 1, PSTR("%s/%s"), Mqtt::hostname_.c_str(), topic.c_str());
|
||||
message = std::make_shared<MqttMessage>(operation, full_topic, std::move(payload), retain);
|
||||
}
|
||||
|
||||
// if the queue is full, make room but removing the last one
|
||||
|
||||
98
src/mqtt.h
98
src/mqtt.h
@@ -24,7 +24,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <functional>
|
||||
|
||||
#include <AsyncMqttClient.h>
|
||||
@@ -37,23 +37,29 @@
|
||||
|
||||
using uuid::console::Shell;
|
||||
|
||||
#define EMSESP_MAX_JSON_SIZE_SMALL 200 // for smaller json docs
|
||||
#define EMSESP_MAX_JSON_SIZE_MEDIUM 800 // for smaller json docs from ems devices
|
||||
#define EMSESP_MAX_JSON_SIZE_LARGE 1500 // for large json docs from ems devices, like boiler or thermostat data
|
||||
#define EMSESP_MAX_JSON_SIZE_SMALL 200 // for smaller json docs when using StaticJsonDocument
|
||||
#define EMSESP_MAX_JSON_SIZE_MEDIUM 800 // for smaller json docs from ems devices, when using StaticJsonDocument
|
||||
#define EMSESP_MAX_JSON_SIZE_LARGE 1500 // for large json docs from ems devices, like boiler or thermostat data. Using DynamicJsonDocument
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
using mqtt_subfunction_p = std::function<void(const char * message)>;
|
||||
using namespace std::placeholders; // for `_1`
|
||||
using mqtt_cmdfunction_p = std::function<void(const char * data, const int8_t id)>;
|
||||
|
||||
struct MqttMessage {
|
||||
MqttMessage(const uint8_t operation, const std::string & topic, const std::string & payload, bool retain);
|
||||
~MqttMessage() = default;
|
||||
|
||||
const uint8_t operation;
|
||||
const std::string topic;
|
||||
const std::string payload;
|
||||
const bool retain;
|
||||
|
||||
MqttMessage(const uint8_t operation, const std::string & topic, const std::string && payload, bool retain)
|
||||
: operation(operation)
|
||||
, topic(topic)
|
||||
, payload(std::move(payload))
|
||||
, retain(retain) {
|
||||
}
|
||||
};
|
||||
|
||||
class Mqtt {
|
||||
@@ -68,25 +74,31 @@ class Mqtt {
|
||||
|
||||
static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 100;
|
||||
|
||||
static void subscribe(const uint8_t device_id, const std::string & topic, mqtt_subfunction_p cb);
|
||||
static void subscribe(const uint8_t device_type, const std::string & topic, mqtt_subfunction_p cb);
|
||||
static void subscribe(const std::string & topic, mqtt_subfunction_p cb);
|
||||
static void resubscribe();
|
||||
|
||||
static void add_command(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, mqtt_cmdfunction_p cb);
|
||||
|
||||
static void publish(const std::string & topic, const std::string & payload, bool retain = false);
|
||||
static void publish(const std::string & topic, const JsonDocument & payload, bool retain = false);
|
||||
static void publish(const std::string & topic, const bool value);
|
||||
static void publish(const std::string & topic);
|
||||
|
||||
static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_id);
|
||||
static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type);
|
||||
static void show_mqtt(uuid::console::Shell & shell);
|
||||
|
||||
static void on_connect();
|
||||
|
||||
static bool call_command(const uint8_t device_type, const char * cmd, const char * value, const int8_t id);
|
||||
|
||||
void disconnect() {
|
||||
mqttClient_->disconnect();
|
||||
}
|
||||
|
||||
void incoming(char * topic, char * payload); // for testing
|
||||
#if defined(EMSESP_DEBUG)
|
||||
void incoming(const char * topic, const char * payload); // for testing only
|
||||
#endif
|
||||
|
||||
static bool connected() {
|
||||
return mqttClient_->connected();
|
||||
@@ -100,22 +112,43 @@ class Mqtt {
|
||||
mqtt_publish_fails_ = 0;
|
||||
}
|
||||
|
||||
static std::string hostname_;
|
||||
struct MQTTCmdFunction {
|
||||
uint8_t device_type_;
|
||||
uint8_t device_id_;
|
||||
const __FlashStringHelper * cmd_;
|
||||
mqtt_cmdfunction_p mqtt_cmdfunction_;
|
||||
|
||||
MQTTCmdFunction(uint8_t device_type, uint8_t device_id, const __FlashStringHelper * cmd, mqtt_cmdfunction_p mqtt_cmdfunction)
|
||||
: device_type_(device_type)
|
||||
, device_id_(device_id)
|
||||
, cmd_(cmd)
|
||||
, mqtt_cmdfunction_(mqtt_cmdfunction) {
|
||||
}
|
||||
};
|
||||
|
||||
static std::vector<MQTTCmdFunction> commands() {
|
||||
return mqtt_cmdfunctions_;
|
||||
}
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
class QueuedMqttMessage {
|
||||
public:
|
||||
QueuedMqttMessage(uint16_t id, std::shared_ptr<MqttMessage> && content);
|
||||
~QueuedMqttMessage() = default;
|
||||
|
||||
const uint16_t id_;
|
||||
const std::shared_ptr<const MqttMessage> content_;
|
||||
uint8_t retry_count_;
|
||||
uint16_t packet_id_;
|
||||
|
||||
~QueuedMqttMessage() = default;
|
||||
QueuedMqttMessage(uint16_t id, std::shared_ptr<MqttMessage> && content)
|
||||
: id_(id)
|
||||
, content_(std::move(content)) {
|
||||
retry_count_ = 0;
|
||||
packet_id_ = 0;
|
||||
}
|
||||
};
|
||||
static std::deque<QueuedMqttMessage> mqtt_messages_;
|
||||
static std::list<QueuedMqttMessage> mqtt_messages_;
|
||||
|
||||
static AsyncMqttClient * mqttClient_;
|
||||
|
||||
@@ -123,40 +156,47 @@ class Mqtt {
|
||||
static uint16_t mqtt_message_id_;
|
||||
static bool mqtt_retain_;
|
||||
|
||||
static constexpr size_t MAX_MQTT_MESSAGES = 30; // size of queue
|
||||
static constexpr size_t MAX_MQTT_MESSAGES = 20; // size of queue
|
||||
static constexpr uint32_t MQTT_PUBLISH_WAIT = 200; // delay between sending publishes, to account for large payloads
|
||||
static constexpr uint8_t MQTT_PUBLISH_MAX_RETRY = 3; // max retries for giving up on publishing
|
||||
|
||||
static std::shared_ptr<const MqttMessage> queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, const bool retain, bool no_prefix = false);
|
||||
static std::shared_ptr<const MqttMessage> queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, const bool retain);
|
||||
static std::shared_ptr<const MqttMessage> queue_publish_message(const std::string & topic, const std::string & payload, const bool retain);
|
||||
static std::shared_ptr<const MqttMessage> queue_subscribe_message(const std::string & topic);
|
||||
|
||||
void on_publish(uint16_t packetId);
|
||||
void on_message(char * topic, char * payload, size_t len);
|
||||
void process_queue();
|
||||
void process_all_queue();
|
||||
void on_publish(uint16_t packetId);
|
||||
void on_message(const char * topic, const char * payload, size_t len);
|
||||
void process_queue();
|
||||
void process_all_queue();
|
||||
|
||||
static uint16_t mqtt_publish_fails_;
|
||||
|
||||
// function handlers for MQTT subscriptions
|
||||
class MQTTSubFunction {
|
||||
public:
|
||||
MQTTSubFunction(const uint8_t device_id, const std::string && topic, mqtt_subfunction_p mqtt_subfunction);
|
||||
~MQTTSubFunction() = default;
|
||||
struct MQTTSubFunction {
|
||||
uint8_t device_type_; // which device type, from DeviceType::
|
||||
const std::string topic_; // short topic name
|
||||
const std::string full_topic_; // the fully qualified topic name, usually with the hostname prefixed
|
||||
mqtt_subfunction_p mqtt_subfunction_; // can be empty
|
||||
|
||||
const uint8_t device_id_; // which device ID owns this
|
||||
const std::string topic_;
|
||||
mqtt_subfunction_p mqtt_subfunction_;
|
||||
MQTTSubFunction(uint8_t device_type, const std::string && topic, const std::string && full_topic, mqtt_subfunction_p mqtt_subfunction)
|
||||
: device_type_(device_type)
|
||||
, topic_(topic)
|
||||
, full_topic_(full_topic)
|
||||
, mqtt_subfunction_(mqtt_subfunction) {
|
||||
}
|
||||
};
|
||||
|
||||
static std::vector<MQTTSubFunction> mqtt_subfunctions_; // list of mqtt subscribe callbacks for all devices
|
||||
static std::vector<MQTTCmdFunction> mqtt_cmdfunctions_; // list of commands
|
||||
|
||||
uint32_t last_mqtt_poll_ = 0;
|
||||
uint32_t last_publish_ = 0;
|
||||
|
||||
// settings, copied over
|
||||
static uint8_t mqtt_qos_;
|
||||
static uint16_t publish_time_;
|
||||
static std::string hostname_;
|
||||
static uint8_t mqtt_qos_;
|
||||
static uint16_t publish_time_;
|
||||
static uint8_t bus_id_;
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -16,13 +16,11 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// code written by nomis - https://github.com/nomis
|
||||
// code originally written by nomis - https://github.com/nomis
|
||||
|
||||
#include "sensors.h"
|
||||
#include "emsesp.h"
|
||||
|
||||
MAKE_PSTR(logger_name, "sensors")
|
||||
|
||||
#ifdef ESP32
|
||||
#define YIELD
|
||||
#else
|
||||
@@ -31,7 +29,7 @@ MAKE_PSTR(logger_name, "sensors")
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
uuid::log::Logger Sensors::logger_{F_(logger_name), uuid::log::Facility::DAEMON};
|
||||
uuid::log::Logger Sensors::logger_{F_(sensors), uuid::log::Facility::DAEMON};
|
||||
|
||||
void Sensors::start() {
|
||||
// copy over values from MQTT so we don't keep on quering the filesystem
|
||||
@@ -206,7 +204,7 @@ float Sensors::get_temperature_c(const uint8_t addr[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t raw = (raw_value *625) / 100; // round to 0.01
|
||||
uint32_t raw = (raw_value * 625) / 100; // round to 0.01
|
||||
return (float)raw / 100;
|
||||
#else
|
||||
return NAN;
|
||||
@@ -257,7 +255,7 @@ void Sensors::publish_values() {
|
||||
if (mqtt_format_ == MQTT_format::SINGLE) {
|
||||
StaticJsonDocument<100> doc;
|
||||
for (const auto & device : devices_) {
|
||||
char s[5];
|
||||
char s[7]; // sensorrange -55.00 to 125.00
|
||||
doc["temp"] = Helpers::render_value(s, device.temperature_c, 2);
|
||||
char topic[60]; // sensors{1-n}
|
||||
strlcpy(topic, "sensor_", 50); // create topic, e.g. home/ems-esp/sensor_28-EA41-9497-0E03-5F
|
||||
@@ -283,12 +281,12 @@ void Sensors::publish_values() {
|
||||
uint8_t i = 1;
|
||||
for (const auto & device : devices_) {
|
||||
if (mqtt_format_ == MQTT_format::CUSTOM) {
|
||||
char s[5];
|
||||
char s[7];
|
||||
doc[device.to_string()] = Helpers::render_value(s, device.temperature_c, 2);
|
||||
} else {
|
||||
char sensorID[10]; // sensor{1-n}
|
||||
strlcpy(sensorID, "sensor", 10);
|
||||
char s[5];
|
||||
char s[7];
|
||||
strlcat(sensorID, Helpers::itoa(s, i++), 10);
|
||||
JsonObject dataSensor = doc.createNestedObject(sensorID);
|
||||
dataSensor["id"] = device.to_string();
|
||||
@@ -303,4 +301,4 @@ void Sensors::publish_values() {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
} // namespace emsesp
|
||||
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// code written by nomis - https://github.com/nomis
|
||||
// code originally written by nomis - https://github.com/nomis
|
||||
|
||||
#ifndef EMSESP_SENSORS_H
|
||||
#define EMSESP_SENSORS_H
|
||||
@@ -87,10 +87,10 @@ class Sensors {
|
||||
static constexpr uint8_t TYPE_DS1822 = 0x22;
|
||||
static constexpr uint8_t TYPE_DS1825 = 0x3B;
|
||||
|
||||
static constexpr uint32_t READ_INTERVAL_MS = 5000; // 5 seconds
|
||||
static constexpr uint32_t CONVERSION_MS = 1000; // 1 seconds
|
||||
static constexpr uint32_t READ_TIMEOUT_MS = 2000; // 2 seconds
|
||||
static constexpr uint32_t SCAN_TIMEOUT_MS = 3000; // 3 seconds
|
||||
static constexpr uint32_t READ_INTERVAL_MS = 5000; // 5 seconds
|
||||
static constexpr uint32_t CONVERSION_MS = 1000; // 1 seconds
|
||||
static constexpr uint32_t READ_TIMEOUT_MS = 2000; // 2 seconds
|
||||
static constexpr uint32_t SCAN_TIMEOUT_MS = 3000; // 3 seconds
|
||||
|
||||
static constexpr uint8_t CMD_CONVERT_TEMP = 0x44;
|
||||
static constexpr uint8_t CMD_READ_SCRATCHPAD = 0xBE;
|
||||
@@ -112,9 +112,8 @@ class Sensors {
|
||||
|
||||
uint8_t mqtt_format_;
|
||||
uint8_t retrycnt_ = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -18,11 +18,9 @@
|
||||
|
||||
#include "shower.h"
|
||||
|
||||
MAKE_PSTR(logger_name, "shower")
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
uuid::log::Logger Shower::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
uuid::log::Logger Shower::logger_{F_(shower), uuid::log::Facility::CONSOLE};
|
||||
|
||||
void Shower::start() {
|
||||
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
|
||||
|
||||
171
src/system.cpp
171
src/system.cpp
@@ -21,25 +21,9 @@
|
||||
|
||||
#include "version.h" // firmware version of EMS-ESP
|
||||
|
||||
MAKE_PSTR_WORD(passwd)
|
||||
MAKE_PSTR_WORD(hostname)
|
||||
MAKE_PSTR_WORD(wifi)
|
||||
MAKE_PSTR_WORD(reconnect)
|
||||
MAKE_PSTR_WORD(ssid)
|
||||
MAKE_PSTR_WORD(heartbeat)
|
||||
MAKE_PSTR_WORD(users)
|
||||
|
||||
MAKE_PSTR(host_fmt, "Host = %s")
|
||||
MAKE_PSTR(hostname_fmt, "WiFi Hostname = %s")
|
||||
MAKE_PSTR(mark_interval_fmt, "Mark interval = %lus");
|
||||
MAKE_PSTR(wifi_ssid_fmt, "WiFi SSID = %s");
|
||||
MAKE_PSTR(wifi_password_fmt, "WiFi Password = %S")
|
||||
|
||||
MAKE_PSTR(logger_name, "system")
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
uuid::log::Logger System::logger_{F_(logger_name), uuid::log::Facility::KERN};
|
||||
uuid::log::Logger System::logger_{F_(system), uuid::log::Facility::KERN};
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
uuid::syslog::SyslogService System::syslog_;
|
||||
@@ -50,94 +34,20 @@ uint32_t System::heap_start_ = 0;
|
||||
int System::reset_counter_ = 0;
|
||||
bool System::upload_status_ = false;
|
||||
|
||||
// handle generic system related MQTT commands
|
||||
void System::mqtt_commands(const char * message) {
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
|
||||
DeserializationError error = deserializeJson(doc, message);
|
||||
if (error) {
|
||||
LOG_DEBUG(F("MQTT error: payload %s, error %s"), message, error.c_str());
|
||||
return;
|
||||
// send on/off to a gpio pin
|
||||
// value: true = HIGH, false = LOW
|
||||
void System::mqtt_command_pin(const char * value, const int8_t id) {
|
||||
bool v = false;
|
||||
if (Helpers::value2bool(value, v)) {
|
||||
pinMode(id, OUTPUT);
|
||||
digitalWrite(id, v);
|
||||
LOG_INFO(F("GPIO %d set to %s"), id, v ? "HIGH" : "LOW");
|
||||
}
|
||||
}
|
||||
|
||||
if (doc["send"] != nullptr) {
|
||||
const char * data = doc["send"];
|
||||
EMSESP::send_raw_telegram(data);
|
||||
LOG_INFO(F("Sending raw: %s"), data);
|
||||
}
|
||||
|
||||
#if defined(ESP8266)
|
||||
const uint8_t d0_ = 16;
|
||||
const uint8_t d1_ = 5;
|
||||
const uint8_t d2_ = 4;
|
||||
const uint8_t d3_ = 0;
|
||||
#elif defined(ESP32)
|
||||
const uint8_t d0_ = 26;
|
||||
const uint8_t d1_ = 22;
|
||||
const uint8_t d2_ = 21;
|
||||
const uint8_t d3_ = 17;
|
||||
#endif
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if (doc["D0"] != nullptr) {
|
||||
const int8_t set = doc["D0"];
|
||||
pinMode(d0_, OUTPUT);
|
||||
if (set == 1) {
|
||||
digitalWrite(d0_, HIGH);
|
||||
} else if (set == 0) {
|
||||
digitalWrite(d0_, LOW);
|
||||
}
|
||||
LOG_INFO(F("Port D0 set to %d"), set);
|
||||
}
|
||||
|
||||
if (doc["D1"] != nullptr) {
|
||||
const int8_t set = doc["D1"];
|
||||
pinMode(d1_, OUTPUT);
|
||||
if (set == 1) {
|
||||
digitalWrite(d1_, HIGH);
|
||||
} else if (set == 0) {
|
||||
digitalWrite(d1_, LOW);
|
||||
}
|
||||
LOG_INFO(F("Port D1 set to %d"), set);
|
||||
}
|
||||
|
||||
if (doc["D2"] != nullptr) {
|
||||
const int8_t set = doc["D2"];
|
||||
pinMode(d2_, OUTPUT);
|
||||
if (set == 1) {
|
||||
digitalWrite(d2_, HIGH);
|
||||
} else if (set == 0) {
|
||||
digitalWrite(d2_, LOW);
|
||||
}
|
||||
LOG_INFO(F("Port D2 set to %d"), set);
|
||||
}
|
||||
|
||||
if (doc["D3"] != nullptr) {
|
||||
const int8_t set = doc["D3"];
|
||||
pinMode(d3_, OUTPUT);
|
||||
if (set == 1) {
|
||||
digitalWrite(d3_, HIGH);
|
||||
} else if (set == 0) {
|
||||
digitalWrite(d3_, LOW);
|
||||
}
|
||||
LOG_INFO(F("Port D3 set to %d"), set);
|
||||
}
|
||||
#endif
|
||||
|
||||
const char * command = doc["cmd"];
|
||||
if (command == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// send raw command
|
||||
if (strcmp(command, "send") == 0) {
|
||||
const char * data = doc["data"];
|
||||
if (data == nullptr) {
|
||||
return;
|
||||
}
|
||||
EMSESP::send_raw_telegram(data);
|
||||
LOG_INFO(F("Sending raw: %s"), data);
|
||||
return;
|
||||
}
|
||||
// send raw
|
||||
void System::mqtt_command_send(const char * value, const int8_t id) {
|
||||
EMSESP::send_raw_telegram(value); // ignore id
|
||||
}
|
||||
|
||||
// restart EMS-ESP
|
||||
@@ -280,6 +190,24 @@ void System::loop() {
|
||||
send_heartbeat();
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(ESP8266)
|
||||
#if defined(EMSESP_DEBUG)
|
||||
static uint32_t last_memcheck_ = 0;
|
||||
if (currentMillis - last_memcheck_ > 10000) { // 10 seconds
|
||||
last_memcheck_ = currentMillis;
|
||||
show_mem("core");
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void System::show_mem(const char * note) {
|
||||
#if defined(ESP8266)
|
||||
#if defined(EMSESP_DEBUG)
|
||||
LOG_INFO(F("(%s) Free heap: %d%% (%lu), frag:%u%%"), note, free_mem(), (unsigned long)ESP.getFreeHeap(), ESP.getHeapFragmentation());
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
// send periodic MQTT message with system information
|
||||
@@ -297,6 +225,7 @@ void System::send_heartbeat() {
|
||||
doc["freemem"] = free_mem();
|
||||
doc["mqttpublishfails"] = Mqtt::publish_fails();
|
||||
doc["txfails"] = EMSESP::txservice_.telegram_fail_count();
|
||||
doc["rxfails"] = EMSESP::rxservice_.telegram_error_count();
|
||||
|
||||
Mqtt::publish("heartbeat", doc, false); // send to MQTT with retain off. This will add to MQTT queue.
|
||||
}
|
||||
@@ -398,6 +327,7 @@ void System::show_users(uuid::console::Shell & shell) {
|
||||
void System::show_system(uuid::console::Shell & shell) {
|
||||
shell.printfln(F("Uptime: %s"), uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3).c_str());
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
#if defined(ESP8266)
|
||||
shell.printfln(F("Chip ID: 0x%08x"), ESP.getChipId());
|
||||
shell.printfln(F("SDK version: %s"), ESP.getSdkVersion());
|
||||
@@ -407,26 +337,22 @@ void System::show_system(uuid::console::Shell & shell) {
|
||||
shell.printfln(F("Boot mode: %u"), ESP.getBootMode());
|
||||
shell.printfln(F("CPU frequency: %u MHz"), ESP.getCpuFreqMHz());
|
||||
shell.printfln(F("Flash chip: 0x%08X (%u bytes)"), ESP.getFlashChipId(), ESP.getFlashChipRealSize());
|
||||
shell.printfln(F("Sketch size: %u bytes (%u bytes free)"), ESP.getSketchSize(), ESP.getFreeSketchSpace());
|
||||
shell.printfln(F("Reset reason: %s"), ESP.getResetReason().c_str());
|
||||
shell.printfln(F("Reset info: %s"), ESP.getResetInfo().c_str());
|
||||
shell.println();
|
||||
shell.printfln(F("Free heap: %lu bytes"), (unsigned long)ESP.getFreeHeap());
|
||||
shell.printfln(F("Free mem: %d %%"), free_mem());
|
||||
shell.printfln(F("Free mem: %d %%"), free_mem());
|
||||
shell.printfln(F("Maximum free block size: %lu bytes"), (unsigned long)ESP.getMaxFreeBlockSize());
|
||||
shell.printfln(F("Heap fragmentation: %u%"), ESP.getHeapFragmentation());
|
||||
shell.printfln(F("Heap fragmentation: %u %%"), ESP.getHeapFragmentation());
|
||||
shell.printfln(F("Free continuations stack: %lu bytes"), (unsigned long)ESP.getFreeContStack());
|
||||
#elif defined(ESP32)
|
||||
shell.printfln(F("SDK version: %s"), ESP.getSdkVersion());
|
||||
shell.printfln(F("CPU frequency: %u MHz"), ESP.getCpuFreqMHz());
|
||||
#endif
|
||||
shell.printfln(F("Sketch size: %u bytes (%u bytes free)"), ESP.getSketchSize(), ESP.getFreeSketchSpace());
|
||||
shell.printfln(F("Free heap: %lu bytes"), (unsigned long)ESP.getFreeHeap());
|
||||
shell.printfln(F("Free mem: %d %%"), free_mem());
|
||||
#endif
|
||||
|
||||
shell.println();
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
switch (WiFi.status()) {
|
||||
case WL_IDLE_STATUS:
|
||||
shell.printfln(F("WiFi: Idle"));
|
||||
@@ -446,6 +372,7 @@ void System::show_system(uuid::console::Shell & shell) {
|
||||
shell.printfln(F("BSSID: %s"), WiFi.BSSIDstr().c_str());
|
||||
shell.printfln(F("RSSI: %d dBm (%d %%)"), WiFi.RSSI(), wifi_quality());
|
||||
shell.printfln(F("MAC address: %s"), WiFi.macAddress().c_str());
|
||||
|
||||
#if defined(ESP8266)
|
||||
shell.printfln(F("Hostname: %s"), WiFi.hostname().c_str());
|
||||
#elif defined(ESP32)
|
||||
@@ -480,7 +407,7 @@ void System::show_system(uuid::console::Shell & shell) {
|
||||
shell.print(" ");
|
||||
shell.printfln(F_(host_fmt), !settings.syslog_host.isEmpty() ? settings.syslog_host.c_str() : uuid::read_flash_string(F_(unset)).c_str());
|
||||
shell.print(" ");
|
||||
shell.printfln(F_(log_level_fmt), uuid::log::format_level_uppercase(static_cast<uuid::log::Level>(settings.syslog_level)));
|
||||
shell.printfln(F_(log_level_fmt), uuid::log::format_level_lowercase(static_cast<uuid::log::Level>(settings.syslog_level)));
|
||||
shell.print(" ");
|
||||
shell.printfln(F_(mark_interval_fmt), settings.syslog_mark_interval);
|
||||
});
|
||||
@@ -628,17 +555,31 @@ void System::console_commands(Shell & shell, unsigned int context) {
|
||||
shell.printfln(F_(wifi_password_fmt), wifiSettings.ssid.isEmpty() ? F_(unset) : F_(asterisks));
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(show), F_(mqtt)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { Mqtt::show_mqtt(shell); });
|
||||
|
||||
*/
|
||||
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(show), F_(users)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { System::show_users(shell); });
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(pin)},
|
||||
flash_string_vector{F_(gpio_mandatory), F_(data_optional)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
if (arguments.size() == 1) {
|
||||
shell.printfln(F("use on/off, 1/0 or true/false"));
|
||||
return;
|
||||
}
|
||||
int pin = 0;
|
||||
if (Helpers::value2number(arguments[0].c_str(), pin)) {
|
||||
System::mqtt_command_pin(arguments[1].c_str(), pin);
|
||||
}
|
||||
});
|
||||
|
||||
// enter the context
|
||||
Console::enter_custom_context(shell, context);
|
||||
@@ -646,9 +587,10 @@ void System::console_commands(Shell & shell, unsigned int context) {
|
||||
|
||||
// upgrade from previous versions of EMS-ESP
|
||||
void System::check_upgrade() {
|
||||
/*
|
||||
// check for v1.9. It uses SPIFFS and only on the ESP8266
|
||||
#if defined(ESP8266)
|
||||
Serial.begin(115200); // TODO remove, just for debugging
|
||||
Serial.begin(115200);
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
@@ -771,6 +713,7 @@ void System::check_upgrade() {
|
||||
},
|
||||
"local");
|
||||
#endif
|
||||
*/
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -47,7 +47,9 @@ class System {
|
||||
static void format(uuid::console::Shell & shell);
|
||||
|
||||
static void console_commands(Shell & shell, unsigned int context);
|
||||
static void mqtt_commands(const char * message);
|
||||
|
||||
static void mqtt_command_pin(const char * value, const int8_t id);
|
||||
static void mqtt_command_send(const char * value, const int8_t id);
|
||||
|
||||
static uint8_t free_mem();
|
||||
static void upload_status(bool in_progress);
|
||||
@@ -59,6 +61,8 @@ class System {
|
||||
void set_heartbeat(bool system_heartbeat);
|
||||
void send_heartbeat();
|
||||
|
||||
static void show_mem(const char * note);
|
||||
|
||||
void check_upgrade();
|
||||
|
||||
private:
|
||||
@@ -68,7 +72,7 @@ class System {
|
||||
static uuid::syslog::SyslogService syslog_;
|
||||
#endif
|
||||
|
||||
static constexpr uint32_t SYSTEM_CHECK_FREQUENCY = 10000; // check every 10 seconds
|
||||
static constexpr uint32_t SYSTEM_CHECK_FREQUENCY = 5000; // check every 5 seconds
|
||||
static constexpr uint32_t LED_WARNING_BLINK = 1000; // pulse to show no connection, 1 sec
|
||||
static constexpr uint32_t LED_WARNING_BLINK_FAST = 100; // flash quickly for boot up sequence
|
||||
static constexpr uint32_t SYSTEM_HEARTBEAT_INTERVAL = 60000; // in milliseconds, how often the MQTT heartbeat is sent (1 min)
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
#include "telegram.h"
|
||||
#include "emsesp.h"
|
||||
|
||||
MAKE_PSTR(logger_name, "telegram")
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
// CRC lookup table with poly 12 for faster checking
|
||||
@@ -45,7 +43,7 @@ uint8_t EMSbus::ems_bus_id_ = EMSESP_DEFAULT_EMS_BUS_ID;
|
||||
uint8_t EMSbus::tx_mode_ = EMSESP_DEFAULT_TX_MODE;
|
||||
uint8_t EMSbus::tx_state_ = Telegram::Operation::NONE;
|
||||
|
||||
uuid::log::Logger EMSbus::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
uuid::log::Logger EMSbus::logger_{F_(telegram), uuid::log::Facility::CONSOLE};
|
||||
|
||||
// Calculates CRC checksum using lookup table for speed
|
||||
// length excludes the last byte (which mainly is the CRC)
|
||||
@@ -80,12 +78,8 @@ Telegram::Telegram(const uint8_t operation,
|
||||
}
|
||||
}
|
||||
|
||||
// returns telegram's message data bytes in hex
|
||||
// returns telegram as data bytes in hex (excluding CRC)
|
||||
std::string Telegram::to_string() const {
|
||||
if (this->message_length == 0) {
|
||||
return read_flash_string(F("<empty>"));
|
||||
}
|
||||
|
||||
uint8_t data[EMS_MAX_TELEGRAM_LENGTH];
|
||||
uint8_t length = 0;
|
||||
data[0] = this->src ^ RxService::ems_mask();
|
||||
@@ -124,34 +118,13 @@ std::string Telegram::to_string() const {
|
||||
return Helpers::data_to_hex(data, length);
|
||||
}
|
||||
|
||||
// returns telegram's full telegram message in hex
|
||||
std::string Telegram::to_string(const uint8_t * telegram, uint8_t length) const {
|
||||
return Helpers::data_to_hex(telegram, length);
|
||||
}
|
||||
|
||||
RxService::QueuedRxTelegram::QueuedRxTelegram(uint16_t id, std::shared_ptr<Telegram> && telegram)
|
||||
: id_(id)
|
||||
, telegram_(std::move(telegram)) {
|
||||
}
|
||||
|
||||
// empty queue, don't process them
|
||||
void RxService::flush_rx_queue() {
|
||||
rx_telegrams_.clear();
|
||||
rx_telegram_id_ = 0;
|
||||
}
|
||||
|
||||
// Rx loop, run as many times as you can
|
||||
// processes all telegrams on the queue. Assumes there are valid (i.e. CRC checked)
|
||||
void RxService::loop() {
|
||||
while (!rx_telegrams_.empty()) {
|
||||
auto telegram = rx_telegrams_.front().telegram_;
|
||||
|
||||
(void)EMSESP::process_telegram(telegram); // further process the telegram
|
||||
|
||||
increment_telegram_count(); // increase count
|
||||
|
||||
rx_telegrams_.pop_front(); // remove it from the queue
|
||||
// returns telegram's message body only, in hex
|
||||
std::string Telegram::to_string_message() const {
|
||||
if (this->message_length == 0) {
|
||||
return read_flash_string(F("<empty>"));
|
||||
}
|
||||
|
||||
return Helpers::data_to_hex(this->message_data, this->message_length);
|
||||
}
|
||||
|
||||
// add a new rx telegram object
|
||||
@@ -163,13 +136,11 @@ void RxService::add(uint8_t * data, uint8_t length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// validate the CRC
|
||||
// validate the CRC. if it fails then increment the number of corrupt/incomplete telegrams and only report to console/syslog
|
||||
uint8_t crc = calculate_crc(data, length - 1);
|
||||
if (data[length - 1] != crc) {
|
||||
increment_telegram_error_count();
|
||||
if (EMSESP::watch() != EMSESP::Watch::WATCH_OFF) {
|
||||
LOG_ERROR(F("Rx: %s %s(CRC %02X != %02X)%s"), Helpers::data_to_hex(data, length).c_str(), COLOR_RED, data[length - 1], crc, COLOR_RESET);
|
||||
}
|
||||
LOG_ERROR(F("Rx: %s (CRC %02X != %02X)"), Helpers::data_to_hex(data, length).c_str(), data[length - 1], crc);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -220,35 +191,30 @@ void RxService::add(uint8_t * data, uint8_t length) {
|
||||
}
|
||||
|
||||
#ifdef EMSESP_DEBUG
|
||||
LOG_DEBUG(F("[DEBUG] New Rx [#%d] telegram, message length %d"), rx_telegram_id_, message_length);
|
||||
LOG_DEBUG(F("[DEBUG] New Rx telegram, message length %d"), message_length);
|
||||
#endif
|
||||
|
||||
// if we don't have a type_id or empty data block, exit
|
||||
if ((type_id == 0) || (message_length == 0)) {
|
||||
// if we don't have a type_id exit,
|
||||
// do not exit on empty message, it is checked for toggle fetch
|
||||
if (type_id == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if we receive a hc2.. telegram from 0x19.. match it to master_thermostat if master is 0x18
|
||||
src = EMSESP::check_master_device(src, type_id, true);
|
||||
|
||||
// create the telegram
|
||||
auto telegram = std::make_shared<Telegram>(Telegram::Operation::RX, src, dest, type_id, offset, message_data, message_length);
|
||||
rx_telegram = std::make_shared<Telegram>(Telegram::Operation::RX, src, dest, type_id, offset, message_data, message_length);
|
||||
|
||||
// check if queue is full, if so remove top item to make space
|
||||
if (rx_telegrams_.size() >= MAX_RX_TELEGRAMS) {
|
||||
rx_telegrams_.pop_front();
|
||||
}
|
||||
|
||||
rx_telegrams_.emplace_back(rx_telegram_id_++, std::move(telegram)); // add to queue
|
||||
// process it immediately
|
||||
EMSESP::process_telegram(rx_telegram); // further process the telegram
|
||||
increment_telegram_count(); // increase count
|
||||
}
|
||||
|
||||
//
|
||||
// Tx CODE starts here...
|
||||
//
|
||||
|
||||
TxService::QueuedTxTelegram::QueuedTxTelegram(uint16_t id, std::shared_ptr<Telegram> && telegram, bool retry)
|
||||
: id_(id)
|
||||
, telegram_(std::move(telegram))
|
||||
, retry_(retry) {
|
||||
}
|
||||
|
||||
// empty queue, don't process
|
||||
void TxService::flush_tx_queue() {
|
||||
tx_telegrams_.clear();
|
||||
@@ -290,23 +256,18 @@ void TxService::send() {
|
||||
}
|
||||
|
||||
// if there's nothing in the queue to transmit, send back a poll and quit
|
||||
// unless tx_mode is 0
|
||||
if (tx_telegrams_.empty()) {
|
||||
send_poll();
|
||||
return;
|
||||
}
|
||||
|
||||
// if we're in read-only mode (tx_mode 0) forget the Tx call
|
||||
if (tx_mode() == 0) {
|
||||
tx_telegrams_.pop_front();
|
||||
return;
|
||||
if (tx_mode() != 0) {
|
||||
send_telegram(tx_telegrams_.front());
|
||||
}
|
||||
|
||||
// send next telegram in the queue (which is actually a list!)
|
||||
send_telegram(tx_telegrams_.front());
|
||||
|
||||
// remove the telegram from the queue
|
||||
tx_telegrams_.pop_front();
|
||||
tx_telegrams_.pop_front(); // remove the telegram from the queue
|
||||
}
|
||||
|
||||
// process a Tx telegram
|
||||
@@ -326,6 +287,10 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
|
||||
// dest - for READ the MSB must be set
|
||||
// fix the READ or WRITE depending on the operation
|
||||
uint8_t dest = telegram->dest;
|
||||
|
||||
// check if we have to manipulate the id for thermostats > 0x18
|
||||
dest = EMSESP::check_master_device(dest, telegram->type_id, false);
|
||||
|
||||
if (telegram->operation == Telegram::Operation::TX_READ) {
|
||||
dest |= 0x80; // read has 8th bit set for the destination
|
||||
}
|
||||
@@ -382,7 +347,7 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
|
||||
LOG_DEBUG(F("Sending %s Tx [#%d], telegram: %s"),
|
||||
(telegram->operation == Telegram::Operation::TX_WRITE) ? F("write") : F("read"),
|
||||
tx_telegram.id_,
|
||||
telegram->to_string(telegram_raw, length).c_str());
|
||||
Helpers::data_to_hex(telegram_raw, length).c_str());
|
||||
|
||||
// send the telegram to the UART Tx
|
||||
uint16_t status = EMSuart::transmit(telegram_raw, length);
|
||||
@@ -572,11 +537,13 @@ void TxService::retry_tx(const uint8_t operation, const uint8_t * data, const ui
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef EMSESP_DEBUG
|
||||
LOG_DEBUG(F("[DEBUG] Last Tx %s operation failed. Retry #%d. sent message: %s, received: %s"),
|
||||
(operation == Telegram::Operation::TX_WRITE) ? F("Write") : F("Read"),
|
||||
retry_count_,
|
||||
telegram_last_->to_string().c_str(),
|
||||
Helpers::data_to_hex(data, length).c_str());
|
||||
#endif
|
||||
|
||||
// add to the top of the queue
|
||||
if (tx_telegrams_.size() >= MAX_TX_TELEGRAMS) {
|
||||
|
||||
@@ -20,9 +20,7 @@
|
||||
#define EMSESP_TELEGRAM_H
|
||||
|
||||
#include <string>
|
||||
#include <deque>
|
||||
#include <memory> // for unique ptrs
|
||||
#include <vector>
|
||||
#include <list>
|
||||
|
||||
// UART drivers
|
||||
#if defined(ESP8266)
|
||||
@@ -38,9 +36,9 @@
|
||||
#include "helpers.h"
|
||||
|
||||
// default values for null values
|
||||
static constexpr uint8_t VALUE_BOOL = true; // is a boolean
|
||||
static constexpr uint8_t EMS_VALUE_BOOL = 0xFF; // is a boolean
|
||||
static constexpr uint8_t EMS_VALUE_BOOL_OFF = 0x00; // boolean false. True can be 0x01 or 0xFF sometimes.
|
||||
static constexpr uint8_t EMS_VALUE_BOOL = 0xFF; // used to mark that something is a boolean
|
||||
static constexpr uint8_t EMS_VALUE_BOOL_OFF = 0x00; // boolean false
|
||||
static constexpr uint8_t EMS_VALUE_BOOL_ON = 0x01; // boolean true. True can be 0x01 or 0xFF sometimes
|
||||
|
||||
static constexpr uint8_t EMS_VALUE_BOOL_NOTSET = 0xFE; // random number for booleans, that's not 0, 1 or FF
|
||||
static constexpr uint8_t EMS_VALUE_UINT_NOTSET = 0xFF; // for 8-bit unsigned ints/bytes
|
||||
@@ -83,8 +81,8 @@ class Telegram {
|
||||
TX_WRITE,
|
||||
};
|
||||
|
||||
std::string to_string_message() const;
|
||||
std::string to_string() const;
|
||||
std::string to_string(const uint8_t * telegram, uint8_t length) const;
|
||||
|
||||
// reads a bit value from a given telegram position
|
||||
void read_bitvalue(uint8_t & value, const uint8_t index, const uint8_t bit) const {
|
||||
@@ -112,8 +110,7 @@ class Telegram {
|
||||
|
||||
value = 0;
|
||||
for (uint8_t i = 0; i < size; i++) {
|
||||
// shift
|
||||
value = (value << 8) + message_data[abs_index + i];
|
||||
value = (value << 8) + message_data[abs_index + i]; // shift
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,63 +195,39 @@ class EMSbus {
|
||||
|
||||
class RxService : public EMSbus {
|
||||
public:
|
||||
static constexpr size_t MAX_RX_TELEGRAMS = 20;
|
||||
|
||||
RxService() = default;
|
||||
~RxService() = default;
|
||||
|
||||
void loop();
|
||||
|
||||
void add(uint8_t * data, uint8_t length);
|
||||
|
||||
void flush_rx_queue();
|
||||
|
||||
uint16_t telegram_count() const {
|
||||
return telegram_count_;
|
||||
}
|
||||
|
||||
uint16_t telegram_error_count() const {
|
||||
return telegram_error_count_;
|
||||
}
|
||||
|
||||
void increment_telegram_count() {
|
||||
telegram_count_++;
|
||||
}
|
||||
|
||||
uint16_t telegram_error_count() const {
|
||||
return telegram_error_count_;
|
||||
}
|
||||
|
||||
void increment_telegram_error_count() {
|
||||
telegram_error_count_++;
|
||||
}
|
||||
|
||||
class QueuedRxTelegram {
|
||||
public:
|
||||
QueuedRxTelegram(uint16_t id, std::shared_ptr<Telegram> && telegram);
|
||||
~QueuedRxTelegram() = default;
|
||||
|
||||
uint16_t id_; // sequential identifier
|
||||
const std::shared_ptr<const Telegram> telegram_;
|
||||
};
|
||||
|
||||
const std::deque<QueuedRxTelegram> queue() const {
|
||||
return rx_telegrams_;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t last_rx_check_ = 0;
|
||||
|
||||
uint8_t rx_telegram_id_ = 0; // queue counter
|
||||
|
||||
uint16_t telegram_count_ = 0; // # Rx received
|
||||
uint16_t telegram_error_count_ = 0; // # Rx CRC errors
|
||||
|
||||
std::deque<QueuedRxTelegram> rx_telegrams_;
|
||||
uint16_t telegram_count_ = 0; // # Rx received
|
||||
uint16_t telegram_error_count_ = 0; // # Rx CRC errors
|
||||
std::shared_ptr<const Telegram> rx_telegram; // the incoming Rx telegram
|
||||
};
|
||||
|
||||
class TxService : public EMSbus {
|
||||
public:
|
||||
static constexpr size_t MAX_TX_TELEGRAMS = 30; // size of Tx queue
|
||||
static constexpr size_t MAX_TX_TELEGRAMS = 20; // size of Tx queue
|
||||
|
||||
static constexpr uint8_t TX_WRITE_FAIL = 4;
|
||||
static constexpr uint8_t TX_WRITE_SUCCESS = 1;
|
||||
static constexpr uint8_t TX_WRITE_FAIL = 4; // EMS return code for fail
|
||||
static constexpr uint8_t TX_WRITE_SUCCESS = 1; // EMS return code for success
|
||||
|
||||
TxService() = default;
|
||||
~TxService() = default;
|
||||
@@ -337,26 +310,30 @@ class TxService : public EMSbus {
|
||||
|
||||
class QueuedTxTelegram {
|
||||
public:
|
||||
QueuedTxTelegram(uint16_t id, std::shared_ptr<Telegram> && telegram, bool retry);
|
||||
~QueuedTxTelegram() = default;
|
||||
|
||||
uint16_t id_; // sequential identifier
|
||||
const uint16_t id_;
|
||||
const std::shared_ptr<const Telegram> telegram_;
|
||||
bool retry_; // is a retry
|
||||
const bool retry_; // is a retry
|
||||
|
||||
~QueuedTxTelegram() = default;
|
||||
QueuedTxTelegram(uint16_t id, std::shared_ptr<Telegram> && telegram, bool retry)
|
||||
: id_(id)
|
||||
, telegram_(std::move(telegram))
|
||||
, retry_(retry) {
|
||||
}
|
||||
};
|
||||
|
||||
const std::deque<QueuedTxTelegram> queue() const {
|
||||
const std::list<QueuedTxTelegram> queue() const {
|
||||
return tx_telegrams_;
|
||||
}
|
||||
|
||||
#if defined(EMSESP_DEBUG)
|
||||
static constexpr uint8_t MAXIMUM_TX_RETRIES = 0; // when compiled with EMSESP_DEBUG don't retry
|
||||
#else
|
||||
static constexpr uint8_t MAXIMUM_TX_RETRIES = 3;
|
||||
#endif
|
||||
|
||||
private:
|
||||
uint8_t tx_telegram_id_ = 0; // queue counter
|
||||
|
||||
uint32_t last_tx_check_ = 0;
|
||||
|
||||
std::deque<QueuedTxTelegram> tx_telegrams_;
|
||||
std::list<QueuedTxTelegram> tx_telegrams_; // the Tx queue
|
||||
|
||||
uint16_t telegram_read_count_ = 0; // # Tx successful reads
|
||||
uint16_t telegram_write_count_ = 0; // # Tx successful writes
|
||||
@@ -364,7 +341,9 @@ class TxService : public EMSbus {
|
||||
|
||||
std::shared_ptr<Telegram> telegram_last_;
|
||||
uint16_t telegram_last_post_send_query_; // which type ID to query after a successful send, to read back the values just written
|
||||
uint8_t retry_count_ = 0; // count for # Tx retries
|
||||
uint8_t retry_count_ = 0; // count for # Tx retries
|
||||
|
||||
uint8_t tx_telegram_id_ = 0; // queue counter
|
||||
|
||||
void send_telegram(const QueuedTxTelegram & tx_telegram);
|
||||
void send_telegram(const uint8_t * data, const uint8_t length);
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
|
||||
#if defined(EMSESP_DEBUG)
|
||||
|
||||
#include "test.h"
|
||||
|
||||
@@ -26,6 +27,14 @@ namespace emsesp {
|
||||
// create some fake test data
|
||||
// used with the 'test' command, under su/admin
|
||||
void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
if (command == "default") {
|
||||
run_test(shell, "mqtt"); // add the default test case here
|
||||
}
|
||||
|
||||
if (command.empty()) {
|
||||
run_test(shell, "default");
|
||||
}
|
||||
|
||||
if (command == "render") {
|
||||
uint8_t test1 = 12;
|
||||
int8_t test2 = -12;
|
||||
@@ -129,7 +138,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
rx_telegram({0x08, 0x00, 0x07, 0x00, 0x0B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||
}
|
||||
|
||||
if (command == "boiler2") {
|
||||
if (command == "boiler") {
|
||||
// question: do we need to set the mask?
|
||||
std::string version("1.2.3");
|
||||
EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline
|
||||
@@ -142,11 +151,9 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
|
||||
// add boiler
|
||||
EMSESP::add_device(0x08, 84, version, EMSdevice::Brand::BUDERUS);
|
||||
EMSESP::rxservice_.loop();
|
||||
|
||||
// add Controller - BC10 GB142 - but using the same device_id to see what happens
|
||||
EMSESP::add_device(0x09, 84, version, EMSdevice::Brand::BUDERUS);
|
||||
EMSESP::rxservice_.loop();
|
||||
|
||||
// simulate getting version information back from an unknown device
|
||||
// note there is no brand (byte 9)
|
||||
@@ -172,25 +179,64 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
// add thermostat - Thermostat: RC300/RC310/Moduline 3000/CW400/Sense II (DeviceID:0x10, ProductID:158, Version:03.03) ** master device **
|
||||
std::string version("01.03");
|
||||
EMSESP::add_device(0x10, 158, version, EMSdevice::Brand::BUDERUS);
|
||||
EMSESP::rxservice_.loop();
|
||||
|
||||
// simulate incoming telegram
|
||||
// Thermostat(0x10) -> 48(0x48), ?(0x26B), data: 6B 08 4F 00 00 00 02 00 00 00 02 00 03 00 03 00 03
|
||||
rx_telegram({0x10, 0x48, 0xFF, 00, 01, 0x6B, 00, 0x6B, 0x08, 0x4F, 00, 00, 00, 02, 00, 00, 00, 02, 00, 03, 00, 03, 00, 03});
|
||||
}
|
||||
|
||||
if (command == "thermostat") {
|
||||
shell.printfln(F("Testing adding devices on the EMS bus..."));
|
||||
if (command == "general") {
|
||||
shell.printfln(F("Testing adding a boiler & thermostat..."));
|
||||
|
||||
// create some fake devices
|
||||
std::string version("1.2.3");
|
||||
EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline
|
||||
EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
|
||||
|
||||
// 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});
|
||||
|
||||
// Boiler -> Thermostat, UBAParameterWW(0x33), telegram: 08 97 33 00 23 24 (#data=2)
|
||||
uart_telegram({0x08, 0x98, 0x33, 0x00, 0x23, 0x24});
|
||||
|
||||
// Boiler -> Me, UBAParameterWW(0x33), telegram: 08 0B 33 00 08 FF 34 FB 00 28 00 00 46 00 FF FF 00 (#data=13)
|
||||
uart_telegram({0x08, 0x0B, 0x33, 0x00, 0x08, 0xFF, 0x34, 0xFB, 0x00, 0x28, 0x00, 0x00, 0x46, 0x00, 0xFF, 0xFF, 0x00});
|
||||
|
||||
// Thermostat 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});
|
||||
|
||||
shell.invoke_command("show");
|
||||
// shell.invoke_command("system");
|
||||
// shell.invoke_command("show mqtt");
|
||||
|
||||
// shell.loop_all();
|
||||
}
|
||||
|
||||
if (command == "thermostat") {
|
||||
shell.printfln(F("Testing adding a thermostat to the EMS bus..."));
|
||||
|
||||
// add_device(0x10, 165, version, EMSdevice::Brand::BUDERUS);
|
||||
// add_device(0x17, 125, version, EMSdevice::Brand::BUDERUS); // test unknown class test
|
||||
// add_device(0x17, 93, version, EMSdevice::Brand::BUDERUS);
|
||||
// add_device(0x17, 254, version, EMSdevice::Brand::BUDERUS); // test unknown product_id
|
||||
|
||||
// EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
|
||||
|
||||
std::string version("1.2.3");
|
||||
|
||||
// add a boiler
|
||||
// EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline
|
||||
|
||||
// add a thermostat
|
||||
EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
|
||||
|
||||
// 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});
|
||||
|
||||
shell.loop_all();
|
||||
}
|
||||
|
||||
if (command == "solar") {
|
||||
@@ -201,8 +247,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
std::string version("1.2.3");
|
||||
EMSESP::add_device(0x30, 163, version, EMSdevice::Brand::BUDERUS); // SM100
|
||||
|
||||
EMSESP::rxservice_.loop();
|
||||
|
||||
// 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,
|
||||
@@ -221,12 +265,10 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
uart_telegram("30 00 FF 0A 02 6A 04"); // SM100 pump on 1
|
||||
uart_telegram("30 00 FF 00 02 64 00 00 00 04 00 00 FF 00 00 1E 0B 09 64 00 00 00 00"); // SM100 modulation
|
||||
|
||||
EMSESP::rxservice_.loop();
|
||||
EMSESP::show_device_values(shell);
|
||||
|
||||
uart_telegram("30 00 FF 0A 02 6A 03"); // SM100 pump off 0
|
||||
|
||||
EMSESP::rxservice_.loop();
|
||||
EMSESP::show_device_values(shell);
|
||||
}
|
||||
|
||||
@@ -240,12 +282,10 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
std::string version("1.2.3");
|
||||
EMSESP::add_device(0x10, 158, version, EMSdevice::Brand::BUDERUS); // RC300
|
||||
EMSESP::add_device(0x48, 189, version, EMSdevice::Brand::BUDERUS); // KM200
|
||||
EMSESP::rxservice_.loop();
|
||||
|
||||
// see https://github.com/proddy/EMS-ESP/issues/390
|
||||
|
||||
/*
|
||||
|
||||
uart_telegram_withCRC("90 48 FF 04 01 A6 5C");
|
||||
uart_telegram_withCRC("90 48 FF 00 01 A6 4C");
|
||||
uart_telegram_withCRC("90 48 FF 08 01 A7 6D");
|
||||
@@ -283,7 +323,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
uart_telegram_withCRC("C8 90 FF 00 02 01 A6 D0");
|
||||
|
||||
// uart_telegram_withCRC("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");
|
||||
|
||||
*/
|
||||
|
||||
uart_telegram_withCRC("C8 90 F7 02 01 FF 01 A6 BA");
|
||||
@@ -295,7 +334,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
uart_telegram_withCRC("90 00 FF 19 01 A5 01 04 00 00 00 00 FF 64 2A 00 3C 01 FF 92");
|
||||
|
||||
EMSESP::show_ems(shell);
|
||||
EMSESP::rxservice_.loop();
|
||||
EMSESP::show_device_values(shell);
|
||||
}
|
||||
|
||||
@@ -308,7 +346,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
|
||||
std::string version("1.2.3");
|
||||
EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
|
||||
EMSESP::rxservice_.loop();
|
||||
|
||||
// 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)
|
||||
@@ -318,8 +355,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
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
|
||||
|
||||
shell.loop_all();
|
||||
EMSESP::rxservice_.loop();
|
||||
EMSESP::txservice_.flush_tx_queue();
|
||||
shell.loop_all();
|
||||
EMSESP::show_device_values(shell);
|
||||
@@ -337,6 +372,13 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
EMSESP::txservice_.send(); // send it to UART
|
||||
}
|
||||
|
||||
if (command == "rx2") {
|
||||
shell.printfln(F("Testing Rx2..."));
|
||||
for (uint8_t i = 0; i < 30; i++) {
|
||||
uart_telegram({0x08, 0x0B, 0x33, 0x00, 0x08, 0xFF, 0x34, 0xFB, 0x00, 0x28, 0x00, 0x00, 0x46, 0x00, 0xFF, 0xFF, 0x00});
|
||||
}
|
||||
}
|
||||
|
||||
if (command == "rx") {
|
||||
shell.printfln(F("Testing Rx..."));
|
||||
|
||||
@@ -473,49 +515,89 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
EMSESP::txservice_.flush_tx_queue();
|
||||
}
|
||||
|
||||
if (command == "mqtt1") {
|
||||
if (command == "pin") {
|
||||
shell.printfln(F("Testing pin..."));
|
||||
|
||||
EMSESP::add_context_menus(); // need to add this as it happens later in the code
|
||||
shell.invoke_command("su");
|
||||
shell.invoke_command("system");
|
||||
shell.invoke_command("help");
|
||||
shell.invoke_command("pin");
|
||||
shell.invoke_command("pin 1 true");
|
||||
|
||||
shell.loop_all();
|
||||
}
|
||||
|
||||
if (command == "mqtt") {
|
||||
shell.printfln(F("Testing MQTT..."));
|
||||
|
||||
// MQTT test
|
||||
// add a boiler
|
||||
// question: do we need to set the mask?
|
||||
std::string version("1.2.3");
|
||||
EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline
|
||||
|
||||
// add a thermostat
|
||||
EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
|
||||
|
||||
// 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("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
|
||||
|
||||
shell.loop_all();
|
||||
|
||||
char boiler_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
char thermostat_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
char system_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
|
||||
// test publish and adding to queue
|
||||
EMSESP::txservice_.flush_tx_queue();
|
||||
EMSESP::EMSESP::mqtt_.publish("boiler_cmd", "test me");
|
||||
// EMSESP::mqtt_.show_queue();
|
||||
// simulate an incoming mqtt msg
|
||||
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
char payload[100];
|
||||
Mqtt::show_mqtt(shell); // show queue
|
||||
|
||||
strcpy(topic, "boiler_cmd");
|
||||
strcpy(payload, "12345");
|
||||
EMSESP::mqtt_.incoming(topic, payload);
|
||||
EMSESP::mqtt_.incoming(payload, payload); // should report error
|
||||
strcpy(boiler_topic, "ems-esp/boiler_cmd");
|
||||
strcpy(thermostat_topic, "ems-esp/thermostat_cmd");
|
||||
strcpy(system_topic, "ems-esp/saystem_cmd");
|
||||
|
||||
strcpy(topic, "thermostat_cmd_mode");
|
||||
strcpy(payload, "auto");
|
||||
EMSESP::mqtt_.incoming(topic, payload);
|
||||
EMSESP::mqtt_.incoming(boiler_topic, "12345"); // invalid format
|
||||
EMSESP::mqtt_.incoming("bad_topic", "12345"); // no matching topic
|
||||
EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"garbage\",\"data\":22.52}"); // should report error
|
||||
EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"comfort\",\"data\":\"eco\"}");
|
||||
EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"wwactivated\",\"data\":\"1\"}");
|
||||
EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"wwactivated\",\"data\":1}");
|
||||
EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"flowtemp\",\"data\":55}");
|
||||
|
||||
strcpy(topic, "thermostat_cmd_temp");
|
||||
strcpy(payload, "20");
|
||||
EMSESP::mqtt_.incoming(topic, payload);
|
||||
EMSESP::mqtt_.incoming(system_topic, "{\"cmd\":\"send\",\"data\":\"01 02 03 04 05\"}");
|
||||
EMSESP::mqtt_.incoming(system_topic, "{\"cmd\":\"pin\",\"id\":12,\"data\":\"1\"}");
|
||||
|
||||
strcpy(topic, "thermostat_cmd");
|
||||
strcpy(payload, "{\"cmd\":\"temp\",\"data\":22.52}");
|
||||
EMSESP::mqtt_.incoming(topic, payload);
|
||||
|
||||
strcpy(topic, "boiler_cmd_wwtemp");
|
||||
strcpy(payload, "66");
|
||||
EMSESP::mqtt_.incoming(topic, payload);
|
||||
|
||||
strcpy(topic, "thermostat_cmd");
|
||||
strcpy(payload, "{\"cmd\":\"temp\",\"hc\":2,\"data\":22}");
|
||||
EMSESP::mqtt_.incoming(topic, payload);
|
||||
|
||||
strcpy(topic, "home/ems-esp/cmd");
|
||||
strcpy(payload, "restart");
|
||||
EMSESP::mqtt_.incoming(topic, payload);
|
||||
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"wwmode\",\"data\":\"auto\"}");
|
||||
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"control\",\"data\":\"1\"}");
|
||||
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"control\",\"data\":1}");
|
||||
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"mode\",\"data\":\"auto\",\"id\":2}");
|
||||
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"mode\",\"data\":\"auto\",\"hc\":2}"); // hc as number
|
||||
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"mode\",\"data\":\"auto\",\"hc\":\"2\"}"); // hc as string
|
||||
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"temp\",\"data\":22.56}");
|
||||
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"temp\",\"data\":22}");
|
||||
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"temp\",\"data\":\"22.56\"}");
|
||||
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"temp\",\"id\":2,\"data\":22}");
|
||||
|
||||
// EMSESP::txservice_.show_tx_queue();
|
||||
// EMSESP::publish_all_values();
|
||||
|
||||
EMSESP::publish_all_values();
|
||||
EMSESP::add_context_menus(); // need to add this as it happens later in the code
|
||||
shell.invoke_command("su");
|
||||
shell.invoke_command("thermostat");
|
||||
shell.invoke_command("help");
|
||||
shell.invoke_command("call");
|
||||
shell.invoke_command("call wwmode");
|
||||
shell.invoke_command("call mode auto 2");
|
||||
shell.invoke_command("call temp 22.56");
|
||||
|
||||
Mqtt::resubscribe();
|
||||
Mqtt::show_mqtt(shell); // show queue
|
||||
|
||||
shell.loop_all();
|
||||
}
|
||||
|
||||
if (command == "poll2") {
|
||||
@@ -526,8 +608,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
uint8_t poll[1] = {0x8B};
|
||||
EMSESP::incoming_telegram(poll, 1);
|
||||
|
||||
EMSESP::rxservice_.loop();
|
||||
|
||||
EMSESP::show_ems(shell);
|
||||
EMSESP::txservice_.flush_tx_queue();
|
||||
}
|
||||
@@ -542,12 +622,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
uart_telegram({0x21, 0x0B, 0xFF, 0x00});
|
||||
}
|
||||
|
||||
if (command == "mqtt2") {
|
||||
for (uint8_t i = 0; i < 30; i++) {
|
||||
Mqtt::subscribe("topic", dummy_mqtt_commands);
|
||||
}
|
||||
}
|
||||
|
||||
// testing the UART tx command, without a queue
|
||||
if (command == "tx2") {
|
||||
uint8_t t[] = {0x0B, 0x88, 0x18, 0x00, 0x20, 0xD4}; // including CRC
|
||||
@@ -571,8 +645,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
|
||||
EMSESP::add_device(0x20, 160, version, EMSdevice::Brand::BOSCH); // MM100
|
||||
|
||||
EMSESP::rxservice_.loop();
|
||||
|
||||
// WWC1 on 0x29
|
||||
rx_telegram({0xA9, 0x00, 0xFF, 0x00, 0x02, 0x32, 0x02, 0x6C, 0x00, 0x3C, 0x00, 0x3C, 0x3C, 0x46, 0x02, 0x03, 0x03, 0x00, 0x3C});
|
||||
|
||||
@@ -591,20 +663,26 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
void Test::rx_telegram(const std::vector<uint8_t> & rx_data) {
|
||||
uint8_t len = rx_data.size();
|
||||
uint8_t data[50];
|
||||
std::copy(rx_data.begin(), rx_data.end(), data);
|
||||
data[len] = EMSESP::rxservice_.calculate_crc(rx_data.data(), len);
|
||||
uint8_t i = 0;
|
||||
while (len--) {
|
||||
data[i] = rx_data[i];
|
||||
i++;
|
||||
}
|
||||
data[i] = EMSESP::rxservice_.calculate_crc(data, i);
|
||||
EMSESP::rxservice_.add(data, len + 1);
|
||||
EMSESP::rxservice_.loop();
|
||||
}
|
||||
|
||||
// simulates a telegram straight from UART, but without the CRC which is added automatically
|
||||
void Test::uart_telegram(const std::vector<uint8_t> & rx_data) {
|
||||
uint8_t len = rx_data.size();
|
||||
uint8_t data[50];
|
||||
std::copy(rx_data.begin(), rx_data.end(), data);
|
||||
data[len] = EMSESP::rxservice_.calculate_crc(rx_data.data(), len);
|
||||
EMSESP::incoming_telegram(data, len + 1);
|
||||
EMSESP::rxservice_.loop();
|
||||
uint8_t i = 0;
|
||||
while (len--) {
|
||||
data[i] = rx_data[i];
|
||||
i++;
|
||||
}
|
||||
data[i] = EMSESP::rxservice_.calculate_crc(data, i);
|
||||
EMSESP::incoming_telegram(data, i + 1);
|
||||
}
|
||||
|
||||
// takes raw string, assuming it contains the CRC. This is what is output from 'watch raw'
|
||||
@@ -686,9 +764,6 @@ void Test::uart_telegram(const char * rx_data) {
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
|
||||
void Test::dummy_mqtt_commands(const char * message) {
|
||||
//
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
#if defined(EMSESP_DEBUG)
|
||||
|
||||
#ifndef EMSESP_TEST_H
|
||||
#define EMSESP_TEST_H
|
||||
@@ -36,8 +36,6 @@
|
||||
#include "mqtt.h"
|
||||
#include "emsesp.h"
|
||||
|
||||
MAKE_PSTR_WORD(test)
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class Test {
|
||||
|
||||
@@ -1 +1 @@
|
||||
#define EMSESP_APP_VERSION "2.0.0b11"
|
||||
#define EMSESP_APP_VERSION "2.0.0b12"
|
||||
|
||||
Reference in New Issue
Block a user