initial commit with refactored mqtt commands

This commit is contained in:
proddy
2020-08-03 23:14:43 +02:00
parent 56c4e043fe
commit 6154ff38f2
41 changed files with 1208 additions and 1351 deletions

View File

@@ -66,59 +66,49 @@ common commands available in all contexts:
su su
(from the root) (from the root)
set set
fetch fetch
system (enters a context) system (enters a context)
boiler (enters a context) boiler (enters a context)
thermostat (enters a context) thermostat (enters a context)
scan devices [deep] scan devices [deep]
send telegram <"XX XX ..."> send telegram <"XX XX ...">
set bus_id <device ID> set bus_id <device ID>
set tx_mode <n> set tx_mode <n>
show show
show devices show devices
show ems show ems
show values show values
system system
set set
show show
show mqtt show mqtt
show users show users
passwd passwd
restart restart
set wifi hostname <name> set wifi hostname <name>
set wifi password set wifi password
set wifi ssid <name> set wifi ssid <name>
wifi reconnect wifi reconnect
boiler boiler
comfort <hot |eco | intelligent> read <type ID>
flowtemp <degrees> call [cmd] [n] (cmd's: comfort wwactivated wwtapactivated wwonetime wwcirculation flowtemp wwtemp burnmaxpower burnminpower boilhyston boilhystoff burnperiod pumpdelay)
wwactive <on | off>
wwcirculation <on | off>
wwonetime <on | off>
wwtemp <degrees>
read <type ID>
maxpower <%>
minpower <%>
thermostat thermostat
set set
set master [device ID] set master [device ID]
mode <mode> [heating circuit] read <type ID>
temp <degrees> [heating circuit] call [cmd] [n] (cmd's: wwmode control mode holiday pause party datetime minexttemp clockoffset calinttemp display building language remotetemp temp nighttemp daytemp nofrosttemp ecotemp heattemp summertemp designtemp offsettemp holidaytemp)
read <type ID>
``` ```
---------- ----------
### **mqtt commands** ### **mqtt commands**
commands can be written as `{"cmd": ,"data": }` or direct. commands must be written as `{"cmd":<cmd> ,"data":<data>, "id":<n> }`. The `id` can be replaced with `hc` for some devices.
To set thermostat commands depending on heatingcircuits add `"hc": ` or nest the command. Without `"hc":` the first ative heatingcircuit is set.
These commands are equivalent:
`{"hc":2,"cmd":"daytemp","data":21}` or `{"hc":2,"daytemp":21}`or `{"hc2"{"daytemp":21}}`
In direct commands it's possible to combine commands, i.e. `{"hc1":{"daytemp":21,"nighttemp":17},"hc2":{"daytemp":20}}`
``` ```
*boiler_cmd* *boiler_cmd*
comfort <hot, eco, intelligent> comfort <hot, eco, intelligent>
@@ -161,10 +151,7 @@ In direct commands it's possible to combine commands, i.e. `{"hc1":{"daytemp":21
*cmd* *cmd*
send <"0B XX XX .."> send <"0B XX XX ..">
D0 <0 | 1> gpio <0 | 1> <0-3> (for D0-D3)
D1 <0 | 1>
D2 <0 | 1>
D3 <0 | 1>
``` ```
@@ -180,13 +167,6 @@ In direct commands it's possible to combine commands, i.e. `{"hc1":{"daytemp":21
- See if it's easier to use timers instead of millis() based timers, using [polledTimeout](https://github.com/esp8266/Arduino/blob/master/libraries/esp8266/examples/BlinkPolledTimeout/BlinkPolledTimeout.ino). - See if it's easier to use timers instead of millis() based timers, using [polledTimeout](https://github.com/esp8266/Arduino/blob/master/libraries/esp8266/examples/BlinkPolledTimeout/BlinkPolledTimeout.ino).
- Port over to ESP-IDF. The Arduino SDK is showing its limitations - Port over to ESP-IDF. The Arduino SDK is showing its limitations
### **Features to add**
- Multi-language. German, Dutch, French
- Click on a device in the Web UI shows it's details
- Publish time can be customized per device (solar, mixing etc)
- add homeassistant mqtt discovery for Boiler as well
### **Customizing the Web UI** ### **Customizing the Web UI**
The Web is based off Rick's awesome [esp8266-react](https://github.com/rjwats/esp8266-react/) framework. These are the files that are modified: The Web is based off Rick's awesome [esp8266-react](https://github.com/rjwats/esp8266-react/) framework. These are the files that are modified:

View File

@@ -202,7 +202,7 @@ uint8_t OneWire::reset(void) {
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
void IRAM_ATTR OneWire::write_bit(uint8_t v) { void IRAM_ATTR OneWire::write_bit(uint8_t v) {
#else #else
void OneWire::write_bit(uint8_t v) { void OneWire::write_bit(uint8_t v) {
#endif #endif
IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask; IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask;
volatile IO_REG_TYPE * reg IO_REG_BASE_ATTR = baseReg; volatile IO_REG_TYPE * reg IO_REG_BASE_ATTR = baseReg;

View File

@@ -105,10 +105,10 @@ class OneWire {
// Write a bit. The bus is always left powered at the end, see // Write a bit. The bus is always left powered at the end, see
// note in write() about that. // note in write() about that.
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
void IRAM_ATTR write_bit(uint8_t v); void IRAM_ATTR write_bit(uint8_t v);
#else #else
void write_bit(uint8_t v); void write_bit(uint8_t v);
#endif #endif
// Read a bit. // Read a bit.

View File

@@ -54,7 +54,7 @@ void NTPSettingsService::onStationModeDisconnected(const WiFiEventStationModeDis
void NTPSettingsService::configureNTP() { void NTPSettingsService::configureNTP() {
if (WiFi.isConnected() && _state.enabled) { if (WiFi.isConnected() && _state.enabled) {
Serial.println(F("Starting NTP...")); // Serial.println(F("Starting NTP..."));
#ifdef ESP32 #ifdef ESP32
configTzTime(_state.tzFormat.c_str(), _state.server.c_str()); configTzTime(_state.tzFormat.c_str(), _state.server.c_str());
#elif defined(ESP8266) #elif defined(ESP8266)

View File

@@ -22,11 +22,6 @@ static void check(const JsonObject object, const char (&expected_data)[N]) {
check(object, expected_data, expected_len); check(object, expected_data, expected_len);
} }
// TODO: used by the commented test
// static void check(const JsonObject object, const std::string& expected) {
// check(object, expected.data(), expected.length());
//}
TEST_CASE("serialize MsgPack object") { TEST_CASE("serialize MsgPack object") {
DynamicJsonDocument doc(4096); DynamicJsonDocument doc(4096);
JsonObject object = doc.to<JsonObject>(); JsonObject object = doc.to<JsonObject>();
@@ -54,23 +49,6 @@ TEST_CASE("serialize MsgPack object") {
"iC\x0C\xA2iD\x0D\xA2iE\x0E\xA2iF\x0F"); "iC\x0C\xA2iD\x0D\xA2iE\x0E\xA2iF\x0F");
} }
// TODO: improve performance and uncomment
// SECTION("map 32") {
// std::string expected("\xDF\x00\x01\x00\x00", 5);
//
// for (int i = 0; i < 65536; ++i) {
// char kv[16];
// sprintf(kv, "%04x", i);
// object[kv] = kv;
// expected += '\xA4';
// expected += kv;
// expected += '\xA4';
// expected += kv;
// }
//
// check(object, expected);
// }
SECTION("serialized(const char*)") { SECTION("serialized(const char*)") {
object["hello"] = serialized("\xDB\x00\x01\x00\x00", 5); object["hello"] = serialized("\xDB\x00\x01\x00\x00", 5);
check(object, "\x81\xA5hello\xDB\x00\x01\x00\x00"); check(object, "\x81\xA5hello\xDB\x00\x01\x00\x00");

View File

@@ -10323,7 +10323,6 @@ namespace Catch {
virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE {
StreamingReporterBase::testGroupEnded( testGroupStats ); StreamingReporterBase::testGroupEnded( testGroupStats );
// TODO: Check testGroupStats.aborting and act accordingly.
m_xml.scopedElement( "OverallResults" ) m_xml.scopedElement( "OverallResults" )
.writeAttribute( "successes", testGroupStats.totals.assertions.passed ) .writeAttribute( "successes", testGroupStats.totals.assertions.passed )
.writeAttribute( "failures", testGroupStats.totals.assertions.failed ) .writeAttribute( "failures", testGroupStats.totals.assertions.failed )

View File

@@ -23,7 +23,6 @@ class MsgPackDeserializer {
TStringStorage stringStorage) TStringStorage stringStorage)
: _pool(&pool), _reader(reader), _stringStorage(stringStorage) {} : _pool(&pool), _reader(reader), _stringStorage(stringStorage) {}
// TODO: add support for filter
DeserializationError parse(VariantData &variant, AllowAllFilter, DeserializationError parse(VariantData &variant, AllowAllFilter,
NestingLimit nestingLimit) { NestingLimit nestingLimit) {
return parse(variant, nestingLimit); return parse(variant, nestingLimit);
@@ -40,7 +39,6 @@ class MsgPackDeserializer {
} }
if ((code & 0xe0) == 0xe0) { if ((code & 0xe0) == 0xe0) {
// TODO: add setNegativeInteger()
variant.setSignedInteger(static_cast<int8_t>(code)); variant.setSignedInteger(static_cast<int8_t>(code));
return DeserializationError::Ok; return DeserializationError::Ok;
} }

View File

@@ -27,6 +27,7 @@ CXX_STANDARD := -std=c++11
# Defined Symbols # Defined Symbols
#---------------------------------------------------------------------- #----------------------------------------------------------------------
DEFINES += -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_NO_LED DEFINES += -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_NO_LED
DEFINES += -DRUN_TEST
#---------------------------------------------------------------------- #----------------------------------------------------------------------
# Sources & Files # Sources & Files

View File

@@ -1,8 +1,8 @@
; PlatformIO Project Configuration File for EMS-ESP ; PlatformIO Project Configuration File for EMS-ESP
[platformio] [platformio]
; default_envs = esp8266 default_envs = esp8266
default_envs = esp32 ; default_envs = esp32
# override any settings with your own local ones in pio_local.ini # override any settings with your own local ones in pio_local.ini
extra_configs = extra_configs =
@@ -76,7 +76,7 @@ board_build.f_cpu = 160000000L ; 160MHz
; eagle.flash.4m1m.ld = 1019 KB sketch, 1000 KB SPIFFS. 4KB EEPROM, 4KB RFCAL, 12KB WIFI stack, 2052 KB OTA & buffer ; eagle.flash.4m1m.ld = 1019 KB sketch, 1000 KB SPIFFS. 4KB EEPROM, 4KB RFCAL, 12KB WIFI stack, 2052 KB OTA & buffer
; eagle.flash.4m2m.ld = 1019 KB sketch, 2024 KB SPIFFS. 4KB EEPROM, 4KB RFCAL, 12KB WIFI stack, 1028 KB OTA & buffer ; eagle.flash.4m2m.ld = 1019 KB sketch, 2024 KB SPIFFS. 4KB EEPROM, 4KB RFCAL, 12KB WIFI stack, 1028 KB OTA & buffer
; board_build.ldscript = eagle.flash.4m2m.ld ; board_build.ldscript = eagle.flash.4m2m.ld
build_flags = ${common.build_flags} ${common.debug_flags} -DFT_NTP=0 build_flags = ${common.build_flags} ${common.debug_flags} -D FT_NTP=0
lib_ignore = lib_ignore =
AsyncTCP AsyncTCP
@@ -87,4 +87,4 @@ build_type = release
platform = https://github.com/platformio/platform-espressif32.git platform = https://github.com/platformio/platform-espressif32.git
board_build.partitions = min_spiffs.csv ; https://github.com/espressif/arduino-esp32/blob/master/tools/partitions/ board_build.partitions = min_spiffs.csv ; https://github.com/espressif/arduino-esp32/blob/master/tools/partitions/
lib_deps = ${common.libs_core} lib_deps = ${common.libs_core}
build_flags = ${common.build_flags} ${common.debug_flags} -DFT_NTP=1 build_flags = ${common.build_flags} ${common.debug_flags} -D FT_NTP=1

0
scripts/build.sh Executable file → Normal file
View File

View File

@@ -91,6 +91,12 @@ void EMSESPShell::display_banner() {
// turn off watch // turn off watch
emsesp::EMSESP::watch_id(WATCH_ID_NONE); emsesp::EMSESP::watch_id(WATCH_ID_NONE);
emsesp::EMSESP::watch(EMSESP::WATCH_OFF); emsesp::EMSESP::watch(EMSESP::WATCH_OFF);
#if defined(EMSESP_STANDALONE)
#ifdef RUN_TEST
invoke_command("test"); // "test default"
#endif
#endif
} }
// pre-loads all the console commands into the MAIN context // pre-loads all the console commands into the MAIN context
@@ -285,11 +291,15 @@ void Console::enter_custom_context(Shell & shell, unsigned int context) {
void Console::load_standard_commands(unsigned int context) { void Console::load_standard_commands(unsigned int context) {
#ifdef EMSESP_STANDALONE #ifdef EMSESP_STANDALONE
EMSESPShell::commands->add_command(context, EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN, CommandFlags::USER,
flash_string_vector{F_(test)}, 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))) { [](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 #endif

View File

@@ -50,69 +50,8 @@ using uuid::log::Level;
#define F_(string_name) FPSTR(__pstr__##string_name) #define F_(string_name) FPSTR(__pstr__##string_name)
// clang-format on // clang-format on
// common words // localizations
MAKE_PSTR_WORD(exit) #include "locale_EN.h"
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>")
#ifdef LOCAL #ifdef LOCAL
#undef LOCAL #undef LOCAL

View File

@@ -18,37 +18,15 @@
#include "boiler.h" #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 { namespace emsesp {
REGISTER_FACTORY(Boiler, EMSdevice::DeviceType::BOILER) 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) 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) { : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
LOG_DEBUG(F("Registering new Boiler with device ID 0x%02X"), device_id); LOG_DEBUG(F("Adding new Boiler with device ID 0x%02X"), device_id);
// the telegram handlers... // the telegram handlers...
register_telegram_type(0x10, F("UBAErrorMessage1"), false, std::bind(&Boiler::process_UBAErrorMessage, this, _1)); register_telegram_type(0x10, F("UBAErrorMessage1"), false, std::bind(&Boiler::process_UBAErrorMessage, this, _1));
@@ -68,15 +46,22 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_telegram_type(0xE3, F("UBAMonitorSlowPlus"), false, std::bind(&Boiler::process_UBAMonitorSlowPlus2, 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(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(0xE5, F("UBAMonitorSlowPlus"), false, std::bind(&Boiler::process_UBAMonitorSlowPlus, this, _1));
register_telegram_type(0xE9, F("UBADHWStatus"), false, std::bind(&Boiler::process_UBADHWStatus, this, _1)); register_telegram_type(0xE9, F("UBADHWStatus"), false, std::bind(&Boiler::process_UBADHWStatus, this, _1));
// MQTT callbacks // MQTT commands for boiler_cmd topic
register_mqtt_topic("boiler_cmd", std::bind(&Boiler::boiler_cmd, this, _1)); register_mqtt_cmd(F("comfort"), std::bind(&Boiler::set_warmwater_mode, this, _1, _2));
register_mqtt_topic("boiler_cmd_wwactivated", std::bind(&Boiler::boiler_cmd_wwactivated, this, _1)); register_mqtt_cmd(F("wwactivated"), std::bind(&Boiler::set_warmwater_activated, this, _1, _2));
register_mqtt_topic("boiler_cmd_wwonetime", std::bind(&Boiler::boiler_cmd_wwonetime, this, _1)); register_mqtt_cmd(F("wwtapactivated"), std::bind(&Boiler::set_tapwarmwater_activated, this, _1, _2));
register_mqtt_topic("boiler_cmd_wwcirculation", std::bind(&Boiler::boiler_cmd_wwcirculation, this, _1)); register_mqtt_cmd(F("wwonetime"), std::bind(&Boiler::set_warmwater_onetime, this, _1, _2));
register_mqtt_topic("boiler_cmd_wwtemp", std::bind(&Boiler::boiler_cmd_wwtemp, this, _1)); register_mqtt_cmd(F("wwcirculation"), std::bind(&Boiler::set_warmwater_circulation, this, _1, _2));
register_mqtt_cmd(F("flowtemp"), std::bind(&Boiler::set_flow_temp, this, _1, _2));
register_mqtt_cmd(F("wwtemp"), std::bind(&Boiler::set_warmwater_temp, this, _1, _2));
register_mqtt_cmd(F("burnmaxpower"), std::bind(&Boiler::set_max_power, this, _1, _2));
register_mqtt_cmd(F("burnminpower"), std::bind(&Boiler::set_min_power, this, _1, _2));
register_mqtt_cmd(F("boilhyston"), std::bind(&Boiler::set_hyst_on, this, _1, _2));
register_mqtt_cmd(F("boilhystoff"), std::bind(&Boiler::set_hyst_off, this, _1, _2));
register_mqtt_cmd(F("burnperiod"), std::bind(&Boiler::set_burn_period, this, _1, _2));
register_mqtt_cmd(F("pumpdelay"), std::bind(&Boiler::set_pump_delay, this, _1, _2));
} }
// add submenu context // add submenu context
@@ -86,156 +71,10 @@ void Boiler::add_context_menu() {
flash_string_vector{F_(boiler)}, flash_string_vector{F_(boiler)},
[&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { [&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
Boiler::console_commands(shell, ShellContext::BOILER); 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) { void Boiler::device_info(JsonArray & root) {
JsonObject dataElement; JsonObject dataElement;
@@ -295,16 +134,16 @@ void Boiler::publish_values() {
if (Helpers::hasValue(pumpMod2_)) { if (Helpers::hasValue(pumpMod2_)) {
doc["pumpMod2"] = 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); 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"; doc["wWCiPuType"] = wWCircPumpType_ ? "valve" : "pump";
} }
if (Helpers::hasValue(wWCircPumpMode_)) { if (Helpers::hasValue(wWCircPumpMode_)) {
doc["wWCiPuMode"] = 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); doc["wWCirc"] = Helpers::render_value(s, wWCirc_, EMS_VALUE_BOOL);
} }
if (Helpers::hasValue(extTemp_)) { if (Helpers::hasValue(extTemp_)) {
@@ -340,43 +179,43 @@ void Boiler::publish_values() {
if (Helpers::hasValue(exhaustTemp_)) { if (Helpers::hasValue(exhaustTemp_)) {
doc["exhaustTemp"] = (float)exhaustTemp_ / 10; 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); 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); 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); 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); 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); 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); 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); 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); doc["burnGas"] = Helpers::render_value(s, burnGas_, EMS_VALUE_BOOL);
} }
if (Helpers::hasValue(flameCurr_)) { if (Helpers::hasValue(flameCurr_)) {
doc["flameCurr"] = (float)(int16_t)flameCurr_ / 10; 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); 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); 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); 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); doc["wWHeat"] = Helpers::render_value(s, wWHeat_, EMS_VALUE_BOOL);
} }
if (Helpers::hasValue(heating_temp_)) { if (Helpers::hasValue(heating_temp_)) {
@@ -450,16 +289,16 @@ bool Boiler::updated_values() {
void Boiler::show_values(uuid::console::Shell & shell) { void Boiler::show_values(uuid::console::Shell & shell) {
EMSdevice::show_values(shell); // for showing the header 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")); 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("Central heating"), heating_active_ ? F("active") : F("off"));
} }
print_value(shell, 2, F("Warm Water activated"), wWActivated_, nullptr, EMS_VALUE_BOOL); 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 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); print_value(shell, 2, F("Warm Water circulation pump available"), wWCircPump_, nullptr, EMS_VALUE_BOOL);
@@ -557,6 +396,8 @@ void Boiler::show_values(uuid::console::Shell & shell) {
if (Helpers::hasValue(UBAuptime_)) { 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.printfln(F(" Total UBA working time: %d days %d hours %d minutes"), UBAuptime_ / 1440, (UBAuptime_ % 1440) / 60, UBAuptime_ % 60);
} }
shell.println();
} }
/* /*
@@ -579,7 +420,7 @@ void Boiler::check_active() {
// see if the heating or hot tap water has changed, if so send // 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 // 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_; uint8_t latest_boilerState = (tap_water_active_ << 1) + heating_active_;
if (latest_boilerState != last_boilerState) { if (latest_boilerState != last_boilerState) {
last_boilerState = latest_boilerState; last_boilerState = latest_boilerState;
@@ -650,12 +491,12 @@ void Boiler::process_UBATotalUptime(std::shared_ptr<const Telegram> telegram) {
*/ */
void Boiler::process_UBAParameters(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(heating_temp_, 1);
telegram->read_value(burnPowermax_,2); telegram->read_value(burnPowermax_, 2);
telegram->read_value(burnPowermin_,3); telegram->read_value(burnPowermin_, 3);
telegram->read_value(boilTemp_off_,4); telegram->read_value(boilTemp_off_, 4);
telegram->read_value(boilTemp_on_,5); telegram->read_value(boilTemp_on_, 5);
telegram->read_value(burnPeriod_,6); telegram->read_value(burnPeriod_, 6);
telegram->read_value(pumpDelay_,8); telegram->read_value(pumpDelay_, 8);
telegram->read_value(pump_mod_max_, 9); telegram->read_value(pump_mod_max_, 9);
telegram->read_value(pump_mod_min_, 10); telegram->read_value(pump_mod_min_, 10);
} }
@@ -804,7 +645,7 @@ void Boiler::process_UBAMaintenanceStatus(std::shared_ptr<const Telegram> telegr
// 0x10, 0x11, 0x12 // 0x10, 0x11, 0x12
// not yet implemented // not yet implemented
void Boiler::process_UBAErrorMessage(std::shared_ptr<const Telegram> telegram) { 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 #pragma GCC diagnostic pop
@@ -818,67 +659,113 @@ void Boiler::process_UBAMaintenanceData(std::shared_ptr<const Telegram> telegram
} }
} }
/*
* Commands
*/
// Set the warm water temperature 0x33 // Set the warm water temperature 0x33
void Boiler::set_warmwater_temp(const uint8_t temperature) { void Boiler::set_warmwater_temp(const char * value, const int8_t id) {
LOG_INFO(F("Setting boiler warm water temperature to %d C"), temperature); uint8_t v = 0;
write_command(EMS_TYPE_UBAParameterWW, 2, temperature); if (!Helpers::value2number(value, v)) {
write_command(EMS_TYPE_UBAFlags, 3, temperature); // for i9000, see #397 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 // flow temp
void Boiler::set_flow_temp(const uint8_t temperature) { void Boiler::set_flow_temp(const char * value, const int8_t id) {
LOG_INFO(F("Setting boiler flow temperature to %d C"), temperature); uint8_t v = 0;
write_command(EMS_TYPE_UBASetPoints, 0, temperature); 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 // set min boiler output
void Boiler::set_min_power(const uint8_t power) { void Boiler::set_min_power(const char * value, const int8_t id) {
LOG_INFO(F("Setting boiler min power to "), power); uint8_t v = 0;
write_command(EMS_TYPE_UBAParameters, 3, power); 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 // set max temp
void Boiler::set_max_power(const uint8_t power) { void Boiler::set_max_power(const char * value, const int8_t id) {
LOG_INFO(F("Setting boiler max power to %d C"), power); uint8_t v = 0;
write_command(EMS_TYPE_UBAParameters, 2, power); 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 // set oiler on hysteresis
void Boiler::set_hyst_on(const uint8_t temp) { void Boiler::set_hyst_on(const char * value, const int8_t id) {
LOG_INFO(F("Setting boiler hysteresis on to %d C"), temp); uint8_t v = 0;
write_command(EMS_TYPE_UBAParameters, 5, temp); 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 // set boiler off hysteresis
void Boiler::set_hyst_off(const uint8_t temp) { void Boiler::set_hyst_off(const char * value, const int8_t id) {
LOG_INFO(F("Setting boiler hysteresis off to %d C"), temp); uint8_t v = 0;
write_command(EMS_TYPE_UBAParameters, 4, temp); 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 // set min burner period
void Boiler::set_burn_period(const uint8_t t) { void Boiler::set_burn_period(const char * value, const int8_t id) {
LOG_INFO(F("Setting burner min. period to %d min"), t); uint8_t v = 0;
write_command(EMS_TYPE_UBAParameters, 6, t); 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 // set pump delay
void Boiler::set_pump_delay(const uint8_t t) { void Boiler::set_pump_delay(const char * value, const int8_t id) {
LOG_INFO(F("Setting boiler pump delay to %d min"), t); uint8_t v = 0;
write_command(EMS_TYPE_UBAParameters, 8, t); 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 // 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) // 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 uint8_t comfort) { void Boiler::set_warmwater_mode(const char * value, const int8_t id) {
if (value == nullptr) {
return;
}
uint8_t set; uint8_t set;
if (comfort == 1) { if (strcmp(value, "hot") == 0) {
LOG_INFO(F("Setting boiler warm water to Hot")); LOG_INFO(F("Setting boiler warm water to Hot"));
set = 0x00; set = 0x00;
} else if (comfort == 2) { } else if (strcmp(value, "eco") == 0) {
LOG_INFO(F("Setting boiler warm water to Eco")); LOG_INFO(F("Setting boiler warm water to Eco"));
set = 0xD8; set = 0xD8;
} else if (comfort == 3) { } else if (strcmp(value, "intelligent") == 0) {
LOG_INFO(F("Setting boiler warm water to Intelligent")); LOG_INFO(F("Setting boiler warm water to Intelligent"));
set = 0xEC; set = 0xEC;
} else { } else {
@@ -888,24 +775,33 @@ void Boiler::set_warmwater_mode(const uint8_t comfort) {
} }
// turn on/off warm water // turn on/off warm water
void Boiler::set_warmwater_activated(const bool activated) { void Boiler::set_warmwater_activated(const char * value, const int8_t id) {
LOG_INFO(F("Setting boiler warm water %s"), activated ? "on" : "off"); bool v = false;
uint8_t value; 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 // https://github.com/proddy/EMS-ESP/issues/268
uint8_t n;
if (EMSbus::is_ht3()) { 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 { } 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 // 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' // 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) { void Boiler::set_tapwarmwater_activated(const char * value, const int8_t id) {
LOG_INFO(F("Setting boiler warm tap water %s"), activated ? "on" : "off"); 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]; uint8_t message_data[EMS_MAX_TELEGRAM_MESSAGE_LENGTH];
for (uint8_t i = 0; i < sizeof(message_data); i++) { for (uint8_t i = 0; i < sizeof(message_data); i++) {
message_data[i] = 0x00; message_data[i] = 0x00;
@@ -914,7 +810,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 // 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 // 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 // when in test mode we're able to mess around with the 3-way valve settings
if (!activated) { if (!v) {
// on // on
message_data[0] = 0x5A; // test mode on message_data[0] = 0x5A; // test mode on
message_data[1] = 0x00; // burner output 0% message_data[1] = 0x00; // burner output 0%
@@ -932,16 +828,26 @@ void Boiler::set_tapwarmwater_activated(const bool activated) {
// Activate / De-activate One Time warm water 0x35 // Activate / De-activate One Time warm water 0x35
// true = on, false = off // true = on, false = off
// See also https://github.com/proddy/EMS-ESP/issues/341#issuecomment-596245458 for Junkers // See also https://github.com/proddy/EMS-ESP/issues/341#issuecomment-596245458 for Junkers
void Boiler::set_warmwater_onetime(const bool activated) { void Boiler::set_warmwater_onetime(const char * value, const int8_t id) {
LOG_INFO(F("Setting boiler warm water OneTime loading %s"), activated ? "on" : "off"); bool v = false;
write_command(EMS_TYPE_UBAFlags, 0, (activated ? 0x22 : 0x02)); 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 // Activate / De-activate circulation of warm water 0x35
// true = on, false = off // true = on, false = off
void Boiler::set_warmwater_circulation(const bool activated) { void Boiler::set_warmwater_circulation(const char * value, const int8_t id) {
LOG_INFO(F("Setting boiler warm water circulation %s"), activated ? "on" : "off"); bool v = false;
write_command(EMS_TYPE_UBAFlags, 1, (activated ? 0x22 : 0x02)); 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 // add console commands
@@ -955,115 +861,6 @@ void Boiler::console_commands(Shell & shell, unsigned int context) {
EMSESP::send_read_request(type_id, device_id()); EMSESP::send_read_request(type_id, 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, EMSESPShell::commands->add_command(ShellContext::BOILER,
CommandFlags::USER, CommandFlags::USER,
flash_string_vector{F_(show)}, flash_string_vector{F_(show)},

View File

@@ -124,7 +124,7 @@ class Boiler : public EMSdevice {
uint8_t burnPowermin_ = EMS_VALUE_UINT_NOTSET; uint8_t burnPowermin_ = EMS_VALUE_UINT_NOTSET;
uint8_t burnPowermax_ = EMS_VALUE_UINT_NOTSET; uint8_t burnPowermax_ = EMS_VALUE_UINT_NOTSET;
int8_t boilTemp_off_ = EMS_VALUE_INT_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 burnPeriod_ = EMS_VALUE_UINT_NOTSET;
uint8_t pumpDelay_ = EMS_VALUE_UINT_NOTSET; uint8_t pumpDelay_ = EMS_VALUE_UINT_NOTSET;
@@ -160,27 +160,20 @@ class Boiler : public EMSdevice {
void check_active(); void check_active();
void set_warmwater_temp(const uint8_t temperature); // commands
void set_flow_temp(const uint8_t temperature); void set_warmwater_mode(const char * value, const int8_t id);
void set_warmwater_mode(const uint8_t comfort); void set_warmwater_activated(const char * value, const int8_t id);
void set_warmwater_activated(const bool activated); void set_tapwarmwater_activated(const char * value, const int8_t id);
void set_tapwarmwater_activated(const bool activated); void set_warmwater_onetime(const char * value, const int8_t id);
void set_warmwater_onetime(const bool activated); void set_warmwater_circulation(const char * value, const int8_t id);
void set_warmwater_circulation(const bool activated); void set_warmwater_temp(const char * value, const int8_t id);
void set_min_power(const uint8_t power); void set_flow_temp(const char * value, const int8_t id);
void set_max_power(const uint8_t power); void set_min_power(const char * value, const int8_t id);
void set_hyst_on(const uint8_t temp); void set_max_power(const char * value, const int8_t id);
void set_hyst_off(const uint8_t temp); void set_hyst_on(const char * value, const int8_t id);
void set_burn_period(const uint8_t t); void set_hyst_off(const char * value, const int8_t id);
void set_pump_delay(const uint8_t t); void set_burn_period(const char * value, const int8_t id);
void set_pump_delay(const char * value, const int8_t id);
// 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);
}; };
} // namespace emsesp } // namespace emsesp

View File

@@ -22,16 +22,10 @@ namespace emsesp {
REGISTER_FACTORY(Connect, EMSdevice::DeviceType::CONNECT); REGISTER_FACTORY(Connect, EMSdevice::DeviceType::CONNECT);
MAKE_PSTR(logger_name, "connect") uuid::log::Logger Connect::logger_{F_(connect), uuid::log::Facility::CONSOLE};
uuid::log::Logger Connect::logger_{F_(logger_name), 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) 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) { : 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) { void Connect::device_info(JsonArray & root) {
@@ -42,7 +36,7 @@ void Connect::add_context_menu() {
// display all values into the shell console // display all values into the shell console
void Connect::show_values(uuid::console::Shell & shell) { 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 // publish values via MQTT

View File

@@ -18,22 +18,14 @@
#include "controller.h" #include "controller.h"
// MAKE_PSTR_WORD(controller)
namespace emsesp { namespace emsesp {
REGISTER_FACTORY(Controller, EMSdevice::DeviceType::CONTROLLER); REGISTER_FACTORY(Controller, EMSdevice::DeviceType::CONTROLLER);
MAKE_PSTR(logger_name, "controller") uuid::log::Logger Controller::logger_{F_(controller), uuid::log::Facility::CONSOLE};
uuid::log::Logger Controller::logger_{F_(logger_name), 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) 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) { : 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() { void Controller::add_context_menu() {
@@ -44,7 +36,7 @@ void Controller::device_info(JsonArray & root) {
// display all values into the shell console // display all values into the shell console
void Controller::show_values(uuid::console::Shell & shell) { 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 // publish values via MQTT

View File

@@ -18,22 +18,14 @@
#include "gateway.h" #include "gateway.h"
// MAKE_PSTR_WORD(gateway)
namespace emsesp { namespace emsesp {
REGISTER_FACTORY(Gateway, EMSdevice::DeviceType::GATEWAY); REGISTER_FACTORY(Gateway, EMSdevice::DeviceType::GATEWAY);
MAKE_PSTR(logger_name, "gateway") uuid::log::Logger Gateway::logger_{F_(gateway), uuid::log::Facility::CONSOLE};
uuid::log::Logger Gateway::logger_{F_(logger_name), 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) 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) { : 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() { void Gateway::add_context_menu() {

View File

@@ -18,32 +18,19 @@
#include "heatpump.h" #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 { namespace emsesp {
REGISTER_FACTORY(Heatpump, EMSdevice::DeviceType::HEATPUMP); REGISTER_FACTORY(Heatpump, EMSdevice::DeviceType::HEATPUMP);
MAKE_PSTR(logger_name, "heatpump") uuid::log::Logger Heatpump::logger_{F_(heatpump), uuid::log::Facility::CONSOLE};
uuid::log::Logger Heatpump::logger_{F_(logger_name), 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) 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) { : 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 // telegram handlers
register_telegram_type(0x047B, F("HP1"), true, std::bind(&Heatpump::process_HPMonitor1, this, _1)); 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)); 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));
} }
// context submenu // context submenu
@@ -55,7 +42,7 @@ void Heatpump::device_info(JsonArray & root) {
// display all values into the shell console // display all values into the shell console
void Heatpump::show_values(uuid::console::Shell & shell) { 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 // publish values via MQTT

View File

@@ -22,12 +22,11 @@ namespace emsesp {
REGISTER_FACTORY(Mixing, EMSdevice::DeviceType::MIXING); REGISTER_FACTORY(Mixing, EMSdevice::DeviceType::MIXING);
MAKE_PSTR(logger_name, "mixing") uuid::log::Logger Mixing::logger_{F_(mixing), uuid::log::Facility::CONSOLE};
uuid::log::Logger Mixing::logger_{F_(logger_name), 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) 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) { : 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 (flags == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) {
if (device_id <= 0x27) { if (device_id <= 0x27) {
@@ -48,9 +47,6 @@ Mixing::Mixing(uint8_t device_type, uint8_t device_id, uint8_t product_id, const
if (flags == EMSdevice::EMS_DEVICE_FLAG_IPM) { if (flags == EMSdevice::EMS_DEVICE_FLAG_IPM) {
register_telegram_type(0x010C, F("IPMSetMessage"), false, std::bind(&Mixing::process_IPMStatusMessage, this, _1)); register_telegram_type(0x010C, F("IPMSetMessage"), false, std::bind(&Mixing::process_IPMStatusMessage, this, _1));
} }
// MQTT callbacks
// register_mqtt_topic("topic", std::bind(&Mixing::cmd, this, _1));
} }
// add context submenu // add context submenu
@@ -72,7 +68,6 @@ void Mixing::device_info(JsonArray & root) {
render_value_json(root, "", F("Setpoint flow temperature"), flowSetTemp_, F_(degrees)); 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 pump modulation"), pumpMod_, F_(percent));
render_value_json(root, "", F("Current valve status"), status_, nullptr); render_value_json(root, "", F("Current valve status"), status_, nullptr);
} }
// check to see if values have been updated // check to see if values have been updated
@@ -101,6 +96,8 @@ void Mixing::show_values(uuid::console::Shell & shell) {
print_value(shell, 4, F("Setpoint flow temperature"), flowSetTemp_, F_(degrees)); 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 pump modulation"), pumpMod_, F_(percent));
print_value(shell, 4, F("Current valve status"), status_, nullptr); print_value(shell, 4, F("Current valve status"), status_, nullptr);
shell.println();
} }
// publish values via MQTT // publish values via MQTT

View File

@@ -18,19 +18,15 @@
#include "solar.h" #include "solar.h"
MAKE_PSTR(kwh, "kWh")
MAKE_PSTR(wh, "Wh")
namespace emsesp { namespace emsesp {
REGISTER_FACTORY(Solar, EMSdevice::DeviceType::SOLAR); REGISTER_FACTORY(Solar, EMSdevice::DeviceType::SOLAR);
MAKE_PSTR(logger_name, "solar") uuid::log::Logger Solar::logger_{F_(solar), uuid::log::Facility::CONSOLE};
uuid::log::Logger Solar::logger_{F_(logger_name), 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) 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) { : 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 // telegram handlers
if (flags == EMSdevice::EMS_DEVICE_FLAG_SM10) { if (flags == EMSdevice::EMS_DEVICE_FLAG_SM10) {
@@ -49,9 +45,6 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
register_telegram_type(0x0103, F("ISM1StatusMessage"), true, std::bind(&Solar::process_ISM1StatusMessage, this, _1)); 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(0x0101, F("ISM1Set"), false, std::bind(&Solar::process_ISM1Set, this, _1));
} }
// MQTT callbacks
// register_mqtt_topic("topic", std::bind(&Solar::cmd, this, _1));
} }
// context submenu // context submenu
@@ -105,6 +98,8 @@ void Solar::show_values(uuid::console::Shell & shell) {
print_value(shell, 2, F("Energy last hour"), energyLastHour_, F_(wh), 10); print_value(shell, 2, F("Energy last hour"), energyLastHour_, F_(wh), 10);
print_value(shell, 2, F("Energy today"), energyToday_, F_(wh)); print_value(shell, 2, F("Energy today"), energyToday_, F_(wh));
print_value(shell, 2, F("Energy total"), energyTotal_, F_(kwh), 10); print_value(shell, 2, F("Energy total"), energyTotal_, F_(kwh), 10);
shell.println();
} }
// publish values via MQTT // publish values via MQTT
@@ -129,11 +124,11 @@ void Solar::publish_values() {
doc["pumpmodulation"] = pumpModulation_; doc["pumpmodulation"] = pumpModulation_;
} }
if (Helpers::hasValue(pump_, VALUE_BOOL)) { if (Helpers::hasValue(pump_, EMS_VALUE_BOOL)) {
doc["pump"] = Helpers::render_value(s, pump_, EMS_VALUE_BOOL); doc["pump"] = Helpers::render_value(s, pump_, 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); doc["valvestatus"] = Helpers::render_value(s, valveStatus_, EMS_VALUE_BOOL);
} }
@@ -141,11 +136,11 @@ void Solar::publish_values() {
doc["pumpWorkMin"] = (float)pumpWorkMin_; 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); doc["tankHeated"] = Helpers::render_value(s, tankHeated_, EMS_VALUE_BOOL);
} }
if (Helpers::hasValue(collectorOnOff_, VALUE_BOOL)) { if (Helpers::hasValue(collectorOnOff_, EMS_VALUE_BOOL)) {
doc["collectorOnOff"] = Helpers::render_value(s, collectorOnOff_, EMS_VALUE_BOOL); doc["collectorOnOff"] = Helpers::render_value(s, collectorOnOff_, EMS_VALUE_BOOL);
} }

View File

@@ -18,22 +18,14 @@
#include "switch.h" #include "switch.h"
// MAKE_PSTR_WORD(switch)
namespace emsesp { namespace emsesp {
REGISTER_FACTORY(Switch, EMSdevice::DeviceType::SWITCH); REGISTER_FACTORY(Switch, EMSdevice::DeviceType::SWITCH);
MAKE_PSTR(logger_name, "switch") uuid::log::Logger Switch::logger_{F_(switch), uuid::log::Facility::CONSOLE};
uuid::log::Logger Switch::logger_{F_(logger_name), 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) 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) { : 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() { void Switch::add_context_menu() {
@@ -44,7 +36,7 @@ void Switch::device_info(JsonArray & root) {
// display all values into the shell console // display all values into the shell console
void Switch::show_values(uuid::console::Shell & shell) { 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 // publish values via MQTT

View File

@@ -18,22 +18,11 @@
#include "thermostat.h" #include "thermostat.h"
MAKE_PSTR_WORD(thermostat)
MAKE_PSTR_WORD(master)
MAKE_PSTR_WORD(temp)
MAKE_PSTR_WORD(mode)
MAKE_PSTR_WORD(wwmode)
MAKE_PSTR(hc_optional, "[heating circuit]")
MAKE_PSTR(mode_mandatory, "<mode>")
MAKE_PSTR(mode_optional, "[mode]")
MAKE_PSTR(master_thermostat_fmt, "Master Thermostat device ID = %s")
namespace emsesp { namespace emsesp {
REGISTER_FACTORY(Thermostat, EMSdevice::DeviceType::THERMOSTAT); REGISTER_FACTORY(Thermostat, EMSdevice::DeviceType::THERMOSTAT);
MAKE_PSTR(logger_name, "thermostat")
uuid::log::Logger Thermostat::logger_{F_(logger_name), uuid::log::Facility::CONSOLE}; uuid::log::Logger Thermostat::logger_{F_(thermostat), uuid::log::Facility::CONSOLE};
Thermostat::Thermostat(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) Thermostat::Thermostat(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) { : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
@@ -138,18 +127,17 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
uint8_t actual_master_thermostat = EMSESP::actual_master_thermostat(); // what we're actually using uint8_t actual_master_thermostat = EMSESP::actual_master_thermostat(); // what we're actually using
uint8_t num_devices = EMSESP::count_devices(EMSdevice::DeviceType::THERMOSTAT) + 1; // including this thermostat uint8_t num_devices = EMSESP::count_devices(EMSdevice::DeviceType::THERMOSTAT) + 1; // including this thermostat
// if we're on auto mode, register this thermostat if it has a device id of 0x10 or 0x17 // if we're on auto mode, register this thermostat if it has a device id of 0x10, 0x17 or 0x18
// or if its the master thermostat we defined // or if its the master thermostat we defined
// see https://github.com/proddy/EMS-ESP/issues/362#issuecomment-629628161 // see https://github.com/proddy/EMS-ESP/issues/362#issuecomment-629628161
if (((num_devices == 1) && (actual_master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT)) || (master_thermostat == device_id)) { if (((num_devices == 1) && (actual_master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT)) || (master_thermostat == device_id)) {
EMSESP::actual_master_thermostat(device_id); EMSESP::actual_master_thermostat(device_id);
LOG_DEBUG(F("Registering new thermostat with device ID 0x%02X (as master)"), device_id); LOG_DEBUG(F("Adding new thermostat with device ID 0x%02X (as master)"), device_id);
init_mqtt(); init_mqtt();
} else { } else {
LOG_DEBUG(F("Registering new thermostat with device ID 0x%02X"), device_id); LOG_DEBUG(F("Adding new thermostat with device ID 0x%02X"), device_id);
} }
// for the thermostat, go a query all the heating circuits. This is only done once. The automatic fetch will from now on // for the thermostat, go a query all the heating circuits. This is only done once. The automatic fetch will from now on
// only update the active heating circuits // only update the active heating circuits
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
@@ -160,14 +148,6 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
} }
} }
// for the master thermostat initialize the MQTT subscribes
// these will be prefixed with hostname
void Thermostat::init_mqtt() {
register_mqtt_topic("thermostat_cmd", std::bind(&Thermostat::thermostat_cmd, this, _1)); // generic commands
register_mqtt_topic("thermostat_cmd_temp", std::bind(&Thermostat::thermostat_cmd_temp, this, _1));
register_mqtt_topic("thermostat_cmd_mode", std::bind(&Thermostat::thermostat_cmd_mode, this, _1));
}
// prepare data for Web UI // prepare data for Web UI
void Thermostat::device_info(JsonArray & root) { void Thermostat::device_info(JsonArray & root) {
uint8_t flags = (this->flags() & 0x0F); // specific thermostat characteristics, strip the option bits uint8_t flags = (this->flags() & 0x0F); // specific thermostat characteristics, strip the option bits
@@ -205,14 +185,18 @@ void Thermostat::device_info(JsonArray & root) {
dataElement = root.createNestedObject(); dataElement = root.createNestedObject();
std::string mode_str(15, '\0'); std::string mode_str(15, '\0');
snprintf_P(&mode_str[0], mode_str.capacity() + 1, PSTR("%sMode"), hc_str.c_str()); snprintf_P(&mode_str[0], mode_str.capacity() + 1, PSTR("%sMode"), hc_str.c_str());
dataElement["name"] = mode_str; dataElement["name"] = mode_str;
std::string modetype_str(20, '\0'); std::string modetype_str(20, '\0');
if (Helpers::hasValue(hc->summer_mode) && hc->summer_mode) { if (Helpers::hasValue(hc->summer_mode) && hc->summer_mode) {
snprintf_P(&modetype_str[0], modetype_str.capacity() + 1, PSTR("%s - summer"), mode_tostring(hc->get_mode(flags)).c_str()); snprintf_P(&modetype_str[0], modetype_str.capacity() + 1, PSTR("%s - summer"), mode_tostring(hc->get_mode(flags)).c_str());
} else if (Helpers::hasValue(hc->holiday_mode) && hc->holiday_mode) { } else if (Helpers::hasValue(hc->holiday_mode) && hc->holiday_mode) {
snprintf_P(&modetype_str[0], modetype_str.capacity() + 1, PSTR("%s - holiday"), mode_tostring(hc->get_mode(flags)).c_str()); snprintf_P(&modetype_str[0], modetype_str.capacity() + 1, PSTR("%s - holiday"), mode_tostring(hc->get_mode(flags)).c_str());
} else if (Helpers::hasValue(hc->mode_type)) { } else if (Helpers::hasValue(hc->mode_type)) {
snprintf_P(&modetype_str[0], modetype_str.capacity() + 1, PSTR("%s - %s"), mode_tostring(hc->get_mode(flags)).c_str(), mode_tostring(hc->get_mode_type(flags)).c_str()); snprintf_P(&modetype_str[0],
modetype_str.capacity() + 1,
PSTR("%s - %s"),
mode_tostring(hc->get_mode(flags)).c_str(),
mode_tostring(hc->get_mode_type(flags)).c_str());
} else { } else {
snprintf_P(&modetype_str[0], modetype_str.capacity() + 1, mode_tostring(hc->get_mode(flags)).c_str()); snprintf_P(&modetype_str[0], modetype_str.capacity() + 1, mode_tostring(hc->get_mode(flags)).c_str());
} }
@@ -221,8 +205,9 @@ void Thermostat::device_info(JsonArray & root) {
} }
} }
// only add the menu for the master thermostat // context menu "thermostat"
void Thermostat::add_context_menu() { void Thermostat::add_context_menu() {
// only add it once, to prevent conflicts when there are multiple thermostats
if (device_id() != EMSESP::actual_master_thermostat()) { if (device_id() != EMSESP::actual_master_thermostat()) {
return; return;
} }
@@ -232,318 +217,10 @@ void Thermostat::add_context_menu() {
flash_string_vector{F_(thermostat)}, flash_string_vector{F_(thermostat)},
[&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { [&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
Thermostat::console_commands(shell, ShellContext::THERMOSTAT); Thermostat::console_commands(shell, ShellContext::THERMOSTAT);
add_context_commands(ShellContext::THERMOSTAT);
}); });
} }
// general MQTT command for controlling thermostat
// e.g. { "hc": 1, "cmd":"daytemp", "data": 20 }
// or { "hc": 1, "daytemp": 20 } or { "hc2": { "daytemp":20 }}
void Thermostat::thermostat_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;
}
// check for nested commands like {"hc2":{"temp":21}}
for (const auto & hc : heating_circuits_) {
char hc_name[6], s[3]; // hc{1-4}
strlcpy(hc_name, "hc", 6);
uint8_t hc_num = hc->hc_num();
strlcat(hc_name, Helpers::itoa(s, hc_num), 6);
if (nullptr != doc[hc_name]["mode"]) {
std::string mode = doc[hc_name]["mode"];
set_mode(mode, hc_num);
}
if (float f = doc[hc_name]["temp"]) {
set_temperature(f, HeatingCircuit::Mode::AUTO, hc_num);
}
if (float f = doc[hc_name]["nighttemp"]) {
set_temperature(f, HeatingCircuit::Mode::NIGHT, hc_num);
}
if (float f = doc[hc_name]["daytemp"]) {
set_temperature(f, HeatingCircuit::Mode::DAY, hc_num);
}
if (float f = doc[hc_name]["nofrosttemp"]) {
set_temperature(f, HeatingCircuit::Mode::NOFROST, hc_num);
}
if (float f = doc[hc_name]["ecotemp"]) {
set_temperature(f, HeatingCircuit::Mode::ECO, hc_num);
}
if (float f = doc[hc_name]["heattemp"]) {
set_temperature(f, HeatingCircuit::Mode::HEAT, hc_num);
}
if (float f = doc[hc_name]["summertemp"]) {
set_temperature(f, HeatingCircuit::Mode::SUMMER, hc_num);
}
if (float f = doc[hc_name]["designtemp"]) {
set_temperature(f, HeatingCircuit::Mode::DESIGN, hc_num);
}
if (float f = doc[hc_name]["offsettemp"]) {
set_temperature(f, HeatingCircuit::Mode::OFFSET, hc_num);
}
if (float f = doc[hc_name]["holidaytemp"]) { //
set_temperature(f, HeatingCircuit::Mode::HOLIDAY, hc_num);
}
if (float f = doc[hc_name]["remotetemp"]) {
if (f > 100 || f < 0) {
Roomctrl::set_remotetemp(hc_num - 1, EMS_VALUE_SHORT_NOTSET);
} else {
Roomctrl::set_remotetemp(hc_num - 1, (int16_t)(f * 10));
}
}
if (nullptr != doc[hc_name]["control"]) {
uint8_t ctrl = doc[hc_name]["control"];
set_control(ctrl, hc_num);
}
if (nullptr != doc[hc_name]["pause"]) {
uint8_t p = doc[hc_name]["pause"];
set_pause(p, hc_num);
}
if (nullptr != doc[hc_name]["party"]) {
uint8_t p = doc[hc_name]["party"];
set_party(p, hc_num);
}
if (nullptr != doc[hc_name]["holiday"]) {
std::string holiday = doc[hc_name]["holiday"];
set_holiday(holiday.c_str(), hc_num);
}
}
// commands without heatingcircuit
if (nullptr != doc["wwmode"]) {
std::string mode = doc["wwmode"];
set_ww_mode(mode);
}
if (float ct = doc["calinttemp"]) {
set_settings_calinttemp((int8_t)(ct * 10));
}
if (nullptr != doc["minexttemp"]) {
int8_t mt = doc["minexttemp"];
set_settings_minexttemp(mt);
}
if (nullptr != doc["building"]) {
std::string bds = doc["building"];
uint8_t bd = doc["building"];
if (strcmp(bds.c_str(), "light") == 0) {
bd = 0;
} else if (strcmp(bds.c_str(), "medium") == 0) {
bd = 1;
} else if (strcmp(bds.c_str(), "heavy") == 0) {
bd = 2;
}
set_settings_building(bd);
}
if (nullptr != doc["language"]) {
uint8_t lg = doc["language"];
set_settings_language(lg);
}
if (nullptr != doc["display"]) {
uint8_t dp = doc["display"];
set_settings_display(dp);
}
if (nullptr != doc["clockoffset"]) {
int8_t co = doc["clockoffset"];
set_settings_clockoffset(co);
}
// get heating circuit if it exists
uint8_t hc_num = doc["hc"] | AUTO_HEATING_CIRCUIT;
// check for unnested commands like {"temp":21} or {"hc":2,"temp":21,"mode":"auto"}
if (nullptr != doc["mode"]) {
std::string mode = doc["mode"];
set_mode(mode, hc_num);
}
if (float f = doc["temp"]) {
set_temperature(f, HeatingCircuit::Mode::AUTO, hc_num);
}
if (float f = doc["nighttemp"]) {
set_temperature(f, HeatingCircuit::Mode::NIGHT, hc_num);
}
if (float f = doc["daytemp"]) {
set_temperature(f, HeatingCircuit::Mode::DAY, hc_num);
}
if (float f = doc["nofrosttemp"]) {
set_temperature(f, HeatingCircuit::Mode::NOFROST, hc_num);
}
if (float f = doc["ecotemp"]) {
set_temperature(f, HeatingCircuit::Mode::ECO, hc_num);
}
if (float f = doc["heattemp"]) {
set_temperature(f, HeatingCircuit::Mode::HEAT, hc_num);
}
if (float f = doc["summertemp"]) {
set_temperature(f, HeatingCircuit::Mode::SUMMER, hc_num);
}
if (float f = doc["designtemp"]) {
set_temperature(f, HeatingCircuit::Mode::DESIGN, hc_num);
}
if (float f = doc["offsettemp"]) {
set_temperature(f, HeatingCircuit::Mode::OFFSET, hc_num);
}
if (float f = doc["holidaytemp"]) { //
set_temperature(f, HeatingCircuit::Mode::HOLIDAY, hc_num);
}
if (float f = doc["remotetemp"]) {
if (f > 100 || f < 0) {
Roomctrl::set_remotetemp(hc_num - 1, EMS_VALUE_SHORT_NOTSET);
} else {
Roomctrl::set_remotetemp(hc_num - 1, (int16_t)(f * 10));
}
}
if (nullptr != doc["control"]) {
uint8_t ctrl = doc["control"];
set_control(ctrl, hc_num);
}
if (nullptr != doc["pause"]) {
uint8_t p = doc["pause"];
set_pause(p, hc_num);
}
if (nullptr != doc["party"]) {
uint8_t p = doc["party"];
set_party(p, hc_num);
}
if (nullptr != doc["holiday"]) {
std::string holiday = doc["holiday"];
set_holiday(holiday.c_str(), hc_num);
}
if (nullptr != doc["date"]) {
std::string date = doc["date"];
set_datetime(date.c_str());
}
// check for commands like {"hc":2,"cmd":"temp","data":21}
const char * command = doc["cmd"];
if (command == nullptr || doc["data"] == nullptr) {
return;
}
// ok, we have command and data
if (strcmp(command, "temp") == 0) {
float f = doc["data"];
if (f) {
set_temperature(f, HeatingCircuit::Mode::AUTO, hc_num);
}
return;
}
if (strcmp(command, "mode") == 0) {
std::string mode = doc["data"];
if (mode.empty()) {
return;
}
set_mode(mode, hc_num);
return;
}
if (strcmp(command, "nighttemp") == 0) {
float f = doc["data"];
if (f) {
set_temperature(f, HeatingCircuit::Mode::NIGHT, hc_num);
}
return;
}
if (strcmp(command, "daytemp") == 0) {
float f = doc["data"];
if (f) {
set_temperature(f, HeatingCircuit::Mode::DAY, hc_num);
}
return;
}
if (strcmp(command, "holidaytemp") == 0) {
float f = doc["data"];
if (f) {
set_temperature(f, HeatingCircuit::Mode::HOLIDAY, hc_num);
}
return;
}
if (strcmp(command, "ecotemp") == 0) {
float f = doc["data"];
if (f) {
set_temperature(f, HeatingCircuit::Mode::ECO, hc_num);
}
return;
}
if (strcmp(command, "heattemp") == 0) {
float f = doc["data"];
if (f) {
set_temperature(f, HeatingCircuit::Mode::HEAT, hc_num);
}
return;
}
if (strcmp(command, "nofrosttemp") == 0) {
float f = doc["data"];
if (f) {
set_temperature(f, HeatingCircuit::Mode::NOFROST, hc_num);
}
return;
}
if (strcmp(command, "summertemp") == 0) {
float f = doc["data"];
if (f) {
set_temperature(f, HeatingCircuit::Mode::SUMMER, hc_num);
}
return;
}
if (strcmp(command, "designtemp") == 0) {
float f = doc["data"];
if (f) {
set_temperature(f, HeatingCircuit::Mode::DESIGN, hc_num);
}
return;
}
if (strcmp(command, "offsettemp") == 0) {
float f = doc["data"];
if (f) {
set_temperature(f, HeatingCircuit::Mode::OFFSET, hc_num);
}
return;
}
if (strcmp(command, "remotetemp") == 0) {
float f = doc["data"];
if (f > 100 || f < 0) {
Roomctrl::set_remotetemp(hc_num - 1, EMS_VALUE_SHORT_NOTSET);
} else {
Roomctrl::set_remotetemp(hc_num - 1, (int16_t)(f * 10));
}
return;
}
if (strcmp(command, "control") == 0) {
uint8_t ctrl = doc["data"];
set_control(ctrl, hc_num);
return;
}
if (strcmp(command, "pause") == 0) {
uint8_t p = doc["data"];
set_pause(p, hc_num);
return;
}
if (strcmp(command, "party") == 0) {
uint8_t p = doc["data"];
set_party(p, hc_num);
return;
}
if (strcmp(command, "holiday") == 0) {
std::string holiday = doc["data"];
set_holiday(holiday.c_str(), hc_num);
return;
}
if (strcmp(command, "date") == 0) {
std::string date = doc["data"];
set_datetime(date.c_str());
return;
}
}
void Thermostat::thermostat_cmd_temp(const char * message) {
float f = strtof((char *)message, 0);
set_temperature(f, HeatingCircuit::Mode::AUTO, AUTO_HEATING_CIRCUIT);
}
// message payload holds the text name of the mode e.g. "auto"
void Thermostat::thermostat_cmd_mode(const char * message) {
std::string s(message);
set_mode(s, AUTO_HEATING_CIRCUIT);
}
// this function is called post the telegram handler function has been executed // this function is called post the telegram handler function has been executed
// we check if any of the thermostat values have changed and then republish if necessary // we check if any of the thermostat values have changed and then republish if necessary
bool Thermostat::updated_values() { bool Thermostat::updated_values() {
@@ -761,12 +438,14 @@ void Thermostat::publish_values() {
// returns the heating circuit object based on the hc number // returns the heating circuit object based on the hc number
// of nullptr if it doesn't exist yet // of nullptr if it doesn't exist yet
std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(const uint8_t hc_num) { std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(const uint8_t hc_num) {
// if hc_num is 0 then return the first existing hc in the list
if (hc_num == 0) { if (hc_num == 0) {
// return first existing hc
for (const auto & heating_circuit : heating_circuits_) { for (const auto & heating_circuit : heating_circuits_) {
return heating_circuit; return heating_circuit;
} }
} }
// otherwise find a match
for (const auto & heating_circuit : heating_circuits_) { for (const auto & heating_circuit : heating_circuits_) {
if (heating_circuit->hc_num() == hc_num) { if (heating_circuit->hc_num() == hc_num) {
return heating_circuit; return heating_circuit;
@@ -1161,6 +840,8 @@ void Thermostat::show_values(uuid::console::Shell & shell) {
print_value(shell, 4, F("Target flow temperature"), hc->targetflowtemp, F_(degrees)); print_value(shell, 4, F("Target flow temperature"), hc->targetflowtemp, F_(degrees));
} }
} }
shell.println();
} }
// 0xA8 - for reading the mode from the RC20 thermostat (0x17) // 0xA8 - for reading the mode from the RC20 thermostat (0x17)
@@ -1360,8 +1041,7 @@ void Thermostat::process_RC35Monitor(std::shared_ptr<const Telegram> telegram) {
// type 0x3D (HC1), 0x47 (HC2), 0x51 (HC3), 0x5B (HC4) - Working Mode Heating - for reading the mode from the RC35 thermostat (0x10) // type 0x3D (HC1), 0x47 (HC2), 0x51 (HC3), 0x5B (HC4) - Working Mode Heating - for reading the mode from the RC35 thermostat (0x10)
void Thermostat::process_RC35Set(std::shared_ptr<const Telegram> telegram) { void Thermostat::process_RC35Set(std::shared_ptr<const Telegram> telegram) {
// check to see we have a valid type // check to see we have a valid type. heating: 1 radiator, 2 convectors, 3 floors, 4 room supply
// heating: 1 radiator, 2 convectors, 3 floors, 4 room supply
if (telegram->message_data[0] == 0x00) { if (telegram->message_data[0] == 0x00) {
return; return;
} }
@@ -1389,7 +1069,7 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
return; return;
} }
if (telegram->message_data[7] & 0x0C) { // date and time not valid if (telegram->message_data[7] & 0x0C) { // date and time not valid
set_datetime("NTP"); // set from NTP set_datetime("ntp", -1); // set from NTP
return; return;
} }
if (datetime_.empty()) { if (datetime_.empty()) {
@@ -1415,8 +1095,69 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
); );
} }
// add console commands
void Thermostat::console_commands(Shell & shell, unsigned int context) {
EMSESPShell::commands->add_command(ShellContext::THERMOSTAT,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(master)},
flash_string_vector{F_(deviceid_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) {
uint8_t value;
if (arguments.empty()) {
value = EMSESP_DEFAULT_MASTER_THERMOSTAT;
} else {
value = Helpers::hextoint(arguments.front().c_str());
}
EMSESP::emsespSettingsService.update(
[&](EMSESPSettings & settings) {
settings.master_thermostat = value;
EMSESP::actual_master_thermostat(value); // set the internal value too
char buffer[5];
shell.printfln(F_(master_thermostat_fmt),
!value ? uuid::read_flash_string(F_(auto)).c_str() : Helpers::hextoa(buffer, value));
return StateUpdateResult::CHANGED;
},
"local");
});
EMSESPShell::commands->add_command(ShellContext::THERMOSTAT,
CommandFlags::ADMIN,
flash_string_vector{F_(read)},
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());
});
EMSESPShell::commands->add_command(ShellContext::THERMOSTAT,
CommandFlags::USER,
flash_string_vector{F_(show)},
[&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { show_values(shell); });
EMSESPShell::commands->add_command(ShellContext::THERMOSTAT,
CommandFlags::USER,
flash_string_vector{F_(set)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
char buffer[4];
shell.printfln(F_(master_thermostat_fmt),
settings.master_thermostat == 0 ? uuid::read_flash_string(F_(auto)).c_str()
: Helpers::hextoa(buffer, settings.master_thermostat));
shell.println();
});
});
// enter the context
Console::enter_custom_context(shell, context);
}
// 0xA5 - Set minimum external temperature // 0xA5 - Set minimum external temperature
void Thermostat::set_settings_minexttemp(const int8_t mt) { void Thermostat::set_settings_minexttemp(const char * value, const int8_t id) {
int8_t mt = 0;
if (!Helpers::value2number(value, mt)) {
return;
}
if (((flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) || ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC35)) { if (((flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) || ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC35)) {
LOG_INFO(F("Setting min external temperature to %d"), mt); LOG_INFO(F("Setting min external temperature to %d"), mt);
write_command(EMS_TYPE_IBASettings, 5, mt); write_command(EMS_TYPE_IBASettings, 5, mt);
@@ -1424,14 +1165,25 @@ void Thermostat::set_settings_minexttemp(const int8_t mt) {
} }
// 0xA5 - Clock offset // 0xA5 - Clock offset
void Thermostat::set_settings_clockoffset(const int8_t co) { void Thermostat::set_settings_clockoffset(const char * value, const int8_t id) {
int8_t co = 0;
if (!Helpers::value2number(value, co)) {
return;
}
if ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) { if ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) {
LOG_INFO(F("Setting clock offset to %d"), co); LOG_INFO(F("Setting clock offset to %d"), co);
write_command(EMS_TYPE_IBASettings, 12, co); write_command(EMS_TYPE_IBASettings, 12, co);
} }
} }
// 0xA5 - Calibrate internal temperature // 0xA5 - Calibrate internal temperature
void Thermostat::set_settings_calinttemp(const int8_t ct) { void Thermostat::set_settings_calinttemp(const char * value, const int8_t id) {
int8_t ct = 0;
if (!Helpers::value2number(value, ct)) {
return;
}
// TODO: Michael - does this value need to be multiple by 10?
if (((flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) || ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC35)) { if (((flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) || ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC35)) {
LOG_INFO(F("Calibrating internal temperature to %d.%d"), ct / 10, ct < 0 ? -ct % 10 : ct % 10); LOG_INFO(F("Calibrating internal temperature to %d.%d"), ct / 10, ct < 0 ? -ct % 10 : ct % 10);
write_command(EMS_TYPE_IBASettings, 2, ct); write_command(EMS_TYPE_IBASettings, 2, ct);
@@ -1439,15 +1191,50 @@ void Thermostat::set_settings_calinttemp(const int8_t ct) {
} }
// 0xA5 - Set the display settings // 0xA5 - Set the display settings
void Thermostat::set_settings_display(const uint8_t ds) { void Thermostat::set_settings_display(const char * value, const int8_t id) {
uint8_t ds = 0;
if (!Helpers::value2number(value, ds)) {
return;
}
if ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) { if ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) {
LOG_INFO(F("Setting display to %d"), ds); LOG_INFO(F("Setting display to %d"), ds);
write_command(EMS_TYPE_IBASettings, 0, ds); write_command(EMS_TYPE_IBASettings, 0, ds);
} }
} }
void Thermostat::set_remotetemp(const char * value, const int8_t id) {
float f = 0;
if (!Helpers::value2float(value, f)) {
return;
}
uint8_t hc_num = (id == -1) ? DEFAULT_HEATING_CIRCUIT : id;
if (f > 100 || f < 0) {
Roomctrl::set_remotetemp(hc_num - 1, EMS_VALUE_SHORT_NOTSET);
} else {
Roomctrl::set_remotetemp(hc_num - 1, (int16_t)(f * 10));
}
}
// 0xA5 - Set the building settings // 0xA5 - Set the building settings
void Thermostat::set_settings_building(const uint8_t bg) { void Thermostat::set_settings_building(const char * value, const int8_t id) {
std::string bd;
if (!Helpers::value2string(value, bd)) {
return;
}
uint8_t bg = 0;
if (bd == "light") {
bg = 0;
} else if (bd == "medium") {
bg = 1;
} else if (bd == "heavy") {
bg = 2;
} else {
return; // invalid
}
if (((flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) || ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC35)) { if (((flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) || ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC35)) {
LOG_INFO(F("Setting building to %d"), bg); LOG_INFO(F("Setting building to %d"), bg);
write_command(EMS_TYPE_IBASettings, 6, bg); write_command(EMS_TYPE_IBASettings, 6, bg);
@@ -1455,7 +1242,11 @@ void Thermostat::set_settings_building(const uint8_t bg) {
} }
// 0xA5 Set the language settings // 0xA5 Set the language settings
void Thermostat::set_settings_language(const uint8_t lg) { void Thermostat::set_settings_language(const char * value, const int8_t id) {
uint8_t lg = 0;
if (!Helpers::value2number(value, lg)) {
return;
}
if ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) { if ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) {
LOG_INFO(F("Setting language to %d"), lg); LOG_INFO(F("Setting language to %d"), lg);
write_command(EMS_TYPE_IBASettings, 1, lg); write_command(EMS_TYPE_IBASettings, 1, lg);
@@ -1463,16 +1254,25 @@ void Thermostat::set_settings_language(const uint8_t lg) {
} }
// Set the control-mode for hc 0-off, 1-RC20, 2-RC3x // Set the control-mode for hc 0-off, 1-RC20, 2-RC3x
void Thermostat::set_control(const uint8_t ctrl, const uint8_t hc_num) { void Thermostat::set_control(const char * value, const int8_t id) {
uint8_t ctrl = 0;
if (!Helpers::value2number(value, ctrl)) {
return;
}
uint8_t hc_num = (id == -1) ? DEFAULT_HEATING_CIRCUIT : id;
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num);
if (hc == nullptr) { if (hc == nullptr) {
LOG_WARNING(F("Set control: Heating Circuit %d not found or activated"), hc_num); LOG_WARNING(F("Set control: Heating Circuit %d not found or activated"), hc_num);
return; return;
} }
if (ctrl > 2) { if (ctrl > 2) {
LOG_WARNING(F("Set control: Invalid control mode: %d"), ctrl); LOG_WARNING(F("Set control: Invalid control mode: %d"), ctrl);
return; return;
} }
if ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC35 || (flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) { if ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC35 || (flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) {
LOG_INFO(F("Setting circuit-control for hc%d to %d"), hc_num, ctrl); LOG_INFO(F("Setting circuit-control for hc%d to %d"), hc_num, ctrl);
write_command(set_typeids[hc->hc_num() - 1], 26, ctrl); write_command(set_typeids[hc->hc_num() - 1], 26, ctrl);
@@ -1482,23 +1282,37 @@ void Thermostat::set_control(const uint8_t ctrl, const uint8_t hc_num) {
} }
// sets the thermostat ww working mode, where mode is a string // sets the thermostat ww working mode, where mode is a string
void Thermostat::set_ww_mode(const std::string & mode) { void Thermostat::set_wwmode(const char * value, const int8_t id) {
if (strcasecmp("off", mode.c_str()) == 0) { std::string v;
LOG_INFO(F("Setting thermostat warm water mode to %s"), mode.c_str()); if (!Helpers::value2string(value, v)) {
write_command(EMS_TYPE_wwSettings, 2, 0); return;
} else if (strcasecmp("on", mode.c_str()) == 0) { }
LOG_INFO(F("Setting thermostat warm water mode to %s"), mode.c_str());
write_command(EMS_TYPE_wwSettings, 2, 1); uint8_t set = 0xFF; // some dummy value
} else if (strcasecmp("auto", mode.c_str()) == 0) { if (v == "off") {
LOG_INFO(F("Setting thermostat warm water mode to %s"), mode.c_str()); set = 0;
write_command(EMS_TYPE_wwSettings, 2, 2); } else if (v == "on") {
set = 1;
} else if (v == "auto") {
set = 2;
}
if (set != 0xFF) {
LOG_INFO(F("Setting thermostat warm water mode to %s"), v.c_str());
write_command(EMS_TYPE_wwSettings, 2, set);
} else { } else {
LOG_WARNING(F("Set thermostat warm water mode: Invalid mode: %s"), mode.c_str()); LOG_WARNING(F("Set thermostat warm water mode: Invalid mode: %s"), v.c_str());
} }
} }
// set the holiday as string dd.mm.yyyy-dd.mm.yyyy // set the holiday as string dd.mm.yyyy-dd.mm.yyyy
void Thermostat::set_holiday(const char * hd, const uint8_t hc_num) { void Thermostat::set_holiday(const char * value, const int8_t id) {
std::string hd;
if (!Helpers::value2string(value, hd)) {
return;
}
uint8_t hc_num = (id == -1) ? DEFAULT_HEATING_CIRCUIT : id;
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num);
if (hc == nullptr) { if (hc == nullptr) {
LOG_WARNING(F("Set holiday: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, device_id()); LOG_WARNING(F("Set holiday: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, device_id());
@@ -1520,7 +1334,13 @@ void Thermostat::set_holiday(const char * hd, const uint8_t hc_num) {
} }
// set pause in hours // set pause in hours
void Thermostat::set_pause(const uint8_t hrs, const uint8_t hc_num) { void Thermostat::set_pause(const char * value, const int8_t id) {
uint8_t hrs = 0;
if (!Helpers::value2number(value, hrs)) {
return;
}
uint8_t hc_num = (id == -1) ? DEFAULT_HEATING_CIRCUIT : id;
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num);
if (hc == nullptr) { if (hc == nullptr) {
LOG_WARNING(F("Set pause: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, device_id()); LOG_WARNING(F("Set pause: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, device_id());
@@ -1535,7 +1355,13 @@ void Thermostat::set_pause(const uint8_t hrs, const uint8_t hc_num) {
} }
// set partymode in hours // set partymode in hours
void Thermostat::set_party(const uint8_t hrs, const uint8_t hc_num) { void Thermostat::set_party(const char * value, const int8_t id) {
uint8_t hrs = 0;
if (!Helpers::value2number(value, hrs)) {
return;
}
uint8_t hc_num = (id == -1) ? DEFAULT_HEATING_CIRCUIT : id;
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num); std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num);
if (hc == nullptr) { if (hc == nullptr) {
LOG_WARNING(F("Set party: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, device_id()); LOG_WARNING(F("Set party: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, device_id());
@@ -1551,11 +1377,17 @@ void Thermostat::set_party(const uint8_t hrs, const uint8_t hc_num) {
// set date&time as string hh:mm:ss-dd.mm.yyyy-dw-dst or "NTP" for setting to internet-time // set date&time as string hh:mm:ss-dd.mm.yyyy-dw-dst or "NTP" for setting to internet-time
// dw - day of week (0..6), dst- summertime (0/1) // dw - day of week (0..6), dst- summertime (0/1)
void Thermostat::set_datetime(const char * dt) { // id is ignored
void Thermostat::set_datetime(const char * value, const int8_t id) {
std::string dt;
if (!Helpers::value2string(value, dt)) {
return;
}
uint8_t data[9]; uint8_t data[9];
if (strcmp(dt,"NTP") == 0) { if (dt == "ntp") {
time_t now = time(nullptr); time_t now = time(nullptr);
tm * tm_ = localtime(&now); tm * tm_ = localtime(&now);
if (tm_->tm_year < 110) { // no NTP time if (tm_->tm_year < 110) { // no NTP time
LOG_WARNING(F("No NTP time. Cannot set RCtime")); LOG_WARNING(F("No NTP time. Cannot set RCtime"));
return; return;
@@ -1590,34 +1422,43 @@ void Thermostat::set_datetime(const char * dt) {
} }
// sets the thermostat working mode, where mode is a string // sets the thermostat working mode, where mode is a string
void Thermostat::set_mode(const std::string & mode, const uint8_t hc_num) { // converts string mode to HeatingCircuit::Mode
void Thermostat::set_mode(const char * value, const int8_t id) {
std::string mode;
if (!Helpers::value2string(value, mode)) {
return;
}
uint8_t hc_num = (id == -1) ? DEFAULT_HEATING_CIRCUIT : id;
if (mode_tostring(HeatingCircuit::Mode::OFF) == mode) { if (mode_tostring(HeatingCircuit::Mode::OFF) == mode) {
set_mode(HeatingCircuit::Mode::OFF, hc_num); set_mode_n(HeatingCircuit::Mode::OFF, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::MANUAL) == mode) { } else if (mode_tostring(HeatingCircuit::Mode::MANUAL) == mode) {
set_mode(HeatingCircuit::Mode::MANUAL, hc_num); set_mode_n(HeatingCircuit::Mode::MANUAL, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::AUTO) == mode) { } else if (mode_tostring(HeatingCircuit::Mode::AUTO) == mode) {
set_mode(HeatingCircuit::Mode::AUTO, hc_num); set_mode_n(HeatingCircuit::Mode::AUTO, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::DAY) == mode) { } else if (mode_tostring(HeatingCircuit::Mode::DAY) == mode) {
set_mode(HeatingCircuit::Mode::DAY, hc_num); set_mode_n(HeatingCircuit::Mode::DAY, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::NIGHT) == mode) { } else if (mode_tostring(HeatingCircuit::Mode::NIGHT) == mode) {
set_mode(HeatingCircuit::Mode::NIGHT, hc_num); set_mode_n(HeatingCircuit::Mode::NIGHT, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::HEAT) == mode) { } else if (mode_tostring(HeatingCircuit::Mode::HEAT) == mode) {
set_mode(HeatingCircuit::Mode::HEAT, hc_num); set_mode_n(HeatingCircuit::Mode::HEAT, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::NOFROST) == mode) { } else if (mode_tostring(HeatingCircuit::Mode::NOFROST) == mode) {
set_mode(HeatingCircuit::Mode::NOFROST, hc_num); set_mode_n(HeatingCircuit::Mode::NOFROST, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::ECO) == mode) { } else if (mode_tostring(HeatingCircuit::Mode::ECO) == mode) {
set_mode(HeatingCircuit::Mode::ECO, hc_num); set_mode_n(HeatingCircuit::Mode::ECO, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::HOLIDAY) == mode) { } else if (mode_tostring(HeatingCircuit::Mode::HOLIDAY) == mode) {
set_mode(HeatingCircuit::Mode::HOLIDAY, hc_num); set_mode_n(HeatingCircuit::Mode::HOLIDAY, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::COMFORT) == mode) { } else if (mode_tostring(HeatingCircuit::Mode::COMFORT) == mode) {
set_mode(HeatingCircuit::Mode::COMFORT, hc_num); set_mode_n(HeatingCircuit::Mode::COMFORT, hc_num);
} else { } else {
LOG_WARNING(F("Invalid mode %s. Cannot set"), mode.c_str()); LOG_WARNING(F("Invalid mode %s. Cannot set"), mode.c_str());
} }
} }
// Set the thermostat working mode // Set the thermostat working mode
void Thermostat::set_mode(const uint8_t mode, const uint8_t hc_num) { // mode is HeatingCircuit::Mode
void Thermostat::set_mode_n(const uint8_t mode, const uint8_t hc_num) {
if (can_write()) { if (can_write()) {
LOG_WARNING(F("Write not supported for this model Thermostat")); LOG_WARNING(F("Write not supported for this model Thermostat"));
return; return;
@@ -1893,109 +1734,93 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co
} }
} }
// add console commands // for HA specifically when receiving over MQTT
void Thermostat::console_commands(Shell & shell, unsigned int context) { void Thermostat::thermostat_cmd_temp(const char * message) {
EMSESPShell::commands->add_command(ShellContext::THERMOSTAT, float f = strtof((char *)message, 0);
CommandFlags::ADMIN, set_temperature(f, HeatingCircuit::Mode::AUTO, AUTO_HEATING_CIRCUIT);
flash_string_vector{F_(set), F_(master)},
flash_string_vector{F_(deviceid_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) {
uint8_t value;
if (arguments.empty()) {
value = EMSESP_DEFAULT_MASTER_THERMOSTAT;
} else {
value = Helpers::hextoint(arguments.front().c_str());
}
EMSESP::emsespSettingsService.update(
[&](EMSESPSettings & settings) {
settings.master_thermostat = value;
EMSESP::actual_master_thermostat(value); // set the internal value too
char buffer[5];
shell.printfln(F_(master_thermostat_fmt),
!value ? uuid::read_flash_string(F_(auto)).c_str() : Helpers::hextoa(buffer, value));
return StateUpdateResult::CHANGED;
},
"local");
});
EMSESPShell::commands->add_command(ShellContext::THERMOSTAT,
CommandFlags::ADMIN,
flash_string_vector{F_(read)},
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());
});
EMSESPShell::commands->add_command(ShellContext::THERMOSTAT,
CommandFlags::ADMIN,
flash_string_vector{F_(temp)},
flash_string_vector{F_(degrees_mandatory), F_(hc_optional), F_(mode_optional)},
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
uint8_t hc = (arguments.size() >= 2) ? arguments[1].at(0) - '0' : AUTO_HEATING_CIRCUIT;
if ((arguments.size() == 3)) {
set_temperature(atof(arguments.front().c_str()), arguments.back().c_str(), hc);
} else if (arguments[1].at(0) >= 'A') {
set_temperature(atof(arguments.front().c_str()), arguments.back().c_str(), AUTO_HEATING_CIRCUIT);
} else {
set_temperature(atof(arguments.front().c_str()), HeatingCircuit::Mode::AUTO, hc);
}
});
EMSESPShell::commands->add_command(
ShellContext::THERMOSTAT,
CommandFlags::ADMIN,
flash_string_vector{F_(mode)},
flash_string_vector{F_(mode_mandatory), F_(hc_optional)},
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
uint8_t hc = (arguments.size() == 2) ? arguments[1].at(0) - '0' : AUTO_HEATING_CIRCUIT;
set_mode(arguments.front(), hc);
},
[](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("off")),
read_flash_string(F("manual")),
read_flash_string(F("day")),
read_flash_string(F("night")),
read_flash_string(F("eco")),
read_flash_string(F("comfort")),
read_flash_string(F("heat")),
read_flash_string(F("nofrost")),
read_flash_string(F("auto"))
};
});
EMSESPShell::commands->add_command(
ShellContext::THERMOSTAT,
CommandFlags::ADMIN,
flash_string_vector{F_(wwmode)},
flash_string_vector{F_(mode_mandatory)},
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) { set_ww_mode(arguments.front()); },
[](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("off")), read_flash_string(F("on")), read_flash_string(F("auto"))};
});
EMSESPShell::commands->add_command(ShellContext::THERMOSTAT,
CommandFlags::USER,
flash_string_vector{F_(show)},
[&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { show_values(shell); });
EMSESPShell::commands->add_command(ShellContext::THERMOSTAT,
CommandFlags::USER,
flash_string_vector{F_(set)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
char buffer[4];
shell.printfln(F_(master_thermostat_fmt),
settings.master_thermostat == 0 ? uuid::read_flash_string(F_(auto)).c_str()
: Helpers::hextoa(buffer, settings.master_thermostat));
shell.println();
});
});
// enter the context
Console::enter_custom_context(shell, context);
} }
// for HA specifically when receiving over MQTT
// message payload holds the text name of the mode e.g. "auto"
void Thermostat::thermostat_cmd_mode(const char * message) {
set_mode(message, AUTO_HEATING_CIRCUIT);
}
void Thermostat::set_temperature_value(const char * value, const uint8_t id, const uint8_t mode) {
float f = 0;
uint8_t hc_num = (id == -1) ? DEFAULT_HEATING_CIRCUIT : id;
if (Helpers::value2float(value, f)) {
set_temperature(f, mode, hc_num);
}
}
void Thermostat::set_temp(const char * value, const int8_t id) {
set_temperature_value(value, id, HeatingCircuit::Mode::AUTO);
}
void Thermostat::set_nighttemp(const char * value, const int8_t id) {
set_temperature_value(value, id, HeatingCircuit::Mode::NIGHT);
}
void Thermostat::set_daytemp(const char * value, const int8_t id) {
set_temperature_value(value, id, HeatingCircuit::Mode::DAY);
}
void Thermostat::set_nofrosttemp(const char * value, const int8_t id) {
set_temperature_value(value, id, HeatingCircuit::Mode::NOFROST);
}
void Thermostat::set_ecotemp(const char * value, const int8_t id) {
set_temperature_value(value, id, HeatingCircuit::Mode::ECO);
}
void Thermostat::set_heattemp(const char * value, const int8_t id) {
set_temperature_value(value, id, HeatingCircuit::Mode::HEAT);
}
void Thermostat::set_summertemp(const char * value, const int8_t id) {
set_temperature_value(value, id, HeatingCircuit::Mode::SUMMER);
}
void Thermostat::set_designtemp(const char * value, const int8_t id) {
set_temperature_value(value, id, HeatingCircuit::Mode::DESIGN);
}
void Thermostat::set_offsettemp(const char * value, const int8_t id) {
set_temperature_value(value, id, HeatingCircuit::Mode::OFFSET);
}
void Thermostat::set_holidaytemp(const char * value, const int8_t id) {
set_temperature_value(value, id, HeatingCircuit::Mode::HOLIDAY);
}
// commands for MQTT and Console
void Thermostat::init_mqtt() {
register_mqtt_cmd(F("wwmode"), std::bind(&Thermostat::set_wwmode, this, _1, _2));
register_mqtt_cmd(F("control"), std::bind(&Thermostat::set_control, this, _1, _2));
register_mqtt_cmd(F("mode"), std::bind(&Thermostat::set_mode, this, _1, _2));
register_mqtt_cmd(F("holiday"), std::bind(&Thermostat::set_holiday, this, _1, _2));
register_mqtt_cmd(F("pause"), std::bind(&Thermostat::set_pause, this, _1, _2));
register_mqtt_cmd(F("party"), std::bind(&Thermostat::set_party, this, _1, _2));
register_mqtt_cmd(F("datetime"), std::bind(&Thermostat::set_datetime, this, _1, _2));
register_mqtt_cmd(F("minexttemp"), std::bind(&Thermostat::set_settings_minexttemp, this, _1, _2));
register_mqtt_cmd(F("clockoffset"), std::bind(&Thermostat::set_settings_clockoffset, this, _1, _2));
register_mqtt_cmd(F("calinttemp"), std::bind(&Thermostat::set_settings_calinttemp, this, _1, _2));
register_mqtt_cmd(F("display"), std::bind(&Thermostat::set_settings_display, this, _1, _2));
register_mqtt_cmd(F("building"), std::bind(&Thermostat::set_settings_building, this, _1, _2));
register_mqtt_cmd(F("language"), std::bind(&Thermostat::set_settings_language, this, _1, _2));
register_mqtt_cmd(F("remotetemp"), std::bind(&Thermostat::set_remotetemp, this, _1, _2));
register_mqtt_cmd(F("temp"), std::bind(&Thermostat::set_temp, this, _1, _2));
register_mqtt_cmd(F("nighttemp"), std::bind(&Thermostat::set_nighttemp, this, _1, _2));
register_mqtt_cmd(F("daytemp"), std::bind(&Thermostat::set_daytemp, this, _1, _2));
register_mqtt_cmd(F("nofrosttemp"), std::bind(&Thermostat::set_nofrosttemp, this, _1, _2));
register_mqtt_cmd(F("ecotemp"), std::bind(&Thermostat::set_ecotemp, this, _1, _2));
register_mqtt_cmd(F("heattemp"), std::bind(&Thermostat::set_heattemp, this, _1, _2));
register_mqtt_cmd(F("summertemp"), std::bind(&Thermostat::set_summertemp, this, _1, _2));
register_mqtt_cmd(F("designtemp"), std::bind(&Thermostat::set_designtemp, this, _1, _2));
register_mqtt_cmd(F("offsettemp"), std::bind(&Thermostat::set_offsettemp, this, _1, _2));
register_mqtt_cmd(F("holidaytemp"), std::bind(&Thermostat::set_holidaytemp, this, _1, _2));
}
} // namespace emsesp } // namespace emsesp

View File

@@ -220,55 +220,61 @@ class Thermostat : public EMSdevice {
void process_IBASettings(std::shared_ptr<const Telegram> telegram); void process_IBASettings(std::shared_ptr<const Telegram> telegram);
void process_RCTime(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_RC35wwSettings(std::shared_ptr<const Telegram> telegram);
void process_RC35Monitor(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_RC35Set(std::shared_ptr<const Telegram> telegram);
void process_RC30Monitor(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_RC30Set(std::shared_ptr<const Telegram> telegram);
void process_RC20Monitor(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_RC20Set(std::shared_ptr<const Telegram> telegram);
void process_RC20Remote(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_RC20Monitor_2(std::shared_ptr<const Telegram> telegram);
void process_RC20Set_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_RC10Monitor(std::shared_ptr<const Telegram> telegram);
void process_RC10Set(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_RC300Monitor(std::shared_ptr<const Telegram> telegram);
void process_RC300Set(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_JunkersMonitor(std::shared_ptr<const Telegram> telegram);
void process_JunkersSet(std::shared_ptr<const Telegram> telegram); void process_JunkersSet(std::shared_ptr<const Telegram> telegram);
void process_EasyMonitor(std::shared_ptr<const Telegram> telegram); void process_EasyMonitor(std::shared_ptr<const Telegram> telegram);
void process_RC300WWmode(std::shared_ptr<const Telegram> telegram); void process_RC300WWmode(std::shared_ptr<const Telegram> telegram);
// set functions // internal helper functions
void set_settings_minexttemp(const int8_t mt); void set_mode_n(const uint8_t mode, const uint8_t hc_num);
void set_settings_calinttemp(const int8_t ct); void set_temperature_value(const char * value, const uint8_t hc, const uint8_t mode);
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);
void set_temperature(const float temperature, const std::string & mode, const uint8_t hc_num); 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); void set_temperature(const float temperature, const uint8_t mode, const uint8_t hc_num);
// MQTT functions // for HA specifically. MQTT functions.
void thermostat_cmd(const char * message);
void thermostat_cmd_temp(const char * message); void thermostat_cmd_temp(const char * message);
void thermostat_cmd_mode(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
void set_wwmode(const char * value, const int8_t id);
void set_datetime(const char * value, const int8_t id);
void set_settings_minexttemp(const char * value, const int8_t id);
void set_settings_clockoffset(const char * value, const int8_t id);
void set_settings_calinttemp(const char * value, const int8_t id);
void set_settings_display(const char * value, const int8_t id);
void set_settings_building(const char * value, const int8_t id);
void set_settings_language(const char * value, const int8_t id);
}; };
} // namespace emsesp } // namespace emsesp

View File

@@ -20,11 +20,9 @@
#include "emsesp.h" #include "emsesp.h"
#include "mqtt.h" // for the mqtt_function_p #include "mqtt.h" // for the mqtt_function_p
MAKE_PSTR(logger_name, "emsdevice")
namespace emsesp { 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 { std::string EMSdevice::brand_to_string() const {
switch (brand_) { switch (brand_) {
@@ -55,6 +53,35 @@ std::string EMSdevice::brand_to_string() const {
return std::string{}; 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::BOILER:
return read_flash_string(F("boiler"));
break;
case DeviceType::THERMOSTAT:
return read_flash_string(F("thermostat"));
break;
case DeviceType::HEATPUMP:
return read_flash_string(F("heatpump"));
break;
case DeviceType::SOLAR:
return read_flash_string(F("solar"));
break;
case DeviceType::MIXING:
return read_flash_string(F("mixing"));
break;
default:
return std::string{};
break;
}
}
std::string EMSdevice::device_type_name() const { std::string EMSdevice::device_type_name() const {
switch (device_type_) { switch (device_type_) {
case DeviceType::BOILER: case DeviceType::BOILER:
@@ -206,12 +233,17 @@ void EMSdevice::show_telegram_handlers(uuid::console::Shell & shell) {
// list all the mqtt handlers for this device // list all the mqtt handlers for this device
void EMSdevice::show_mqtt_handlers(uuid::console::Shell & shell) { 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) { 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_); 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_id_, topic, f); Mqtt::subscribe(this->device_type_, topic, f);
}
void EMSdevice::register_mqtt_cmd(const __FlashStringHelper * cmd, mqtt_cmdfunction_p f) {
LOG_DEBUG(F("Registering MQTT cmd %s for device type %s"), uuid::read_flash_string(cmd).c_str(), this->device_type_name().c_str());
Mqtt::add_command(this->device_type_, cmd, f);
} }
EMSdevice::TelegramFunction::TelegramFunction(uint16_t telegram_type_id, EMSdevice::TelegramFunction::TelegramFunction(uint16_t telegram_type_id,
@@ -239,7 +271,7 @@ std::string EMSdevice::telegram_type_name(std::shared_ptr<const Telegram> telegr
} }
for (const auto & tf : telegram_functions_) { 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_); return uuid::read_flash_string(tf.telegram_type_name_);
} }
} }
@@ -295,6 +327,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_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) { void EMSdevice::print_value(uuid::console::Shell & shell, uint8_t padding, const __FlashStringHelper * name, const char * value) {
uint8_t i = padding; uint8_t i = padding;
while (i-- > 0) { while (i-- > 0) {
@@ -304,4 +337,52 @@ 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); 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) {
EMSESPShell::commands->add_command(
context,
CommandFlags::ADMIN,
flash_string_vector{F_(call)},
flash_string_vector{F_(cmd_optional), F_(data_optional), F_(id_optional)},
[&](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 } // namespace emsesp

View File

@@ -47,11 +47,22 @@ class EMSdevice {
virtual ~EMSdevice() = default; // destructor of base class must always be virtual because it's a polymorphic class virtual ~EMSdevice() = default; // destructor of base class must always be virtual because it's a polymorphic class
/*
// https://github.com/proddy/EMS-ESP/issues/434#issuecomment-667840531
inline uint8_t device_id(uint8_t hc = 0) const {
if (((device_id_ & 0x7F) >= 0x18) && ((device_id_ & 0x7F) <= 0x1B)) {
return ((device_id_ & 0x80) + 0x18 + hc);
}
return device_id_;
}
*/
inline uint8_t device_id() const { inline uint8_t device_id() const {
return device_id_; 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 { inline uint8_t product_id() const {
return product_id_; return product_id_;
@@ -129,7 +140,10 @@ class EMSdevice {
void read_command(const uint16_t type_id); 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_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 functions overrules by derived classes
virtual void show_values(uuid::console::Shell & shell) = 0; virtual void show_values(uuid::console::Shell & shell) = 0;
@@ -222,7 +236,7 @@ class EMSdevice {
enum DeviceType : uint8_t { enum DeviceType : uint8_t {
UNKNOWN = 0, UNKNOWN = 0,
SERVICEKEY, SERVICEKEY, // us
BOILER, BOILER,
THERMOSTAT, THERMOSTAT,
MIXING, MIXING,

View File

@@ -18,8 +18,6 @@
#include "emsesp.h" #include "emsesp.h"
MAKE_PSTR(logger_name, "emsesp")
namespace emsesp { namespace emsesp {
using DeviceFlags = emsesp::EMSdevice; using DeviceFlags = emsesp::EMSdevice;
@@ -45,7 +43,7 @@ EMSESPDevicesService EMSESP::emsespDevicesService = EMSESPDevicesService(&webSer
std::vector<std::unique_ptr<EMSdevice>> EMSESP::emsdevices; // array of all the detected EMS devices 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 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 // The services
RxService EMSESP::rxservice_; // incoming Telegram Rx handler RxService EMSESP::rxservice_; // incoming Telegram Rx handler
@@ -242,7 +240,6 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) {
for (const auto & emsdevice : emsdevices) { for (const auto & emsdevice : emsdevices) {
if ((emsdevice) && (emsdevice->device_type() == device_class.first)) { if ((emsdevice) && (emsdevice->device_type() == device_class.first)) {
emsdevice->show_values(shell); emsdevice->show_values(shell);
shell.println();
} }
} }
} }
@@ -758,17 +755,14 @@ void EMSESP::start() {
esp8266React.begin(); // loads system settings (wifi, mqtt, etc) esp8266React.begin(); // loads system settings (wifi, mqtt, etc)
emsespSettingsService.begin(); // load EMS-ESP specific settings emsespSettingsService.begin(); // load EMS-ESP specific settings
// system_.check_upgrade(); // see if we need to migrate from previous versions // system_.check_upgrade(); // see if we need to migrate from previous versions
mqtt_.start(); // mqtt init
console_.start(); // telnet and serial console console_.start(); // telnet and serial console
system_.start(); // starts syslog, uart, sets version, initializes LED. Requires pre-loaded settings. system_.start(); // starts syslog, uart, sets version, initializes LED. Requires pre-loaded settings.
mqtt_.start(); // mqtt init
shower_.start(); // initialize shower timer and shower alert shower_.start(); // initialize shower timer and shower alert
txservice_.start(); // sets bus ID, sends out request for EMS devices txservice_.start(); // sets bus ID, sends out request for EMS devices
sensors_.start(); // dallas external sensors sensors_.start(); // dallas external sensors
webServer.begin(); // start web server
webServer.begin(); // start web server
} }
// main loop calling all services // main loop calling all services
@@ -777,9 +771,11 @@ void EMSESP::loop() {
// if we're doing an OTA upload, skip MQTT and EMS // if we're doing an OTA upload, skip MQTT and EMS
if (system_.upload_status()) { if (system_.upload_status()) {
/*
#if defined(ESP32) #if defined(ESP32)
delay(10); // slow down OTA update to avoid getting killed by task watchdog (task_wdt) delay(10); // slow down OTA update to avoid getting killed by task watchdog (task_wdt)
#endif #endif
*/
return; return;
} }
@@ -796,7 +792,9 @@ void EMSESP::loop() {
fetch_device_values(); fetch_device_values();
} }
#if defined(ESP8266)
delay(1); delay(1);
#endif
} }
} // namespace emsesp } // namespace emsesp

View File

@@ -17,7 +17,6 @@
*/ */
#include "helpers.h" #include "helpers.h"
#include "telegram.h" // for EMS_VALUE_* settings
namespace emsesp { namespace emsesp {
@@ -329,9 +328,9 @@ bool Helpers::check_abs(const int32_t i) {
return ((i < 0 ? -i : i) != 0xFFFFFF); return ((i < 0 ? -i : i) != 0xFFFFFF);
} }
// for booleans, use isBool true (VALUE_BOOL) // for booleans, use isBool true (EMS_VALUE_BOOL)
bool Helpers::hasValue(const uint8_t v, bool isBool) { bool Helpers::hasValue(const uint8_t v, const uint8_t isBool) {
if (isBool) { if (isBool == EMS_VALUE_BOOL) {
return (v != EMS_VALUE_BOOL_NOTSET); return (v != EMS_VALUE_BOOL_NOTSET);
} }
return (v != EMS_VALUE_UINT_NOTSET); return (v != EMS_VALUE_UINT_NOTSET);
@@ -354,4 +353,61 @@ bool Helpers::hasValue(const uint32_t v) {
return (v != EMS_VALUE_ULONG_NOTSET); 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, uint8_t value) {
if ((v == nullptr) || (strlen(v) == 0)) {
return false;
}
if ((strncmp(v, "on", 2) == 0) || (strncmp(v, "1", 1) == 0) || (strncmp(v, "true", 4) == 0)) {
value = true;
return true;
}
if ((strncmp(v, "off", 3) == 0) || (strncmp(v, "0", 1) == 0) || (strncmp(v, "false", 5) == 0)) {
value = false;
return true;
}
return false;
}
} // namespace emsesp } // namespace emsesp

View File

@@ -22,6 +22,8 @@
#include <Arduino.h> #include <Arduino.h>
#include <uuid/common.h> #include <uuid/common.h>
#include "telegram.h" // for EMS_VALUE_* settings
namespace emsesp { namespace emsesp {
class Helpers { class Helpers {
@@ -48,11 +50,18 @@ class Helpers {
static char * ultostr(char * ptr, uint32_t value, const uint8_t base); static char * ultostr(char * ptr, uint32_t value, const uint8_t base);
#endif #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 int8_t v);
static bool hasValue(const int16_t v); static bool hasValue(const int16_t v);
static bool hasValue(const uint16_t v); static bool hasValue(const uint16_t v);
static bool hasValue(const uint32_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, uint8_t value);
static bool value2string(const char * v, std::string & value);
}; };
} // namespace emsesp } // namespace emsesp

117
src/locale_EN.h Normal file
View File

@@ -0,0 +1,117 @@
/*
* 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(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, "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_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(asterisks, "********")
MAKE_PSTR(n_mandatory, "<n>")
MAKE_PSTR(n_optional, "[n]")
MAKE_PSTR(data_optional, "[data]")
MAKE_PSTR(id_optional, "[id]")
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 "emsesp.h"
#include "version.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 { namespace emsesp {
AsyncMqttClient * Mqtt::mqttClient_; AsyncMqttClient * Mqtt::mqttClient_;
@@ -35,14 +29,16 @@ std::string Mqtt::hostname_;
uint8_t Mqtt::mqtt_qos_; uint8_t Mqtt::mqtt_qos_;
uint16_t Mqtt::publish_time_; uint16_t Mqtt::publish_time_;
std::vector<Mqtt::MQTTSubFunction> Mqtt::mqtt_subfunctions_; std::vector<Mqtt::MQTTSubFunction> Mqtt::mqtt_subfunctions_;
std::vector<Mqtt::MQTTCmdFunction> Mqtt::mqtt_cmdfunctions_;
uint16_t Mqtt::mqtt_publish_fails_ = 0; uint16_t Mqtt::mqtt_publish_fails_ = 0;
size_t Mqtt::maximum_mqtt_messages_ = Mqtt::MAX_MQTT_MESSAGES; size_t Mqtt::maximum_mqtt_messages_ = Mqtt::MAX_MQTT_MESSAGES;
uint16_t Mqtt::mqtt_message_id_ = 0; uint16_t Mqtt::mqtt_message_id_ = 0;
std::deque<Mqtt::QueuedMqttMessage> Mqtt::mqtt_messages_; std::deque<Mqtt::QueuedMqttMessage> Mqtt::mqtt_messages_;
char will_topic_[Mqtt::MQTT_TOPIC_MAX_SIZE]; // because MQTT library keeps only char pointer char will_topic_[Mqtt::MQTT_TOPIC_MAX_SIZE]; // because MQTT library keeps only char pointer
uuid::log::Logger Mqtt::logger_{F_(logger_name), uuid::log::Facility::DAEMON}; uuid::log::Logger Mqtt::logger_{F_(mqtt), uuid::log::Facility::DAEMON};
Mqtt::QueuedMqttMessage::QueuedMqttMessage(uint16_t id, std::shared_ptr<MqttMessage> && content) Mqtt::QueuedMqttMessage::QueuedMqttMessage(uint16_t id, std::shared_ptr<MqttMessage> && content)
: id_(id) : id_(id)
@@ -58,34 +54,65 @@ MqttMessage::MqttMessage(const uint8_t operation, const std::string & topic, con
, retain(retain) { , retain(retain) {
} }
Mqtt::MQTTSubFunction::MQTTSubFunction(const uint8_t device_id, const std::string && topic, mqtt_subfunction_p mqtt_subfunction) Mqtt::MQTTSubFunction::MQTTSubFunction(const uint8_t device_type, const std::string && topic, const std::string && full_topic, mqtt_subfunction_p mqtt_subfunction)
: device_id_(device_id) : device_type_(device_type)
, topic_(topic) , topic_(topic)
, full_topic_(full_topic)
, mqtt_subfunction_(mqtt_subfunction) { , mqtt_subfunction_(mqtt_subfunction) {
} }
Mqtt::MQTTCmdFunction::MQTTCmdFunction(const uint8_t device_type, const __FlashStringHelper * cmd, mqtt_cmdfunction_p mqtt_cmdfunction)
: device_type_(device_type)
, cmd_(cmd)
, mqtt_cmdfunction_(mqtt_cmdfunction) {
}
// subscribe to an MQTT topic, and store the associated callback function // 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) { // only if it already hasn't been added
auto message = queue_subscribe_message(topic); // add subscription to queue. The hostname will automatically be appended void Mqtt::subscribe(const uint8_t device_type, const std::string & topic, mqtt_subfunction_p cb) {
if (message == nullptr) {
return;
}
// the message will contain the full topic, with the hostname prefixed
// check if we already have the topic subscribed, if so don't add it again // check if we already have the topic subscribed, if so don't add it again
bool exists = false;
if (!mqtt_subfunctions_.empty()) { if (!mqtt_subfunctions_.empty()) {
for (const auto & mqtt_subfunction : mqtt_subfunctions_) { 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)) { if ((mqtt_subfunction.device_type_ == device_type) && (strcmp(mqtt_subfunction.topic_.c_str(), topic.c_str()) == 0)) {
exists = true; return; // it exists, exit
} }
} }
} }
if (!exists) { // add to MQTT queue as a subscribe operation
mqtt_subfunctions_.emplace_back(device_id, std::move(message->topic), cb); // register a call back function for a specific telegram type 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), cb);
}
// adds a command and callback function for a specific device
void Mqtt::add_command(const uint8_t device_type, const __FlashStringHelper * cmd, mqtt_cmdfunction_p cb) {
// subscribe to the command topic if it doesn't exist yet
// create the cmd topic for a device like "<device_type>_cmd" e.g. "boiler_cmd"
// unless its a system MQTT command
std::string cmd_topic(40, '\0');
if (device_type == EMSdevice::DeviceType::SERVICEKEY) {
cmd_topic = "system"; // hard-coded system
} else {
snprintf_P(&cmd_topic[0], 40, PSTR("%s_cmd"), EMSdevice::device_type_topic_name(device_type).c_str());
} }
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
}
// add the function to our list
mqtt_cmdfunctions_.emplace_back(device_type, cmd, cb);
} }
// subscribe to an MQTT topic, and store the associated callback function. For generic functions not tied to a specific device // subscribe to an MQTT topic, and store the associated callback function. For generic functions not tied to a specific device
@@ -142,7 +169,20 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) {
// show subscriptions // show subscriptions
shell.printfln(F("MQTT subscriptions:")); shell.printfln(F("MQTT subscriptions:"));
for (const auto & mqtt_subfunction : mqtt_subfunctions_) { for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
shell.printfln(F(" %s"), mqtt_subfunction.topic_.c_str()); shell.printfln(F(" %s"), mqtt_subfunction.full_topic_.c_str());
}
shell.println();
// show command handlers
shell.printfln(F("MQTT commands:"));
for (const auto & mqtt_cmdfunction : mqtt_cmdfunctions_) {
if (mqtt_cmdfunction.device_type_ == EMSdevice::DeviceType::SERVICEKEY) {
shell.printfln(F(" on topic: system, cmd: %s"), uuid::read_flash_string(mqtt_cmdfunction.cmd_).c_str()); // hardcoded topic is system
} else {
shell.printfln(F(" on topic: %s_cmd, cmd: %s"),
EMSdevice::device_type_topic_name(mqtt_cmdfunction.device_type_).c_str(),
uuid::read_flash_string(mqtt_cmdfunction.cmd_).c_str());
}
} }
shell.println(); shell.println();
@@ -190,6 +230,31 @@ void Mqtt::incoming(char * topic, char * payload) {
on_message(topic, payload, strlen(payload)); on_message(topic, payload, strlen(payload));
} }
// 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("calling command %s, value %s, id is default"), cmd, value);
} else {
LOG_DEBUG(F("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 // received an MQTT message that we subscribed too
void Mqtt::on_message(char * topic, char * payload, size_t len) { void Mqtt::on_message(char * topic, char * payload, size_t len) {
if (len == 0) { if (len == 0) {
@@ -199,33 +264,65 @@ void Mqtt::on_message(char * topic, char * payload, size_t len) {
// convert payload to a null-terminated char string // convert payload to a null-terminated char string
char message[len + 2]; char message[len + 2];
strlcpy(message, payload, len + 1); strlcpy(message, payload, len + 1);
#ifdef EMSESP_DEBUG
LOG_DEBUG(F("[DEBUG] Received %s => %s (length %d)"), topic, message, len); 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 // 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_) { for (const auto & mf : mqtt_subfunctions_) {
if (strcmp(topic, mf.topic_.c_str()) == 0) { if (strcmp(topic, mf.full_topic_.c_str()) == 0) {
(mf.mqtt_subfunction_)(message); if (mf.mqtt_subfunction_) {
return; (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
int8_t n = -1; // no value
if (doc.containsKey("hc")) {
n = doc["hc"];
} else if (doc.containsKey("id")) {
n = doc["id"];
}
if (!call_command(mf.device_type_, command, doc["data"], n)) {
// if we got here we didn't find a matching command
LOG_ERROR(F("MQTT error: no matching cmd: %s"), command);
}
return;
}
} }
} }
// if we got here we didn't find a topic match // 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 // print all the topics related to a specific device type
void Mqtt::show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_id) { void Mqtt::show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type) {
if (std::count_if(mqtt_subfunctions_.cbegin(), if (std::count_if(mqtt_subfunctions_.cbegin(),
mqtt_subfunctions_.cend(), 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) { == 0) {
return; return;
} }
shell.print(F(" Subscribed MQTT topics: ")); shell.print(F(" Subscribed MQTT topics: "));
for (const auto & mqtt_subfunction : mqtt_subfunctions_) { 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()); shell.printf(F("%s "), mqtt_subfunction.topic_.c_str());
} }
} }
@@ -297,8 +394,13 @@ void Mqtt::start() {
mqttClient_->setWill(will_topic, 1, true, "offline"); // with qos 1, retain true 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) { 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); on_message(topic, payload, len);
mqttClient_->onPublish([this](uint16_t packetId) { on_publish(packetId); }); });
mqttClient_->onPublish([this](uint16_t packetId) {
// publish
on_publish(packetId);
}); });
} }
@@ -328,15 +430,18 @@ void Mqtt::on_connect() {
resubscribe(); // in case this is a reconnect, re-subscribe again to all MQTT topics 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 // add the system MQTT subscriptions, only if its a fresh start with no previous subscriptions
// these commands respond to the topic "system" and take a payload like {cmd:"", data:"", id:""}
if (mqtt_subfunctions_.empty()) { if (mqtt_subfunctions_.empty()) {
Mqtt::subscribe("cmd", System::mqtt_commands); add_command(EMSdevice::DeviceType::SERVICEKEY, F("gpio"), System::mqtt_command_gpio);
add_command(EMSdevice::DeviceType::SERVICEKEY, F("send"), System::mqtt_command_send);
} }
LOG_INFO(F("MQTT connected")); LOG_INFO(F("MQTT connected"));
} }
// add sub or pub task to the queue. When the message is created, the topic will have // add sub or pub task to the queue.
// automatically the hostname prefixed. // 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> 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) { Mqtt::queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, const bool retain, bool no_prefix) {
if (topic.empty()) { if (topic.empty()) {

View File

@@ -44,6 +44,8 @@ using uuid::console::Shell;
namespace emsesp { namespace emsesp {
using mqtt_subfunction_p = std::function<void(const char * message)>; using mqtt_subfunction_p = std::function<void(const char * message)>;
using mqtt_cmdfunction_p = std::function<void(const char * data, const int8_t id)>;
using namespace std::placeholders; // for `_1` using namespace std::placeholders; // for `_1`
struct MqttMessage { struct MqttMessage {
@@ -68,20 +70,24 @@ class Mqtt {
static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 100; 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 subscribe(const std::string & topic, mqtt_subfunction_p cb);
static void resubscribe(); static void resubscribe();
static void add_command(const uint8_t device_type, 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 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 JsonDocument & payload, bool retain = false);
static void publish(const std::string & topic, const bool value); static void publish(const std::string & topic, const bool value);
static void publish(const std::string & topic); 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 show_mqtt(uuid::console::Shell & shell);
static void on_connect(); 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() { void disconnect() {
mqttClient_->disconnect(); mqttClient_->disconnect();
} }
@@ -102,6 +108,20 @@ class Mqtt {
static std::string hostname_; static std::string hostname_;
class MQTTCmdFunction {
public:
MQTTCmdFunction(const uint8_t device_type, const __FlashStringHelper * cmd, mqtt_cmdfunction_p mqtt_cmdfunction);
~MQTTCmdFunction() = default;
const uint8_t device_type_;
const __FlashStringHelper * cmd_;
mqtt_cmdfunction_p mqtt_cmdfunction_;
};
static std::vector<MQTTCmdFunction> commands() {
return mqtt_cmdfunctions_;
}
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;
@@ -127,29 +147,32 @@ class Mqtt {
static constexpr uint32_t MQTT_PUBLISH_WAIT = 200; // delay between sending publishes, to account for large payloads 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 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, bool no_prefix = false);
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_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); static std::shared_ptr<const MqttMessage> queue_subscribe_message(const std::string & topic);
void on_publish(uint16_t packetId); void on_publish(uint16_t packetId);
void on_message(char * topic, char * payload, size_t len); void on_message(char * topic, char * payload, size_t len);
void process_queue(); void process_queue();
void process_all_queue(); void process_all_queue();
static uint16_t mqtt_publish_fails_; static uint16_t mqtt_publish_fails_;
// function handlers for MQTT subscriptions // function handlers for MQTT subscriptions
class MQTTSubFunction { class MQTTSubFunction {
public: public:
MQTTSubFunction(const uint8_t device_id, const std::string && topic, mqtt_subfunction_p mqtt_subfunction); MQTTSubFunction(const uint8_t device_type, const std::string && topic, const std::string && full_topic, mqtt_subfunction_p mqtt_subfunction);
~MQTTSubFunction() = default; ~MQTTSubFunction() = default;
const uint8_t device_id_; // which device ID owns this const uint8_t device_type_; // which device type, from DeviceType::
const std::string topic_; const std::string topic_;
mqtt_subfunction_p mqtt_subfunction_; const std::string full_topic_; // the fully qualified topic name, usually with the hostname prefixed
mqtt_subfunction_p mqtt_subfunction_; // can be empty
}; };
static std::vector<MQTTSubFunction> mqtt_subfunctions_; // list of mqtt subscribe callbacks for all devices 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_mqtt_poll_ = 0;
uint32_t last_publish_ = 0; uint32_t last_publish_ = 0;

View File

@@ -16,13 +16,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * 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 "sensors.h"
#include "emsesp.h" #include "emsesp.h"
MAKE_PSTR(logger_name, "sensors")
#ifdef ESP32 #ifdef ESP32
#define YIELD #define YIELD
#else #else
@@ -31,7 +29,7 @@ MAKE_PSTR(logger_name, "sensors")
namespace emsesp { 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() { void Sensors::start() {
// copy over values from MQTT so we don't keep on quering the filesystem // 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; 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; return (float)raw / 100;
#else #else
return NAN; return NAN;

View File

@@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * 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 #ifndef EMSESP_SENSORS_H
#define 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_DS1822 = 0x22;
static constexpr uint8_t TYPE_DS1825 = 0x3B; static constexpr uint8_t TYPE_DS1825 = 0x3B;
static constexpr uint32_t READ_INTERVAL_MS = 5000; // 5 seconds static constexpr uint32_t READ_INTERVAL_MS = 5000; // 5 seconds
static constexpr uint32_t CONVERSION_MS = 1000; // 1 seconds static constexpr uint32_t CONVERSION_MS = 1000; // 1 seconds
static constexpr uint32_t READ_TIMEOUT_MS = 2000; // 2 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 SCAN_TIMEOUT_MS = 3000; // 3 seconds
static constexpr uint8_t CMD_CONVERT_TEMP = 0x44; static constexpr uint8_t CMD_CONVERT_TEMP = 0x44;
static constexpr uint8_t CMD_READ_SCRATCHPAD = 0xBE; static constexpr uint8_t CMD_READ_SCRATCHPAD = 0xBE;
@@ -112,7 +112,6 @@ class Sensors {
uint8_t mqtt_format_; uint8_t mqtt_format_;
uint8_t retrycnt_ = 0; uint8_t retrycnt_ = 0;
}; };
} // namespace emsesp } // namespace emsesp

View File

@@ -18,11 +18,9 @@
#include "shower.h" #include "shower.h"
MAKE_PSTR(logger_name, "shower")
namespace emsesp { 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() { void Shower::start() {
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {

View File

@@ -21,25 +21,9 @@
#include "version.h" // firmware version of EMS-ESP #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 { 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 #ifndef EMSESP_STANDALONE
uuid::syslog::SyslogService System::syslog_; uuid::syslog::SyslogService System::syslog_;
@@ -50,94 +34,28 @@ uint32_t System::heap_start_ = 0;
int System::reset_counter_ = 0; int System::reset_counter_ = 0;
bool System::upload_status_ = false; bool System::upload_status_ = false;
// handle generic system related MQTT commands // send on/off to a gpio pin
void System::mqtt_commands(const char * message) { // value: true = HIGH, false = LOW
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc; void System::mqtt_command_gpio(const char * value, const int8_t id) {
DeserializationError error = deserializeJson(doc, message);
if (error) {
LOG_DEBUG(F("MQTT error: payload %s, error %s"), message, error.c_str());
return;
}
if (doc["send"] != nullptr) {
const char * data = doc["send"];
EMSESP::send_raw_telegram(data);
LOG_INFO(F("Sending raw: %s"), data);
}
#if defined(ESP8266) #if defined(ESP8266)
const uint8_t d0_ = 16; const uint8_t pins[] = {16, 5, 4, 0};
const uint8_t d1_ = 5;
const uint8_t d2_ = 4;
const uint8_t d3_ = 0;
#elif defined(ESP32) #elif defined(ESP32)
const uint8_t d0_ = 26; const uint8_t pins[] = {26, 22, 21, 17};
const uint8_t d1_ = 22; #else
const uint8_t d2_ = 21; const uint8_t pins[] = {0, 1, 2, 3};
const uint8_t d3_ = 17;
#endif #endif
bool v = false;
#ifndef EMSESP_STANDALONE if (Helpers::value2bool(value, v)) {
if (doc["D0"] != nullptr) { uint8_t gpio = pins[id]; // D0 - D3
const int8_t set = doc["D0"]; pinMode(gpio, OUTPUT);
pinMode(d0_, OUTPUT); digitalWrite(gpio, v);
if (set == 1) { LOG_INFO(F("Port D%d set to %s"), id, v ? "HIGH" : "LOW");
digitalWrite(d0_, HIGH);
} else if (set == 0) {
digitalWrite(d0_, LOW);
}
LOG_INFO(F("Port D0 set to %d"), set);
} }
}
if (doc["D1"] != nullptr) { // send raw
const int8_t set = doc["D1"]; void System::mqtt_command_send(const char * value, const int8_t id) {
pinMode(d1_, OUTPUT); EMSESP::send_raw_telegram(value); // ignore id
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;
}
} }
// restart EMS-ESP // restart EMS-ESP
@@ -646,9 +564,10 @@ void System::console_commands(Shell & shell, unsigned int context) {
// upgrade from previous versions of EMS-ESP // upgrade from previous versions of EMS-ESP
void System::check_upgrade() { void System::check_upgrade() {
/*
// check for v1.9. It uses SPIFFS and only on the ESP8266 // check for v1.9. It uses SPIFFS and only on the ESP8266
#if defined(ESP8266) #if defined(ESP8266)
Serial.begin(115200); // TODO remove, just for debugging Serial.begin(115200);
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations" #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
@@ -771,6 +690,7 @@ void System::check_upgrade() {
}, },
"local"); "local");
#endif #endif
*/
} }
} // namespace emsesp } // namespace emsesp

View File

@@ -47,7 +47,9 @@ class System {
static void format(uuid::console::Shell & shell); static void format(uuid::console::Shell & shell);
static void console_commands(Shell & shell, unsigned int context); static void console_commands(Shell & shell, unsigned int context);
static void mqtt_commands(const char * message);
static void mqtt_command_gpio(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 uint8_t free_mem();
static void upload_status(bool in_progress); static void upload_status(bool in_progress);

View File

@@ -19,8 +19,6 @@
#include "telegram.h" #include "telegram.h"
#include "emsesp.h" #include "emsesp.h"
MAKE_PSTR(logger_name, "telegram")
namespace emsesp { namespace emsesp {
// CRC lookup table with poly 12 for faster checking // 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_mode_ = EMSESP_DEFAULT_TX_MODE;
uint8_t EMSbus::tx_state_ = Telegram::Operation::NONE; 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 // Calculates CRC checksum using lookup table for speed
// length excludes the last byte (which mainly is the CRC) // length excludes the last byte (which mainly is the CRC)
@@ -572,11 +570,13 @@ void TxService::retry_tx(const uint8_t operation, const uint8_t * data, const ui
return; return;
} }
#ifdef EMSESP_DENUG
LOG_DEBUG(F("[DEBUG] Last Tx %s operation failed. Retry #%d. sent message: %s, received: %s"), 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"), (operation == Telegram::Operation::TX_WRITE) ? F("Write") : F("Read"),
retry_count_, retry_count_,
telegram_last_->to_string().c_str(), telegram_last_->to_string().c_str(),
Helpers::data_to_hex(data, length).c_str()); Helpers::data_to_hex(data, length).c_str());
#endif
// add to the top of the queue // add to the top of the queue
if (tx_telegrams_.size() >= MAX_TX_TELEGRAMS) { if (tx_telegrams_.size() >= MAX_TX_TELEGRAMS) {

View File

@@ -38,9 +38,9 @@
#include "helpers.h" #include "helpers.h"
// default values for null values // default values for null values
static constexpr uint8_t VALUE_BOOL = true; // is a boolean static constexpr uint8_t EMS_VALUE_BOOL = 0xFF; // used to mark that something 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
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_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_BOOL_NOTSET = 0xFE; // random number for booleans, that's not 0, 1 or FF
static constexpr uint8_t EMS_VALUE_UINT_NOTSET = 0xFF; // for 8-bit unsigned ints/bytes static constexpr uint8_t EMS_VALUE_UINT_NOTSET = 0xFF; // for 8-bit unsigned ints/bytes

View File

@@ -17,6 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#if defined(EMSESP_STANDALONE) #if defined(EMSESP_STANDALONE)
#include "test.h" #include "test.h"
@@ -26,6 +27,14 @@ namespace emsesp {
// create some fake test data // create some fake test data
// used with the 'test' command, under su/admin // used with the 'test' command, under su/admin
void Test::run_test(uuid::console::Shell & shell, const std::string & command) { 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") { if (command == "render") {
uint8_t test1 = 12; uint8_t test1 = 12;
int8_t test2 = -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}); 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? // question: do we need to set the mask?
std::string version("1.2.3"); std::string version("1.2.3");
EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline
@@ -180,7 +189,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
} }
if (command == "thermostat") { if (command == "thermostat") {
shell.printfln(F("Testing adding devices on the EMS bus...")); shell.printfln(F("Testing adding a thermostat to the EMS bus..."));
// create some fake devices // create some fake devices
std::string version("1.2.3"); std::string version("1.2.3");
@@ -245,7 +254,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
// see https://github.com/proddy/EMS-ESP/issues/390 // 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 04 01 A6 5C");
uart_telegram_withCRC("90 48 FF 00 01 A6 4C"); uart_telegram_withCRC("90 48 FF 00 01 A6 4C");
uart_telegram_withCRC("90 48 FF 08 01 A7 6D"); uart_telegram_withCRC("90 48 FF 08 01 A7 6D");
@@ -283,7 +291,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("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("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"); uart_telegram_withCRC("C8 90 F7 02 01 FF 01 A6 BA");
@@ -473,49 +480,115 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
EMSESP::txservice_.flush_tx_queue(); EMSESP::txservice_.flush_tx_queue();
} }
if (command == "mqtt1") { if (command == "mqtt") {
shell.printfln(F("Testing MQTT...")); shell.printfln(F("Testing MQTT..."));
// MQTT test // simulate an on connect
EMSESP::txservice_.flush_tx_queue(); Mqtt::on_connect();
EMSESP::EMSESP::mqtt_.publish("boiler_cmd", "test me");
// EMSESP::mqtt_.show_queue(); // add a boiler
// simulate an incoming mqtt msg // 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
EMSESP::rxservice_.loop();
// 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
EMSESP::rxservice_.loop();
shell.loop_all();
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
char payload[100]; char payload[100];
strcpy(topic, "boiler_cmd"); // test publish and adding to queue
EMSESP::txservice_.flush_tx_queue();
EMSESP::EMSESP::mqtt_.publish("boiler_cmd", "test me");
Mqtt::show_mqtt(shell);
strcpy(topic, "ems-esp/boiler_cmd");
strcpy(payload, "12345"); strcpy(payload, "12345");
EMSESP::mqtt_.incoming(topic, payload); EMSESP::mqtt_.incoming(topic, payload); // invalid format
EMSESP::mqtt_.incoming(payload, payload); // should report error EMSESP::mqtt_.incoming(payload, payload); // no matching topic
strcpy(topic, "thermostat_cmd_mode"); strcpy(topic, "ems-esp/boiler_cmd");
strcpy(payload, "auto"); strcpy(payload, "{\"cmd\":\"garbage\",\"data\":22.52}");
EMSESP::mqtt_.incoming(topic, payload); // should report error
strcpy(topic, "ems-esp/boiler_cmd");
strcpy(payload, "{\"cmd\":\"comfort\",\"data\":\"eco\"}");
EMSESP::mqtt_.incoming(topic, payload); EMSESP::mqtt_.incoming(topic, payload);
strcpy(topic, "thermostat_cmd_temp"); strcpy(topic, "ems-esp/boiler_cmd");
strcpy(payload, "20"); strcpy(payload, "{\"cmd\":\"wwactivated\",\"data\":\"1\"}");
EMSESP::mqtt_.incoming(topic, payload); EMSESP::mqtt_.incoming(topic, payload);
strcpy(topic, "thermostat_cmd"); strcpy(topic, "ems-esp/boiler_cmd");
strcpy(payload, "{\"cmd\":\"wwactivated\",\"data\":1}");
EMSESP::mqtt_.incoming(topic, payload);
strcpy(topic, "ems-esp/boiler_cmd");
strcpy(payload, "{\"cmd\":\"flowtemp\",\"data\":55}");
EMSESP::mqtt_.incoming(topic, payload);
strcpy(topic, "ems-esp/system");
strcpy(payload, "{\"cmd\":\"send\",\"data\":\"01 02 03 04 05\"}");
EMSESP::mqtt_.incoming(topic, payload);
strcpy(topic, "ems-esp/system");
strcpy(payload, "{\"cmd\":\"gpio\",\"id\":1,\"data\":\"1\"}");
EMSESP::mqtt_.incoming(topic, payload);
strcpy(topic, "ems-esp/thermostat_cmd");
strcpy(payload, "{\"cmd\":\"wwmode\",\"data\":\"auto\"}");
EMSESP::mqtt_.incoming(topic, payload);
strcpy(topic, "ems-esp/thermostat_cmd");
strcpy(payload, "{\"cmd\":\"control\",\"data\":\"1\"}");
EMSESP::mqtt_.incoming(topic, payload);
strcpy(topic, "ems-esp/thermostat_cmd");
strcpy(payload, "{\"cmd\":\"control\",\"data\":1}");
EMSESP::mqtt_.incoming(topic, payload);
strcpy(topic, "ems-esp/thermostat_cmd");
strcpy(payload, "{\"cmd\":\"mode\",\"data\":\"auto\",\"id\":2}"); // with id
EMSESP::mqtt_.incoming(topic, payload);
strcpy(topic, "ems-esp/thermostat_cmd");
strcpy(payload, "{\"cmd\":\"mode\",\"data\":\"auto\",\"hc\":2}"); // with hc
EMSESP::mqtt_.incoming(topic, payload);
strcpy(topic, "ems-esp/thermostat_cmd");
strcpy(payload, "{\"cmd\":\"temp\",\"data\":22.52}"); strcpy(payload, "{\"cmd\":\"temp\",\"data\":22.52}");
EMSESP::mqtt_.incoming(topic, payload); EMSESP::mqtt_.incoming(topic, payload);
strcpy(topic, "boiler_cmd_wwtemp"); strcpy(topic, "ems-esp/thermostat_cmd");
strcpy(payload, "66"); strcpy(payload, "{\"cmd\":\"temp\",\"data\":22.52}");
EMSESP::mqtt_.incoming(topic, payload); EMSESP::mqtt_.incoming(topic, payload);
strcpy(topic, "thermostat_cmd"); strcpy(topic, "ems-esp/thermostat_cmd");
strcpy(payload, "{\"cmd\":\"temp\",\"hc\":2,\"data\":22}"); strcpy(payload, "{\"cmd\":\"temp\",\"id\":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(topic, payload);
// EMSESP::txservice_.show_tx_queue(); // 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.loop_all();
} }
if (command == "poll2") { if (command == "poll2") {
@@ -542,12 +615,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
uart_telegram({0x21, 0x0B, 0xFF, 0x00}); 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 // testing the UART tx command, without a queue
if (command == "tx2") { if (command == "tx2") {
uint8_t t[] = {0x0B, 0x88, 0x18, 0x00, 0x20, 0xD4}; // including CRC uint8_t t[] = {0x0B, 0x88, 0x18, 0x00, 0x20, 0xD4}; // including CRC
@@ -686,9 +753,6 @@ void Test::uart_telegram(const char * rx_data) {
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wunused-parameter"
void Test::dummy_mqtt_commands(const char * message) {
//
}
#pragma GCC diagnostic pop #pragma GCC diagnostic pop

View File

@@ -36,8 +36,6 @@
#include "mqtt.h" #include "mqtt.h"
#include "emsesp.h" #include "emsesp.h"
MAKE_PSTR_WORD(test)
namespace emsesp { namespace emsesp {
class Test { class Test {

View File

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