Merge branch 'v2_cmd' into v2

This commit is contained in:
proddy
2020-08-19 13:28:20 +02:00
70 changed files with 9906 additions and 1973 deletions

View File

@@ -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();

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)},

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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() {

View File

@@ -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

View File

@@ -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 %
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
};

View File

@@ -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

View File

@@ -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_;

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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
View 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>")

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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) {

View File

@@ -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

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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

View File

@@ -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 {

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "2.0.0b11"
#define EMSESP_APP_VERSION "2.0.0b12"