refactor commands to its own class, implement rest API #506

This commit is contained in:
proddy
2020-09-18 18:13:09 +02:00
parent 0d66d97fd2
commit 80ec1859e4
27 changed files with 1049 additions and 482 deletions

View File

@@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- support for uploading compressed firmware binaries - support for uploading compressed firmware binaries
- add wWType to MQTT publish - add wWType to MQTT publish
- option to set the MQTT retain flag - option to set the MQTT retain flag
- HTTP REST API, e.g. http://ems-esp/api?device=boiler&cmd=wwtemp&data=20&id=1
- `show commands` command
### Fixed ### Fixed
- fix wwontime readback - fix wwontime readback
@@ -21,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- renamed wWCircPumpType to wWChargeType - renamed wWCircPumpType to wWChargeType
- Installation doc moved to wiki - Installation doc moved to wiki
- Removed the _cmd from the MQTT topic names
### Removed ### Removed
- -

View File

@@ -31,6 +31,12 @@ class DummySettings {
String jwtSecret = "ems-esp"; String jwtSecret = "ems-esp";
String ssid = "ems-esp"; String ssid = "ems-esp";
String password = "ems-esp"; String password = "ems-esp";
String localIP;
String gatewayIP;
String subnetMask;
String staticIPConfig;
String dnsIP1;
String dnsIP2;
uint16_t publish_time_boiler; uint16_t publish_time_boiler;
uint16_t publish_time_thermostat; uint16_t publish_time_thermostat;
uint16_t publish_time_solar; uint16_t publish_time_solar;
@@ -105,4 +111,11 @@ class EMSESPSettingsService {
private: private:
}; };
class JsonUtils {
public:
static void writeIP(JsonObject & root, const String & key, const String & ip) {
root[key] = ip;
}
};
#endif #endif

View File

@@ -12,6 +12,39 @@ class AsyncWebServerRequest;
class AsyncWebServerResponse; class AsyncWebServerResponse;
class AsyncJsonResponse; class AsyncJsonResponse;
class AsyncWebParameter {
private:
String _name;
String _value;
size_t _size;
bool _isForm;
bool _isFile;
public:
AsyncWebParameter(const String & name, const String & value, bool form = false, bool file = false, size_t size = 0)
: _name(name)
, _value(value)
, _size(size)
, _isForm(form)
, _isFile(file) {
}
const String & name() const {
return _name;
}
const String & value() const {
return _value;
}
size_t size() const {
return _size;
}
bool isPost() const {
return _isForm;
}
bool isFile() const {
return _isFile;
}
};
typedef enum { typedef enum {
HTTP_GET = 0b00000001, HTTP_GET = 0b00000001,
HTTP_POST = 0b00000010, HTTP_POST = 0b00000010,
@@ -54,9 +87,41 @@ class AsyncWebServerRequest {
void send(AsyncWebServerResponse * response){}; void send(AsyncWebServerResponse * response){};
void send(AsyncJsonResponse * response){}; void send(AsyncJsonResponse * response){};
void send(int code, const String & contentType = String(), const String & content = String()){}; void send(int code, const String & contentType = String(), const String & content = String()){};
void send(int code, const String & contentType, const __FlashStringHelper *){};
bool hasParam(const String & name, bool post, bool file) const {
return false;
}
bool hasParam(const char * name, bool post, bool file) const {
return false;
}
bool hasParam(const __FlashStringHelper * data) const {
return false;
}
bool hasParam(const __FlashStringHelper * data, bool post, bool file) const {
return false;
}
AsyncWebParameter * getParam(const String & name, bool post, bool file) const {
return nullptr;
}
AsyncWebParameter * getParam(const __FlashStringHelper * data, bool post, bool file) const {
return nullptr;
}
AsyncWebParameter * getParam(const __FlashStringHelper * data) const {
return nullptr;
}
AsyncWebParameter * getParam(size_t num) const {
return nullptr;
}
AsyncWebServerResponse * beginResponse(int code, const String & contentType = String(), const String & content = String()) { AsyncWebServerResponse * beginResponse(int code, const String & contentType = String(), const String & content = String()) {
// AsyncWebServerResponse *a = new AsyncWebServerResponse()
return nullptr; return nullptr;
} }

View File

@@ -33,14 +33,13 @@ class String {
return lhs; return lhs;
} }
///
bool isEmpty() { bool isEmpty() {
return _str.empty(); return _str.empty();
} }
// long toInt() const { long toInt() const {
// return std::stol(_str); return std::stol(_str);
// } }
bool equals(const char * s) { bool equals(const char * s) {
return _str == s; return _str == s;

103
src/EMSESPAPIService.cpp Normal file
View File

@@ -0,0 +1,103 @@
/*
* 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/>.
*/
#include "EMSESPAPIService.h"
#include "emsesp.h"
#include "command.h"
#include "emsdevice.h"
#include "system.h"
namespace emsesp {
EMSESPAPIService::EMSESPAPIService(AsyncWebServer * server) {
server->on(EMSESP_API_SERVICE_PATH, HTTP_GET, std::bind(&EMSESPAPIService::emsespAPIService, this, std::placeholders::_1));
}
// http://ems-esp/api?device=boiler&cmd=wwtemp&data=20&id=1
void EMSESPAPIService::emsespAPIService(AsyncWebServerRequest * request) {
// must have device and cmd parameters
if ((!request->hasParam(F_(device))) || (!request->hasParam(F_(cmd)))) {
request->send(400, "text/plain", F("invalid syntax"));
return;
}
// get device
String device = request->getParam(F_(device))->value();
uint8_t device_type = EMSdevice::device_name_2_device_type(device.c_str());
if (device_type == emsesp::EMSdevice::DeviceType::UNKNOWN) {
request->send(400, "text/plain", F("invalid device"));
return;
}
// get cmd, we know we have one
String cmd = request->getParam(F_(cmd))->value();
// first test for special service commands
// e.g. http://ems-esp/api?device=system&cmd=info
if (device.equals("system")) {
if (cmd.equals("info")) {
request->send(200, "text/plain", System::export_settings());
EMSESP::logger().info(F("Sent settings json to web UI"));
return;
}
}
// look up command in our list
if (!Command::find(device_type, cmd.c_str())) {
request->send(400, "text/plain", F("invalid cmd"));
return;
}
String data;
if (request->hasParam(F_(data))) {
data = request->getParam(F_(data))->value();
}
String id;
if (request->hasParam(F_(id))) {
id = request->getParam(F_(id))->value();
}
// execute the command
bool ok = false;
if (data.isEmpty()) {
ok = Command::call_command(device_type, cmd.c_str(), nullptr, -1); // command only
} else if (id.isEmpty()) {
ok = Command::call_command(device_type, cmd.c_str(), data.c_str(), -1); // only ID
} else {
ok = Command::call_command(device_type, cmd.c_str(), data.c_str(), id.toInt()); // has cmd, data and id
}
// debug
#if defined(EMSESP_DEBUG)
std::string output(200, '\0');
snprintf_P(&output[0],
output.capacity() + 1,
PSTR("API: device=%s cmd=%s data=%s id=%s [%s]"),
device.c_str(),
cmd.c_str(),
data.c_str(),
id.c_str(),
ok ? F("OK") : F("Failed"));
EMSESP::logger().info(output.c_str());
#endif
request->send(200, "text/plain", ok ? F("OK") : F("Failed"));
}
} // namespace emsesp

41
src/EMSESPAPIService.h Normal file
View File

@@ -0,0 +1,41 @@
/*
* 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/>.
*/
#ifndef EMSESPAPIService_h
#define EMSESPAPIService_h
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
#define EMSESP_API_SERVICE_PATH "/api"
namespace emsesp {
class EMSESPAPIService {
public:
EMSESPAPIService(AsyncWebServer * server);
private:
void emsespAPIService(AsyncWebServerRequest * request);
};
} // namespace emsesp
#endif

View File

@@ -18,7 +18,6 @@
#include "EMSESPDevicesService.h" #include "EMSESPDevicesService.h"
#include "emsesp.h" #include "emsesp.h"
#include "mqtt.h"
namespace emsesp { namespace emsesp {
@@ -57,7 +56,7 @@ void EMSESPDevicesService::all_devices(AsyncWebServerRequest * request) {
obj["type"] = emsdevice->device_type_name(); obj["type"] = emsdevice->device_type_name();
obj["brand"] = emsdevice->brand_to_string(); obj["brand"] = emsdevice->brand_to_string();
obj["name"] = emsdevice->name(); obj["name"] = emsdevice->name();
obj["deviceid"] = emsdevice->get_device_id(); obj["deviceid"] = emsdevice->device_id();
obj["productid"] = emsdevice->product_id(); obj["productid"] = emsdevice->product_id();
obj["version"] = emsdevice->version(); obj["version"] = emsdevice->version();
} }

View File

@@ -18,8 +18,6 @@
#include "EMSESPStatusService.h" #include "EMSESPStatusService.h"
#include "emsesp.h" #include "emsesp.h"
#include "mqtt.h"
#include "version.h"
namespace emsesp { namespace emsesp {

186
src/command.cpp Normal file
View File

@@ -0,0 +1,186 @@
/*
* 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/>.
*/
#include "command.h"
#include "emsdevice.h"
#include "emsesp.h"
namespace emsesp {
uuid::log::Logger Command::logger_{F_(command), uuid::log::Facility::DAEMON};
std::vector<Command::CmdFunction> Command::cmdfunctions_;
// calls a command, context is the device_type
// id may be used to represent a heating circuit for example
// returns false if error or not found
bool Command::call_command(const uint8_t device_type, const char * cmd, const char * value, const int8_t id) {
#ifdef EMSESP_DEBUG
if (id == -1) {
LOG_DEBUG(F("[DEBUG] Calling command %s, value %s, id is default"), cmd, value);
} else {
LOG_DEBUG(F("[DEBUG] Calling command %s, value %s, id is %d"), cmd, value, id);
}
#endif
if (!cmdfunctions_.empty()) {
for (const auto & cf : 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) {
return ((cf.cmdfunction_)(value, id)); // call function, data needs to be a string and can be null
}
}
}
}
return false;
}
// add a command to the list
void Command::add_command(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cb) {
cmdfunctions_.emplace_back(device_type, device_id, cmd, cb);
// see if we need to subscribe
Mqtt::register_command(device_type, device_id, cmd, cb);
}
// see if a command exists for that device type
bool Command::find(const uint8_t device_type, const char * cmd) {
for (const auto & cf : cmdfunctions_) {
if (strcmp(cmd, uuid::read_flash_string(cf.cmd_).c_str()) == 0) {
return true;
}
}
return false; // not found
}
// output list of all commands to console for a specific DeviceType
void Command::show_commands(uuid::console::Shell & shell, uint8_t device_type) {
for (const auto & cf : Command::commands()) {
if (cf.device_type_ == device_type) {
shell.printf("%s ", uuid::read_flash_string(cf.cmd_).c_str());
}
}
shell.println();
}
// determines the device_type from the shell context we're in
uint8_t Command::context_2_device_type(unsigned int context) {
if (context == ShellContext::MAIN) {
return EMSdevice::DeviceType::SERVICEKEY;
}
if (context == ShellContext::BOILER) {
return EMSdevice::DeviceType::BOILER;
}
if (context == ShellContext::MIXING) {
return EMSdevice::DeviceType::MIXING;
}
if (context == ShellContext::SOLAR) {
return EMSdevice::DeviceType::SOLAR;
}
if (context == ShellContext::SYSTEM) {
return EMSdevice::DeviceType::SERVICEKEY;
}
if (context == ShellContext::THERMOSTAT) {
return EMSdevice::DeviceType::THERMOSTAT;
}
return EMSdevice::DeviceType::UNKNOWN; // unknown type
}
// show command per current context
void Command::show_commands(uuid::console::Shell & shell) {
show_commands(shell, context_2_device_type(shell.context()));
}
// output list of all commands to console
void Command::show_all_commands(uuid::console::Shell & shell) {
// show system first
shell.printf("%s: ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::SERVICEKEY).c_str());
show_commands(shell, EMSdevice::DeviceType::SERVICEKEY);
// do this in the order of factory classes to keep a consistent order when displaying
for (const auto & device_class : EMSFactory::device_handlers()) {
for (const auto & emsdevice : EMSESP::emsdevices) {
if ((emsdevice) && (emsdevice->device_type() == device_class.first)) {
shell.printf("%s: ", EMSdevice::device_type_2_device_name(device_class.first).c_str());
show_commands(shell, device_class.first);
}
}
}
}
// given a context, automatically add the commands to the console
void Command::add_context_commands(unsigned int context) {
// if we're adding commands for a thermostat or mixing, then include an additional optional paramter called heating circuit
flash_string_vector params;
if ((context == ShellContext::THERMOSTAT) || (context == ShellContext::MIXING)) {
params = flash_string_vector{F_(cmd_optional), F_(data_optional), F_(hc_optional)};
} else if ((context == ShellContext::MAIN) || (context == ShellContext::SYSTEM)) {
params = flash_string_vector{F_(cmd_optional), F_(data_optional), F_(n_optional)};
} else {
params = flash_string_vector{F_(cmd_optional), F_(data_optional)};
}
EMSESPShell::commands->add_command(
context,
CommandFlags::ADMIN,
flash_string_vector{F_(call)},
params,
[&](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.empty()) {
// list options
shell.print("Available commands: ");
show_commands(shell);
shell.println();
return;
}
// determine the device_type from the shell context
uint8_t device_type = context_2_device_type(shell.context());
const char * cmd = arguments[0].c_str();
if (arguments.size() == 1) {
// no value specified
(void)Command::call_command(device_type, cmd, nullptr, -1);
} else if (arguments.size() == 2) {
// has a value but no id
(void)Command::call_command(device_type, cmd, arguments.back().c_str(), -1);
} else {
// use value, which could be an id or hc
(void)Command::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;
uint8_t device_type = context_2_device_type(shell.context());
for (const auto & cf : Command::commands()) {
if (cf.device_type_ == device_type) {
commands.emplace_back(uuid::read_flash_string(cf.cmd_));
}
}
return commands;
});
}
} // namespace emsesp

77
src/command.h Normal file
View File

@@ -0,0 +1,77 @@
/*
* 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/>.
*/
#ifndef EMSESP_COMMAND_H_
#define EMSESP_COMMAND_H_
#include <Arduino.h>
#include <ArduinoJson.h>
#include <string>
#include <vector>
#include <functional>
#include "console.h"
#include <uuid/log.h>
using uuid::console::Shell;
namespace emsesp {
using cmdfunction_p = std::function<bool(const char * data, const int8_t id)>;
class Command {
public:
struct CmdFunction {
uint8_t device_type_; // DeviceType::
uint8_t device_id_;
const __FlashStringHelper * cmd_;
cmdfunction_p cmdfunction_;
CmdFunction(uint8_t device_type, uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cmdfunction)
: device_type_(device_type)
, device_id_(device_id)
, cmd_(cmd)
, cmdfunction_(cmdfunction) {
}
};
static std::vector<CmdFunction> commands() {
return cmdfunctions_;
}
static bool call_command(const uint8_t device_type, const char * cmd, const char * value, const int8_t id);
static void add_command(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cb);
static void show_all_commands(uuid::console::Shell & shell);
static void show_commands(uuid::console::Shell & shell);
static void add_context_commands(unsigned int context);
static bool find(const uint8_t device_type, const char * cmd);
static std::vector<CmdFunction> cmdfunctions_; // list of commands
private:
static uuid::log::Logger logger_;
static void show_commands(uuid::console::Shell & shell, uint8_t device_type);
static uint8_t context_2_device_type(unsigned int context);
};
} // namespace emsesp
#endif

View File

@@ -149,6 +149,12 @@ void EMSESPShell::add_console_commands() {
flash_string_vector{F_(show), F_(mqtt)}, flash_string_vector{F_(show), F_(mqtt)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { Mqtt::show_mqtt(shell); }); [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { Mqtt::show_mqtt(shell); });
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(show), F_(commands)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { Command::show_all_commands(shell); });
commands->add_command( commands->add_command(
ShellContext::MAIN, ShellContext::MAIN,
CommandFlags::ADMIN, CommandFlags::ADMIN,
@@ -457,6 +463,11 @@ void Console::load_standard_commands(unsigned int context) {
shell.printfln(F("Filtering only telegrams that match a device ID or telegram type of 0x%02X"), watch_id); shell.printfln(F("Filtering only telegrams that match a device ID or telegram type of 0x%02X"), watch_id);
} }
}); });
// load the commands (console & mqtt topics) for this specific context
Command::add_context_commands(context);
} }
// prompt, change per context // prompt, change per context

View File

@@ -52,22 +52,22 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_telegram_type(0xE9, F("UBADHWStatus"), false, [&](std::shared_ptr<const Telegram> t) { process_UBADHWStatus(t); }); register_telegram_type(0xE9, F("UBADHWStatus"), false, [&](std::shared_ptr<const Telegram> t) { process_UBADHWStatus(t); });
register_telegram_type(0xEA, F("UBAParameterWWPlus"), true, [&](std::shared_ptr<const Telegram> t) { process_UBAParameterWWPlus(t); }); register_telegram_type(0xEA, F("UBAParameterWWPlus"), true, [&](std::shared_ptr<const Telegram> t) { process_UBAParameterWWPlus(t); });
// MQTT commands for boiler_cmd topic // MQTT commands for boiler topic
register_mqtt_cmd(F("comfort"), [&](const char * value, const int8_t id) { set_warmwater_mode(value, id); }); register_mqtt_cmd(F("comfort"), [&](const char * value, const int8_t id) { return set_warmwater_mode(value, id); });
register_mqtt_cmd(F("wwactivated"), [&](const char * value, const int8_t id) { set_warmwater_activated(value, id); }); register_mqtt_cmd(F("wwactivated"), [&](const char * value, const int8_t id) { return set_warmwater_activated(value, id); });
register_mqtt_cmd(F("wwtapactivated"), [&](const char * value, const int8_t id) { set_tapwarmwater_activated(value, id); }); register_mqtt_cmd(F("wwtapactivated"), [&](const char * value, const int8_t id) { return set_tapwarmwater_activated(value, id); });
register_mqtt_cmd(F("wwonetime"), [&](const char * value, const int8_t id) { set_warmwater_onetime(value, id); }); register_mqtt_cmd(F("wwonetime"), [&](const char * value, const int8_t id) { return set_warmwater_onetime(value, id); });
register_mqtt_cmd(F("wwcirculation"), [&](const char * value, const int8_t id) { set_warmwater_circulation(value, id); }); register_mqtt_cmd(F("wwcirculation"), [&](const char * value, const int8_t id) { return set_warmwater_circulation(value, id); });
register_mqtt_cmd(F("flowtemp"), [&](const char * value, const int8_t id) { set_flow_temp(value, id); }); register_mqtt_cmd(F("flowtemp"), [&](const char * value, const int8_t id) { return set_flow_temp(value, id); });
register_mqtt_cmd(F("wwtemp"), [&](const char * value, const int8_t id) { set_warmwater_temp(value, id); }); register_mqtt_cmd(F("wwtemp"), [&](const char * value, const int8_t id) { return set_warmwater_temp(value, id); });
register_mqtt_cmd(F("heatingactivated"), [&](const char * value, const int8_t id) { set_heating_activated(value, id); }); register_mqtt_cmd(F("heatingactivated"), [&](const char * value, const int8_t id) { return set_heating_activated(value, id); });
register_mqtt_cmd(F("heatingtemp"), [&](const char * value, const int8_t id) { set_heating_temp(value, id); }); register_mqtt_cmd(F("heatingtemp"), [&](const char * value, const int8_t id) { return set_heating_temp(value, id); });
register_mqtt_cmd(F("burnmaxpower"), [&](const char * value, const int8_t id) { set_max_power(value, id); }); register_mqtt_cmd(F("burnmaxpower"), [&](const char * value, const int8_t id) { return set_max_power(value, id); });
register_mqtt_cmd(F("burnminpower"), [&](const char * value, const int8_t id) { set_min_power(value, id); }); register_mqtt_cmd(F("burnminpower"), [&](const char * value, const int8_t id) { return set_min_power(value, id); });
register_mqtt_cmd(F("boilhyston"), [&](const char * value, const int8_t id) { set_hyst_on(value, id); }); register_mqtt_cmd(F("boilhyston"), [&](const char * value, const int8_t id) { return set_hyst_on(value, id); });
register_mqtt_cmd(F("boilhystoff"), [&](const char * value, const int8_t id) { set_hyst_off(value, id); }); register_mqtt_cmd(F("boilhystoff"), [&](const char * value, const int8_t id) { return set_hyst_off(value, id); });
register_mqtt_cmd(F("burnperiod"), [&](const char * value, const int8_t id) { set_burn_period(value, id); }); register_mqtt_cmd(F("burnperiod"), [&](const char * value, const int8_t id) { return set_burn_period(value, id); });
register_mqtt_cmd(F("pumpdelay"), [&](const char * value, const int8_t id) { set_pump_delay(value, id); }); register_mqtt_cmd(F("pumpdelay"), [&](const char * value, const int8_t id) { return set_pump_delay(value, id); });
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) { EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
mqtt_format_ = settings.mqtt_format; // single, nested or ha mqtt_format_ = settings.mqtt_format; // single, nested or ha
@@ -85,7 +85,6 @@ 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);
}); });
} }
@@ -765,16 +764,11 @@ 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 char * value, const int8_t id) { bool Boiler::set_warmwater_temp(const char * value, const int8_t id) {
int v = 0; int v = 0;
if (!Helpers::value2number(value, v)) { if (!Helpers::value2number(value, v)) {
return; return false;
} }
LOG_INFO(F("Setting boiler warm water temperature to %d C"), v); LOG_INFO(F("Setting boiler warm water temperature to %d C"), v);
@@ -784,24 +778,28 @@ void Boiler::set_warmwater_temp(const char * value, const int8_t id) {
write_command(EMS_TYPE_UBAParameterWW, 2, v, EMS_TYPE_UBAParameterWW); write_command(EMS_TYPE_UBAParameterWW, 2, v, EMS_TYPE_UBAParameterWW);
write_command(EMS_TYPE_UBAFlags, 3, v, EMS_TYPE_UBAParameterWW); // for i9000, see #397 write_command(EMS_TYPE_UBAFlags, 3, v, EMS_TYPE_UBAParameterWW); // for i9000, see #397
} }
return true;
} }
// flow temp // flow temp
void Boiler::set_flow_temp(const char * value, const int8_t id) { bool Boiler::set_flow_temp(const char * value, const int8_t id) {
int v = 0; int v = 0;
if (!Helpers::value2number(value, v)) { if (!Helpers::value2number(value, v)) {
return; return false;
} }
LOG_INFO(F("Setting boiler flow temperature to %d C"), v); LOG_INFO(F("Setting boiler flow temperature to %d C"), v);
write_command(EMS_TYPE_UBASetPoints, 0, v, EMS_TYPE_UBASetPoints); write_command(EMS_TYPE_UBASetPoints, 0, v, EMS_TYPE_UBASetPoints);
return true;
} }
// set min boiler output // set min boiler output
void Boiler::set_heating_activated(const char * value, const int8_t id) { bool Boiler::set_heating_activated(const char * value, const int8_t id) {
bool v = false; bool v = false;
if (!Helpers::value2bool(value, v)) { if (!Helpers::value2bool(value, v)) {
return; return false;
} }
LOG_INFO(F("Setting boiler heating "), v ? "on" : "off"); LOG_INFO(F("Setting boiler heating "), v ? "on" : "off");
if (get_toggle_fetch(EMS_TYPE_UBAParametersPlus)) { if (get_toggle_fetch(EMS_TYPE_UBAParametersPlus)) {
@@ -809,41 +807,49 @@ void Boiler::set_heating_activated(const char * value, const int8_t id) {
} else { } else {
write_command(EMS_TYPE_UBAParameters, 0, v ? 0xFF : 0, EMS_TYPE_UBAParameters); write_command(EMS_TYPE_UBAParameters, 0, v ? 0xFF : 0, EMS_TYPE_UBAParameters);
} }
return true;
} }
// set heating maximum temperature // set heating maximum temperature
void Boiler::set_heating_temp(const char * value, const int8_t id) { bool Boiler::set_heating_temp(const char * value, const int8_t id) {
int v = 0; int v = 0;
if (!Helpers::value2number(value, v)) { if (!Helpers::value2number(value, v)) {
return; return false;
} }
LOG_INFO(F("Setting boiler heating temperature to "), v); LOG_INFO(F("Setting boiler heating temperature to "), v);
if (get_toggle_fetch(EMS_TYPE_UBAParametersPlus)) { if (get_toggle_fetch(EMS_TYPE_UBAParametersPlus)) {
write_command(EMS_TYPE_UBAParametersPlus, 1, v, EMS_TYPE_UBAParametersPlus); write_command(EMS_TYPE_UBAParametersPlus, 1, v, EMS_TYPE_UBAParametersPlus);
} else { } else {
write_command(EMS_TYPE_UBAParameters, 1, v, EMS_TYPE_UBAParameters); write_command(EMS_TYPE_UBAParameters, 1, v, EMS_TYPE_UBAParameters);
} }
return true;
} }
// set min boiler output // set min boiler output
void Boiler::set_min_power(const char * value, const int8_t id) { bool Boiler::set_min_power(const char * value, const int8_t id) {
int v = 0; int v = 0;
if (!Helpers::value2number(value, v)) { if (!Helpers::value2number(value, v)) {
return; return false;
} }
LOG_INFO(F("Setting boiler min power to "), v); LOG_INFO(F("Setting boiler min power to "), v);
if (get_toggle_fetch(EMS_TYPE_UBAParametersPlus)) { if (get_toggle_fetch(EMS_TYPE_UBAParametersPlus)) {
write_command(EMS_TYPE_UBAParametersPlus, 7, v, EMS_TYPE_UBAParametersPlus); write_command(EMS_TYPE_UBAParametersPlus, 7, v, EMS_TYPE_UBAParametersPlus);
} else { } else {
write_command(EMS_TYPE_UBAParameters, 3, v, EMS_TYPE_UBAParameters); write_command(EMS_TYPE_UBAParameters, 3, v, EMS_TYPE_UBAParameters);
} }
return true;
} }
// set max temp // set max temp
void Boiler::set_max_power(const char * value, const int8_t id) { bool Boiler::set_max_power(const char * value, const int8_t id) {
int v = 0; int v = 0;
if (!Helpers::value2number(value, v)) { if (!Helpers::value2number(value, v)) {
return; return false;
} }
LOG_INFO(F("Setting boiler max power to %d C"), v); LOG_INFO(F("Setting boiler max power to %d C"), v);
@@ -852,13 +858,15 @@ void Boiler::set_max_power(const char * value, const int8_t id) {
} else { } else {
write_command(EMS_TYPE_UBAParameters, 2, v, EMS_TYPE_UBAParameters); write_command(EMS_TYPE_UBAParameters, 2, v, EMS_TYPE_UBAParameters);
} }
return true;
} }
// set boiler on hysteresis // set boiler on hysteresis
void Boiler::set_hyst_on(const char * value, const int8_t id) { bool Boiler::set_hyst_on(const char * value, const int8_t id) {
int v = 0; int v = 0;
if (!Helpers::value2number(value, v)) { if (!Helpers::value2number(value, v)) {
return; return false;
} }
LOG_INFO(F("Setting boiler hysteresis on to %d C"), v); LOG_INFO(F("Setting boiler hysteresis on to %d C"), v);
@@ -867,13 +875,15 @@ void Boiler::set_hyst_on(const char * value, const int8_t id) {
} else { } else {
write_command(EMS_TYPE_UBAParameters, 5, v, EMS_TYPE_UBAParameters); write_command(EMS_TYPE_UBAParameters, 5, v, EMS_TYPE_UBAParameters);
} }
return true;
} }
// set boiler off hysteresis // set boiler off hysteresis
void Boiler::set_hyst_off(const char * value, const int8_t id) { bool Boiler::set_hyst_off(const char * value, const int8_t id) {
int v = 0; int v = 0;
if (!Helpers::value2number(value, v)) { if (!Helpers::value2number(value, v)) {
return; return false;
} }
LOG_INFO(F("Setting boiler hysteresis off to %d C"), v); LOG_INFO(F("Setting boiler hysteresis off to %d C"), v);
@@ -882,13 +892,15 @@ void Boiler::set_hyst_off(const char * value, const int8_t id) {
} else { } else {
write_command(EMS_TYPE_UBAParameters, 4, v, EMS_TYPE_UBAParameters); write_command(EMS_TYPE_UBAParameters, 4, v, EMS_TYPE_UBAParameters);
} }
return true;
} }
// set min burner period // set min burner period
void Boiler::set_burn_period(const char * value, const int8_t id) { bool Boiler::set_burn_period(const char * value, const int8_t id) {
int v = 0; int v = 0;
if (!Helpers::value2number(value, v)) { if (!Helpers::value2number(value, v)) {
return; return false;
} }
LOG_INFO(F("Setting burner min. period to %d min"), v); LOG_INFO(F("Setting burner min. period to %d min"), v);
@@ -897,30 +909,36 @@ void Boiler::set_burn_period(const char * value, const int8_t id) {
} else { } else {
write_command(EMS_TYPE_UBAParameters, 6, v, EMS_TYPE_UBAParameters); write_command(EMS_TYPE_UBAParameters, 6, v, EMS_TYPE_UBAParameters);
} }
return true;
} }
// set pump delay // set pump delay
void Boiler::set_pump_delay(const char * value, const int8_t id) { bool Boiler::set_pump_delay(const char * value, const int8_t id) {
int v = 0; int v = 0;
if (!Helpers::value2number(value, v)) { if (!Helpers::value2number(value, v)) {
return; return false;
} }
if (get_toggle_fetch(EMS_TYPE_UBAParameters)) { if (get_toggle_fetch(EMS_TYPE_UBAParameters)) {
LOG_INFO(F("Setting boiler pump delay to %d min"), v); LOG_INFO(F("Setting boiler pump delay to %d min"), v);
write_command(EMS_TYPE_UBAParameters, 8, v, EMS_TYPE_UBAParameters); write_command(EMS_TYPE_UBAParameters, 8, v, EMS_TYPE_UBAParameters);
} }
return true;
} }
// 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) 1=hot, 2=eco, 3=intelligent // on a RC35 it's by EMSESP::send_write_request(0x37, 0x10, 2, &set, 1, 0); (set is 1,2,3) 1=hot, 2=eco, 3=intelligent
void Boiler::set_warmwater_mode(const char * value, const int8_t id) { bool Boiler::set_warmwater_mode(const char * value, const int8_t id) {
if (value == nullptr) { if (value == nullptr) {
return; return false;
} }
if (!get_toggle_fetch(EMS_TYPE_UBAParameterWW)) { if (!get_toggle_fetch(EMS_TYPE_UBAParameterWW)) {
return; return false;
} }
uint8_t set; uint8_t set;
if (strcmp(value, "hot") == 0) { if (strcmp(value, "hot") == 0) {
LOG_INFO(F("Setting boiler warm water to Hot")); LOG_INFO(F("Setting boiler warm water to Hot"));
@@ -932,16 +950,18 @@ void Boiler::set_warmwater_mode(const char * value, const int8_t id) {
LOG_INFO(F("Setting boiler warm water to Intelligent")); LOG_INFO(F("Setting boiler warm water to Intelligent"));
set = 0xEC; set = 0xEC;
} else { } else {
return; // do nothing return false; // do nothing
} }
write_command(EMS_TYPE_UBAParameterWW, 9, set, EMS_TYPE_UBAParameterWW); write_command(EMS_TYPE_UBAParameterWW, 9, set, EMS_TYPE_UBAParameterWW);
return true;
} }
// turn on/off warm water // turn on/off warm water
void Boiler::set_warmwater_activated(const char * value, const int8_t id) { bool Boiler::set_warmwater_activated(const char * value, const int8_t id) {
bool v = false; bool v = false;
if (!Helpers::value2bool(value, v)) { if (!Helpers::value2bool(value, v)) {
return; return false;
} }
LOG_INFO(F("Setting boiler warm water %s"), v ? "on" : "off"); LOG_INFO(F("Setting boiler warm water %s"), v ? "on" : "off");
@@ -953,19 +973,22 @@ void Boiler::set_warmwater_activated(const char * value, const int8_t id) {
} else { } else {
n = (v ? 0xFF : 0x00); // 0xFF is on, 0x00 is off n = (v ? 0xFF : 0x00); // 0xFF is on, 0x00 is off
} }
if (get_toggle_fetch(EMS_TYPE_UBAParameterWWPlus)) { if (get_toggle_fetch(EMS_TYPE_UBAParameterWWPlus)) {
write_command(EMS_TYPE_UBAParameterWWPlus, 1, v ? 1 : 0, EMS_TYPE_UBAParameterWWPlus); write_command(EMS_TYPE_UBAParameterWWPlus, 1, v ? 1 : 0, EMS_TYPE_UBAParameterWWPlus);
} else { } else {
write_command(EMS_TYPE_UBAParameterWW, 1, n, EMS_TYPE_UBAParameterWW); write_command(EMS_TYPE_UBAParameterWW, 1, n, EMS_TYPE_UBAParameterWW);
} }
return true;
} }
// Activate / De-activate the Warm Tap Water // Activate / De-activate the Warm Tap Water
// 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 char * value, const int8_t id) { bool Boiler::set_tapwarmwater_activated(const char * value, const int8_t id) {
bool v = false; bool v = false;
if (!Helpers::value2bool(value, v)) { if (!Helpers::value2bool(value, v)) {
return; return false;
} }
LOG_INFO(F("Setting tap warm tap water %s"), v ? "on" : "off"); LOG_INFO(F("Setting tap warm tap water %s"), v ? "on" : "off");
@@ -990,15 +1013,17 @@ void Boiler::set_tapwarmwater_activated(const char * value, const int8_t id) {
} }
write_command(EMS_TYPE_UBAFunctionTest, 0, message_data, sizeof(message_data), 0); write_command(EMS_TYPE_UBAFunctionTest, 0, message_data, sizeof(message_data), 0);
return true;
} }
// 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 char * value, const int8_t id) { bool Boiler::set_warmwater_onetime(const char * value, const int8_t id) {
bool v = false; bool v = false;
if (!Helpers::value2bool(value, v)) { if (!Helpers::value2bool(value, v)) {
return; return false;
} }
LOG_INFO(F("Setting boiler warm water OneTime loading %s"), v ? "on" : "off"); LOG_INFO(F("Setting boiler warm water OneTime loading %s"), v ? "on" : "off");
@@ -1007,14 +1032,16 @@ void Boiler::set_warmwater_onetime(const char * value, const int8_t id) {
} else { } else {
write_command(EMS_TYPE_UBAFlags, 0, (v ? 0x22 : 0x02), 0x34); write_command(EMS_TYPE_UBAFlags, 0, (v ? 0x22 : 0x02), 0x34);
} }
return true;
} }
// 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 char * value, const int8_t id) { bool Boiler::set_warmwater_circulation(const char * value, const int8_t id) {
bool v = false; bool v = false;
if (!Helpers::value2bool(value, v)) { if (!Helpers::value2bool(value, v)) {
return; return false;
} }
LOG_INFO(F("Setting boiler warm water circulation %s"), v ? "on" : "off"); LOG_INFO(F("Setting boiler warm water circulation %s"), v ? "on" : "off");
@@ -1023,6 +1050,8 @@ void Boiler::set_warmwater_circulation(const char * value, const int8_t id) {
} else { } else {
write_command(EMS_TYPE_UBAFlags, 1, (v ? 0x22 : 0x02), 0x34); write_command(EMS_TYPE_UBAFlags, 1, (v ? 0x22 : 0x02), 0x34);
} }
return true;
} }
// add console commands // add console commands
@@ -1034,7 +1063,7 @@ void Boiler::console_commands(Shell & shell, unsigned int context) {
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) { [=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
uint16_t type_id = Helpers::hextoint(arguments.front().c_str()); uint16_t type_id = Helpers::hextoint(arguments.front().c_str());
EMSESP::set_read_id(type_id); EMSESP::set_read_id(type_id);
EMSESP::send_read_request(type_id, get_device_id()); EMSESP::send_read_request(type_id, device_id());
}); });
EMSESPShell::commands->add_command(ShellContext::BOILER, EMSESPShell::commands->add_command(ShellContext::BOILER,

View File

@@ -167,21 +167,21 @@ class Boiler : public EMSdevice {
void process_UBADHWStatus(std::shared_ptr<const Telegram> telegram); void process_UBADHWStatus(std::shared_ptr<const Telegram> telegram);
// commands - none of these use the additional id parameter // commands - none of these use the additional id parameter
void set_warmwater_mode(const char * value, const int8_t id); bool set_warmwater_mode(const char * value, const int8_t id);
void set_warmwater_activated(const char * value, const int8_t id); bool set_warmwater_activated(const char * value, const int8_t id);
void set_tapwarmwater_activated(const char * value, const int8_t id); bool set_tapwarmwater_activated(const char * value, const int8_t id);
void set_warmwater_onetime(const char * value, const int8_t id); bool set_warmwater_onetime(const char * value, const int8_t id);
void set_warmwater_circulation(const char * value, const int8_t id); bool set_warmwater_circulation(const char * value, const int8_t id);
void set_warmwater_temp(const char * value, const int8_t id); bool set_warmwater_temp(const char * value, const int8_t id);
void set_flow_temp(const char * value, const int8_t id); bool set_flow_temp(const char * value, const int8_t id);
void set_heating_activated(const char * value, const int8_t id); bool set_heating_activated(const char * value, const int8_t id);
void set_heating_temp(const char * value, const int8_t id); bool set_heating_temp(const char * value, const int8_t id);
void set_min_power(const char * value, const int8_t id); bool set_min_power(const char * value, const int8_t id);
void set_max_power(const char * value, const int8_t id); bool set_max_power(const char * value, const int8_t id);
void set_hyst_on(const char * value, const int8_t id); bool set_hyst_on(const char * value, const int8_t id);
void set_hyst_off(const char * value, const int8_t id); bool set_hyst_off(const char * value, const int8_t id);
void set_burn_period(const char * value, const int8_t id); bool set_burn_period(const char * value, const int8_t id);
void set_pump_delay(const char * value, const int8_t id); bool set_pump_delay(const char * value, const int8_t id);
}; };
} // namespace emsesp } // namespace emsesp

View File

@@ -158,7 +158,7 @@ void Mixing::publish_values() {
char topic[30]; char topic[30];
strlcpy(topic, "mixing_data", 30); strlcpy(topic, "mixing_data", 30);
strlcat(topic, Helpers::itoa(s, get_device_id() - 0x20 + 1), 30); // append hc to topic strlcat(topic, Helpers::itoa(s, device_id() - 0x20 + 1), 30); // append hc to topic
Mqtt::publish(topic, doc); Mqtt::publish(topic, doc);
} }
@@ -190,7 +190,7 @@ void Mixing::process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> tel
// A1 00 FF 00 00 0C 02 04 00 01 1D 00 82 // A1 00 FF 00 00 0C 02 04 00 01 1D 00 82
void Mixing::process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram) { void Mixing::process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram) {
type_ = Type::HC; type_ = Type::HC;
hc_ = get_device_id() - 0x20 + 1; hc_ = device_id() - 0x20 + 1;
uint8_t ismixed = 0; uint8_t ismixed = 0;
changed_ |= telegram->read_value(ismixed, 0); // check if circuit is active, 0-off, 1-unmixed, 2-mixed changed_ |= telegram->read_value(ismixed, 0); // check if circuit is active, 0-off, 1-unmixed, 2-mixed
if (ismixed == 0) { if (ismixed == 0) {
@@ -213,7 +213,7 @@ void Mixing::process_MMStatusMessage(std::shared_ptr<const Telegram> telegram) {
// the heating circuit is determine by which device_id it is, 0x20 - 0x23 // the heating circuit is determine by which device_id it is, 0x20 - 0x23
// 0x21 is position 2. 0x20 is typically reserved for the WM10 switch module // 0x21 is position 2. 0x20 is typically reserved for the WM10 switch module
// see https://github.com/proddy/EMS-ESP/issues/270 and https://github.com/proddy/EMS-ESP/issues/386#issuecomment-629610918 // see https://github.com/proddy/EMS-ESP/issues/270 and https://github.com/proddy/EMS-ESP/issues/386#issuecomment-629610918
hc_ = get_device_id() - 0x20 + 1; hc_ = device_id() - 0x20 + 1;
changed_ |= telegram->read_value(flowTemp_, 1); // is * 10 changed_ |= telegram->read_value(flowTemp_, 1); // is * 10
changed_ |= telegram->read_value(pump_, 3); changed_ |= telegram->read_value(pump_, 3);
changed_ |= telegram->read_value(flowSetTemp_, 0); changed_ |= telegram->read_value(flowSetTemp_, 0);
@@ -226,7 +226,7 @@ void Mixing::process_MMStatusMessage(std::shared_ptr<const Telegram> telegram) {
// Mixing on a MM10 - 0xAA // Mixing on a MM10 - 0xAA
// e.g. Thermostat -> Mixing Module, type 0xAA, telegram: 10 21 AA 00 FF 0C 0A 11 0A 32 xx // e.g. Thermostat -> Mixing Module, type 0xAA, telegram: 10 21 AA 00 FF 0C 0A 11 0A 32 xx
void Mixing::process_MMConfigMessage(std::shared_ptr<const Telegram> telegram) { void Mixing::process_MMConfigMessage(std::shared_ptr<const Telegram> telegram) {
hc_ = get_device_id() - 0x20 + 1; hc_ = device_id() - 0x20 + 1;
// pos 0: active FF = on // pos 0: active FF = on
// pos 1: valve runtime 0C = 120 sec in units of 10 sec // pos 1: valve runtime 0C = 120 sec in units of 10 sec
} }
@@ -234,7 +234,7 @@ void Mixing::process_MMConfigMessage(std::shared_ptr<const Telegram> telegram) {
// Mixing on a MM10 - 0xAC // Mixing on a MM10 - 0xAC
// e.g. Thermostat -> Mixing Module, type 0xAC, telegram: 10 21 AC 00 1E 64 01 AB // e.g. Thermostat -> Mixing Module, type 0xAC, telegram: 10 21 AC 00 1E 64 01 AB
void Mixing::process_MMSetMessage(std::shared_ptr<const Telegram> telegram) { void Mixing::process_MMSetMessage(std::shared_ptr<const Telegram> telegram) {
hc_ = get_device_id() - 0x20 + 1; hc_ = device_id() - 0x20 + 1;
// pos 0: flowtemp setpoint 1E = 30°C // pos 0: flowtemp setpoint 1E = 30°C
// pos 1: position in % // pos 1: position in %
} }

View File

@@ -222,7 +222,7 @@ void Thermostat::device_info_web(JsonArray & root) {
// context menu "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 // only add it once, to prevent conflicts when there are multiple thermostats
if (this->get_device_id() != EMSESP::actual_master_thermostat()) { if (this->device_id() != EMSESP::actual_master_thermostat()) {
return; return;
} }
@@ -231,7 +231,6 @@ 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);
}); });
} }
@@ -239,7 +238,7 @@ void Thermostat::add_context_menu() {
// 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() {
// only publish on the master thermostat // only publish on the master thermostat
if (EMSESP::actual_master_thermostat() != this->get_device_id()) { if (EMSESP::actual_master_thermostat() != this->device_id()) {
return false; return false;
} }
if (changed_) { if (changed_) {
@@ -252,7 +251,7 @@ bool Thermostat::updated_values() {
// publish values via MQTT // publish values via MQTT
void Thermostat::publish_values() { void Thermostat::publish_values() {
// only publish on the master thermostat // only publish on the master thermostat
if (EMSESP::actual_master_thermostat() != this->get_device_id()) { if (EMSESP::actual_master_thermostat() != this->device_id()) {
return; return;
} }
@@ -1191,7 +1190,7 @@ void Thermostat::console_commands(Shell & shell, unsigned int context) {
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) { [=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
uint16_t type_id = Helpers::hextoint(arguments.front().c_str()); uint16_t type_id = Helpers::hextoint(arguments.front().c_str());
EMSESP::set_read_id(type_id); EMSESP::set_read_id(type_id);
EMSESP::send_read_request(type_id, this->get_device_id()); EMSESP::send_read_request(type_id, this->device_id());
}); });
EMSESPShell::commands->add_command(ShellContext::THERMOSTAT, EMSESPShell::commands->add_command(ShellContext::THERMOSTAT,
@@ -1217,50 +1216,61 @@ void Thermostat::console_commands(Shell & shell, unsigned int context) {
} }
// 0xA5 - Set minimum external temperature // 0xA5 - Set minimum external temperature
void Thermostat::set_minexttemp(const char * value, const int8_t id) { bool Thermostat::set_minexttemp(const char * value, const int8_t id) {
int mt = 0; int mt = 0;
if (!Helpers::value2number(value, mt)) { if (!Helpers::value2number(value, mt)) {
return; return false;
} }
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, EMS_TYPE_IBASettings); write_command(EMS_TYPE_IBASettings, 5, mt, EMS_TYPE_IBASettings);
return true;
} }
// 0xA5 - Clock offset // 0xA5 - Clock offset
void Thermostat::set_clockoffset(const char * value, const int8_t id) { bool Thermostat::set_clockoffset(const char * value, const int8_t id) {
int co = 0; int co = 0;
if (!Helpers::value2number(value, co)) { if (!Helpers::value2number(value, co)) {
return; return false;
} }
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, EMS_TYPE_IBASettings); write_command(EMS_TYPE_IBASettings, 12, co, EMS_TYPE_IBASettings);
return true;
} }
// 0xA5 - Calibrate internal temperature // 0xA5 - Calibrate internal temperature
void Thermostat::set_calinttemp(const char * value, const int8_t id) { bool Thermostat::set_calinttemp(const char * value, const int8_t id) {
int ct = 0; int ct = 0;
if (!Helpers::value2number(value, ct)) { if (!Helpers::value2number(value, ct)) {
return; return false;
} }
// does this value need to be multiple by 10?
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, EMS_TYPE_IBASettings); write_command(EMS_TYPE_IBASettings, 2, ct, EMS_TYPE_IBASettings);
return true;
} }
// 0xA5 - Set the display settings // 0xA5 - Set the display settings
void Thermostat::set_display(const char * value, const int8_t id) { bool Thermostat::set_display(const char * value, const int8_t id) {
int ds = 0; int ds = 0;
if (!Helpers::value2number(value, ds)) { if (!Helpers::value2number(value, ds)) {
return; return false;
} }
LOG_INFO(F("Setting display to %d"), ds); LOG_INFO(F("Setting display to %d"), ds);
write_command(EMS_TYPE_IBASettings, 0, ds, EMS_TYPE_IBASettings); write_command(EMS_TYPE_IBASettings, 0, ds, EMS_TYPE_IBASettings);
return true;
} }
void Thermostat::set_remotetemp(const char * value, const int8_t id) { bool Thermostat::set_remotetemp(const char * value, const int8_t id) {
float f = 0; float f = 0;
if (!Helpers::value2float(value, f)) { if (!Helpers::value2float(value, f)) {
return; return false;
} }
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id;
@@ -1270,13 +1280,15 @@ void Thermostat::set_remotetemp(const char * value, const int8_t id) {
} else { } else {
Roomctrl::set_remotetemp(hc_num - 1, (int16_t)(f * 10)); Roomctrl::set_remotetemp(hc_num - 1, (int16_t)(f * 10));
} }
return true;
} }
// 0xA5 - Set the building settings // 0xA5 - Set the building settings
void Thermostat::set_building(const char * value, const int8_t id) { bool Thermostat::set_building(const char * value, const int8_t id) {
std::string bd(20, '\0'); std::string bd(20, '\0');
if (!Helpers::value2string(value, bd)) { if (!Helpers::value2string(value, bd)) {
return; return false;
} }
uint8_t bg = 0; uint8_t bg = 0;
@@ -1287,28 +1299,33 @@ void Thermostat::set_building(const char * value, const int8_t id) {
} else if (bd == "heavy") { } else if (bd == "heavy") {
bg = 2; bg = 2;
} else { } else {
return; // invalid return false; // invalid
} }
LOG_INFO(F("Setting building to %d"), bg); LOG_INFO(F("Setting building to %d"), bg);
write_command(EMS_TYPE_IBASettings, 6, bg, EMS_TYPE_IBASettings); write_command(EMS_TYPE_IBASettings, 6, bg, EMS_TYPE_IBASettings);
return true;
} }
// 0xA5 Set the language settings // 0xA5 Set the language settings
void Thermostat::set_language(const char * value, const int8_t id) { bool Thermostat::set_language(const char * value, const int8_t id) {
int lg = 0; int lg = 0;
if (!Helpers::value2number(value, lg)) { if (!Helpers::value2number(value, lg)) {
return; return false;
} }
LOG_INFO(F("Setting language to %d"), lg); LOG_INFO(F("Setting language to %d"), lg);
write_command(EMS_TYPE_IBASettings, 1, lg, EMS_TYPE_IBASettings); write_command(EMS_TYPE_IBASettings, 1, lg, EMS_TYPE_IBASettings);
return true;
} }
// 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 char * value, const int8_t id) { bool Thermostat::set_control(const char * value, const int8_t id) {
int ctrl = 0; int ctrl = 0;
if (!Helpers::value2number(value, ctrl)) { if (!Helpers::value2number(value, ctrl)) {
return; return false;
} }
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id;
@@ -1316,23 +1333,25 @@ void Thermostat::set_control(const char * value, const int8_t 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 false;
} }
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 false;
} }
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);
return true;
} }
// 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_wwmode(const char * value, const int8_t id) { bool Thermostat::set_wwmode(const char * value, const int8_t id) {
std::string v(10, '\0'); std::string v(10, '\0');
if (!Helpers::value2string(value, v)) { if (!Helpers::value2string(value, v)) {
return; return false;
} }
uint8_t set = 0xFF; // some dummy value uint8_t set = 0xFF; // some dummy value
@@ -1350,21 +1369,24 @@ void Thermostat::set_wwmode(const char * value, const int8_t id) {
} else { } else {
LOG_WARNING(F("Set thermostat warm water mode: Invalid mode: %s"), v.c_str()); LOG_WARNING(F("Set thermostat warm water mode: Invalid mode: %s"), v.c_str());
} }
return true;
} }
// 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 * value, const int8_t id) { bool Thermostat::set_holiday(const char * value, const int8_t id) {
std::string hd(30, '\0'); std::string hd(30, '\0');
if (!Helpers::value2string(value, hd)) { if (!Helpers::value2string(value, hd)) {
return; return false;
} }
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; uint8_t hc_num = (id == -1) ? AUTO_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, this->get_device_id()); LOG_WARNING(F("Set holiday: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, this->device_id());
return; return false;
} }
uint8_t data[6]; uint8_t data[6];
data[0] = (hd[0] - '0') * 10 + (hd[1] - '0'); data[0] = (hd[0] - '0') * 10 + (hd[1] - '0');
data[1] = (hd[3] - '0') * 10 + (hd[4] - '0'); data[1] = (hd[3] - '0') * 10 + (hd[4] - '0');
@@ -1375,49 +1397,56 @@ void Thermostat::set_holiday(const char * value, const int8_t id) {
LOG_INFO(F("Setting holiday for hc %d"), hc->hc_num()); LOG_INFO(F("Setting holiday for hc %d"), hc->hc_num());
write_command(timer_typeids[hc->hc_num() - 1], 87, data, 6, 0); write_command(timer_typeids[hc->hc_num() - 1], 87, data, 6, 0);
return true;
} }
// set pause in hours // set pause in hours
void Thermostat::set_pause(const char * value, const int8_t id) { bool Thermostat::set_pause(const char * value, const int8_t id) {
int hrs = 0; int hrs = 0;
if (!Helpers::value2number(value, hrs)) { if (!Helpers::value2number(value, hrs)) {
return; return false;
} }
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; uint8_t hc_num = (id == -1) ? AUTO_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, this->get_device_id()); LOG_WARNING(F("Set pause: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, this->device_id());
return; return false;
} }
LOG_INFO(F("Setting pause: %d hours, hc: %d"), hrs, hc->hc_num()); LOG_INFO(F("Setting pause: %d hours, hc: %d"), hrs, hc->hc_num());
write_command(timer_typeids[hc->hc_num() - 1], 85, hrs); write_command(timer_typeids[hc->hc_num() - 1], 85, hrs);
return true;
} }
// set partymode in hours // set partymode in hours
void Thermostat::set_party(const char * value, const int8_t id) { bool Thermostat::set_party(const char * value, const int8_t id) {
int hrs = 0; int hrs = 0;
if (!Helpers::value2number(value, hrs)) { if (!Helpers::value2number(value, hrs)) {
return; return false;
} }
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; uint8_t hc_num = (id == -1) ? AUTO_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, this->get_device_id()); LOG_WARNING(F("Set party: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, this->device_id());
return; return false;
} }
LOG_INFO(F("Setting party: %d hours, hc: %d"), hrs, hc->hc_num()); LOG_INFO(F("Setting party: %d hours, hc: %d"), hrs, hc->hc_num());
write_command(timer_typeids[hc->hc_num() - 1], 86, hrs); write_command(timer_typeids[hc->hc_num() - 1], 86, hrs);
return true;
} }
// 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)
// id is ignored // id is ignored
void Thermostat::set_datetime(const char * value, const int8_t id) { bool Thermostat::set_datetime(const char * value, const int8_t id) {
std::string dt(30, '\0'); std::string dt(30, '\0');
if (!Helpers::value2string(value, dt)) { if (!Helpers::value2string(value, dt)) {
return; return false;
} }
uint8_t data[9]; uint8_t data[9];
@@ -1426,8 +1455,9 @@ void Thermostat::set_datetime(const char * value, const int8_t id) {
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 false;
} }
data[0] = tm_->tm_year - 100; // Bosch counts from 2000 data[0] = tm_->tm_year - 100; // Bosch counts from 2000
data[1] = tm_->tm_mon; data[1] = tm_->tm_mon;
data[2] = tm_->tm_hour; data[2] = tm_->tm_hour;
@@ -1449,53 +1479,65 @@ void Thermostat::set_datetime(const char * value, const int8_t id) {
data[6] = (dt[20] - '0'); // day of week data[6] = (dt[20] - '0'); // day of week
data[7] = (dt[22] - '0') + 2; // DST and flag data[7] = (dt[22] - '0') + 2; // DST and flag
} }
LOG_INFO(F("Setting date and time")); LOG_INFO(F("Setting date and time"));
write_command(EMS_TYPE_time, 0, data, 8, EMS_TYPE_time); write_command(EMS_TYPE_time, 0, data, 8, EMS_TYPE_time);
return true;
} }
// sets the thermostat working mode, where mode is a string // sets the thermostat working mode, where mode is a string
// converts string mode to HeatingCircuit::Mode // converts string mode to HeatingCircuit::Mode
void Thermostat::set_mode(const char * value, const int8_t id) { bool Thermostat::set_mode(const char * value, const int8_t id) {
std::string mode(10, '\0'); std::string mode(10, '\0');
if (!Helpers::value2string(value, mode)) { if (!Helpers::value2string(value, mode)) {
return; return false;
} }
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id;
if (mode_tostring(HeatingCircuit::Mode::OFF) == mode) { if (mode_tostring(HeatingCircuit::Mode::OFF) == mode) {
set_mode_n(HeatingCircuit::Mode::OFF, hc_num); return set_mode_n(HeatingCircuit::Mode::OFF, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::MANUAL) == mode) {
set_mode_n(HeatingCircuit::Mode::MANUAL, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::AUTO) == mode) {
set_mode_n(HeatingCircuit::Mode::AUTO, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::DAY) == mode) {
set_mode_n(HeatingCircuit::Mode::DAY, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::NIGHT) == mode) {
set_mode_n(HeatingCircuit::Mode::NIGHT, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::HEAT) == mode) {
set_mode_n(HeatingCircuit::Mode::HEAT, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::NOFROST) == mode) {
set_mode_n(HeatingCircuit::Mode::NOFROST, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::ECO) == mode) {
set_mode_n(HeatingCircuit::Mode::ECO, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::HOLIDAY) == mode) {
set_mode_n(HeatingCircuit::Mode::HOLIDAY, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::COMFORT) == mode) {
set_mode_n(HeatingCircuit::Mode::COMFORT, hc_num);
} else {
LOG_WARNING(F("Invalid mode %s. Cannot set"), mode.c_str());
} }
if (mode_tostring(HeatingCircuit::Mode::MANUAL) == mode) {
return set_mode_n(HeatingCircuit::Mode::MANUAL, hc_num);
}
if (mode_tostring(HeatingCircuit::Mode::AUTO) == mode) {
return set_mode_n(HeatingCircuit::Mode::AUTO, hc_num);
}
if (mode_tostring(HeatingCircuit::Mode::DAY) == mode) {
return set_mode_n(HeatingCircuit::Mode::DAY, hc_num);
}
if (mode_tostring(HeatingCircuit::Mode::NIGHT) == mode) {
return set_mode_n(HeatingCircuit::Mode::NIGHT, hc_num);
}
if (mode_tostring(HeatingCircuit::Mode::HEAT) == mode) {
return set_mode_n(HeatingCircuit::Mode::HEAT, hc_num);
}
if (mode_tostring(HeatingCircuit::Mode::NOFROST) == mode) {
return set_mode_n(HeatingCircuit::Mode::NOFROST, hc_num);
}
if (mode_tostring(HeatingCircuit::Mode::ECO) == mode) {
return set_mode_n(HeatingCircuit::Mode::ECO, hc_num);
}
if (mode_tostring(HeatingCircuit::Mode::HOLIDAY) == mode) {
return set_mode_n(HeatingCircuit::Mode::HOLIDAY, hc_num);
}
if (mode_tostring(HeatingCircuit::Mode::COMFORT) == mode) {
return set_mode_n(HeatingCircuit::Mode::COMFORT, hc_num);
}
LOG_WARNING(F("Invalid mode %s. Cannot set"), mode.c_str());
return false;
} }
// Set the thermostat working mode // Set the thermostat working mode
// mode is HeatingCircuit::Mode // mode is HeatingCircuit::Mode
void Thermostat::set_mode_n(const uint8_t mode, const uint8_t hc_num) { bool Thermostat::set_mode_n(const uint8_t mode, const uint8_t hc_num) {
// get hc based on number // get hc based on number
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 mode: Heating Circuit %d not found or activated"), hc_num); LOG_WARNING(F("set mode: Heating Circuit %d not found or activated"), hc_num);
return; return false;
} }
uint8_t set_mode_value, offset; uint8_t set_mode_value, offset;
@@ -1578,47 +1620,61 @@ void Thermostat::set_mode_n(const uint8_t mode, const uint8_t hc_num) {
// add the write command to the Tx queue // add the write command to the Tx queue
// post validate is the corresponding monitor or set type IDs as they can differ per model // post validate is the corresponding monitor or set type IDs as they can differ per model
write_command(set_typeids[hc->hc_num() - 1], offset, set_mode_value, validate_typeid); write_command(set_typeids[hc->hc_num() - 1], offset, set_mode_value, validate_typeid);
return true;
} }
// sets the thermostat temp, where mode is a string // sets the thermostat temp, where mode is a string
void Thermostat::set_temperature(const float temperature, const std::string & mode, const uint8_t hc_num) { bool Thermostat::set_temperature(const float temperature, const std::string & mode, const uint8_t hc_num) {
if (mode_tostring(HeatingCircuit::Mode::MANUAL) == mode) { if (mode_tostring(HeatingCircuit::Mode::MANUAL) == mode) {
set_temperature(temperature, HeatingCircuit::Mode::MANUAL, hc_num); return set_temperature(temperature, HeatingCircuit::Mode::MANUAL, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::AUTO) == mode) {
set_temperature(temperature, HeatingCircuit::Mode::AUTO, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::DAY) == mode) {
set_temperature(temperature, HeatingCircuit::Mode::DAY, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::NIGHT) == mode) {
set_temperature(temperature, HeatingCircuit::Mode::NIGHT, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::COMFORT) == mode) {
set_temperature(temperature, HeatingCircuit::Mode::COMFORT, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::HEAT) == mode) {
set_temperature(temperature, HeatingCircuit::Mode::HEAT, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::ECO) == mode) {
set_temperature(temperature, HeatingCircuit::Mode::ECO, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::NOFROST) == mode) {
set_temperature(temperature, HeatingCircuit::Mode::NOFROST, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::SUMMER) == mode) {
set_temperature(temperature, HeatingCircuit::Mode::SUMMER, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::HOLIDAY) == mode) {
set_temperature(temperature, HeatingCircuit::Mode::HOLIDAY, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::OFFSET) == mode) {
set_temperature(temperature, HeatingCircuit::Mode::OFFSET, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::DESIGN) == mode) {
set_temperature(temperature, HeatingCircuit::Mode::DESIGN, hc_num);
} else {
LOG_WARNING(F("Invalid mode %s."), mode.c_str());
} }
if (mode_tostring(HeatingCircuit::Mode::AUTO) == mode) {
return set_temperature(temperature, HeatingCircuit::Mode::AUTO, hc_num);
}
if (mode_tostring(HeatingCircuit::Mode::DAY) == mode) {
return set_temperature(temperature, HeatingCircuit::Mode::DAY, hc_num);
}
if (mode_tostring(HeatingCircuit::Mode::NIGHT) == mode) {
return set_temperature(temperature, HeatingCircuit::Mode::NIGHT, hc_num);
}
if (mode_tostring(HeatingCircuit::Mode::COMFORT) == mode) {
return set_temperature(temperature, HeatingCircuit::Mode::COMFORT, hc_num);
}
if (mode_tostring(HeatingCircuit::Mode::HEAT) == mode) {
return set_temperature(temperature, HeatingCircuit::Mode::HEAT, hc_num);
}
if (mode_tostring(HeatingCircuit::Mode::ECO) == mode) {
return set_temperature(temperature, HeatingCircuit::Mode::ECO, hc_num);
}
if (mode_tostring(HeatingCircuit::Mode::NOFROST) == mode) {
return set_temperature(temperature, HeatingCircuit::Mode::NOFROST, hc_num);
}
if (mode_tostring(HeatingCircuit::Mode::SUMMER) == mode) {
return set_temperature(temperature, HeatingCircuit::Mode::SUMMER, hc_num);
}
if (mode_tostring(HeatingCircuit::Mode::HOLIDAY) == mode) {
return set_temperature(temperature, HeatingCircuit::Mode::HOLIDAY, hc_num);
}
if (mode_tostring(HeatingCircuit::Mode::OFFSET) == mode) {
return set_temperature(temperature, HeatingCircuit::Mode::OFFSET, hc_num);
}
if (mode_tostring(HeatingCircuit::Mode::DESIGN) == mode) {
return set_temperature(temperature, HeatingCircuit::Mode::DESIGN, hc_num);
}
LOG_WARNING(F("Invalid mode %s."), mode.c_str());
return false;
} }
// Set the temperature of the thermostat // Set the temperature of the thermostat
// the id passed into this function is the heating circuit number // the id passed into this function is the heating circuit number
void Thermostat::set_temperature(const float temperature, const uint8_t mode, const uint8_t hc_num) { bool Thermostat::set_temperature(const float temperature, const uint8_t mode, const uint8_t hc_num) {
// get hc based on number // get hc based on number
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 temperature: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, this->get_device_id()); LOG_WARNING(F("Set temperature: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, this->device_id());
return; return false;
} }
uint8_t model = this->model(); uint8_t model = this->model();
@@ -1790,75 +1846,80 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co
// add the write command to the Tx queue. value is *2 // add the write command to the Tx queue. value is *2
// post validate is the corresponding monitor or set type IDs as they can differ per model // post validate is the corresponding monitor or set type IDs as they can differ per model
write_command(set_typeids[hc->hc_num() - 1], offset, (uint8_t)((float)temperature * (float)factor), validate_typeid); write_command(set_typeids[hc->hc_num() - 1], offset, (uint8_t)((float)temperature * (float)factor), validate_typeid);
return true;
} }
return false;
} }
// for HA specifically when receiving over MQTT // for HA specifically when receiving over MQTT
void Thermostat::thermostat_cmd_temp(const char * message) { bool Thermostat::thermostat_cmd_temp(const char * message) {
float f = strtof((char *)message, 0); float f = strtof((char *)message, 0);
set_temperature(f, HeatingCircuit::Mode::AUTO, AUTO_HEATING_CIRCUIT); return set_temperature(f, HeatingCircuit::Mode::AUTO, AUTO_HEATING_CIRCUIT);
} }
// for HA specifically when receiving over MQTT // for HA specifically when receiving over MQTT
// message payload holds the text name of the mode e.g. "auto" // message payload holds the text name of the mode e.g. "auto"
void Thermostat::thermostat_cmd_mode(const char * message) { bool Thermostat::thermostat_cmd_mode(const char * message) {
set_mode(message, AUTO_HEATING_CIRCUIT); return set_mode(message, AUTO_HEATING_CIRCUIT);
} }
void Thermostat::set_temperature_value(const char * value, const int8_t id, const uint8_t mode) { bool Thermostat::set_temperature_value(const char * value, const int8_t id, const uint8_t mode) {
float f = 0; float f = 0;
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id;
if (Helpers::value2float(value, f)) { if (Helpers::value2float(value, f)) {
set_temperature(f, mode, hc_num); return set_temperature(f, mode, hc_num);
} else {
return false;
} }
} }
void Thermostat::set_temp(const char * value, const int8_t id) { bool Thermostat::set_temp(const char * value, const int8_t id) {
set_temperature_value(value, id, HeatingCircuit::Mode::AUTO); return set_temperature_value(value, id, HeatingCircuit::Mode::AUTO);
} }
void Thermostat::set_nighttemp(const char * value, const int8_t id) { bool Thermostat::set_nighttemp(const char * value, const int8_t id) {
set_temperature_value(value, id, HeatingCircuit::Mode::NIGHT); return set_temperature_value(value, id, HeatingCircuit::Mode::NIGHT);
} }
void Thermostat::set_daytemp(const char * value, const int8_t id) { bool Thermostat::set_daytemp(const char * value, const int8_t id) {
set_temperature_value(value, id, HeatingCircuit::Mode::DAY); return set_temperature_value(value, id, HeatingCircuit::Mode::DAY);
} }
void Thermostat::set_comforttemp(const char * value, const int8_t id) { bool Thermostat::set_comforttemp(const char * value, const int8_t id) {
set_temperature_value(value, id, HeatingCircuit::Mode::COMFORT); return set_temperature_value(value, id, HeatingCircuit::Mode::COMFORT);
} }
void Thermostat::set_nofrosttemp(const char * value, const int8_t id) { bool Thermostat::set_nofrosttemp(const char * value, const int8_t id) {
set_temperature_value(value, id, HeatingCircuit::Mode::NOFROST); return set_temperature_value(value, id, HeatingCircuit::Mode::NOFROST);
} }
void Thermostat::set_ecotemp(const char * value, const int8_t id) { bool Thermostat::set_ecotemp(const char * value, const int8_t id) {
set_temperature_value(value, id, HeatingCircuit::Mode::ECO); return set_temperature_value(value, id, HeatingCircuit::Mode::ECO);
} }
void Thermostat::set_heattemp(const char * value, const int8_t id) { bool Thermostat::set_heattemp(const char * value, const int8_t id) {
set_temperature_value(value, id, HeatingCircuit::Mode::HEAT); return set_temperature_value(value, id, HeatingCircuit::Mode::HEAT);
} }
void Thermostat::set_summertemp(const char * value, const int8_t id) { bool Thermostat::set_summertemp(const char * value, const int8_t id) {
set_temperature_value(value, id, HeatingCircuit::Mode::SUMMER); return set_temperature_value(value, id, HeatingCircuit::Mode::SUMMER);
} }
void Thermostat::set_designtemp(const char * value, const int8_t id) { bool Thermostat::set_designtemp(const char * value, const int8_t id) {
set_temperature_value(value, id, HeatingCircuit::Mode::DESIGN); return set_temperature_value(value, id, HeatingCircuit::Mode::DESIGN);
} }
void Thermostat::set_offsettemp(const char * value, const int8_t id) { bool Thermostat::set_offsettemp(const char * value, const int8_t id) {
set_temperature_value(value, id, HeatingCircuit::Mode::OFFSET); return set_temperature_value(value, id, HeatingCircuit::Mode::OFFSET);
} }
void Thermostat::set_holidaytemp(const char * value, const int8_t id) { bool Thermostat::set_holidaytemp(const char * value, const int8_t id) {
set_temperature_value(value, id, HeatingCircuit::Mode::HOLIDAY); return set_temperature_value(value, id, HeatingCircuit::Mode::HOLIDAY);
} }
void Thermostat::set_manualtemp(const char * value, const int8_t id) { bool Thermostat::set_manualtemp(const char * value, const int8_t id) {
set_temperature_value(value, id, HeatingCircuit::Mode::MANUAL); return set_temperature_value(value, id, HeatingCircuit::Mode::MANUAL);
} }
// commands for MQTT and Console // commands for MQTT and Console
@@ -1869,48 +1930,48 @@ void Thermostat::add_commands() {
} }
// common to all thermostats // common to all thermostats
register_mqtt_cmd(F("wwmode"), [&](const char * value, const int8_t id) { set_wwmode(value, id); }); register_mqtt_cmd(F("wwmode"), [&](const char * value, const int8_t id) { return set_wwmode(value, id); });
register_mqtt_cmd(F("temp"), [&](const char * value, const int8_t id) { set_temp(value, id); }); register_mqtt_cmd(F("temp"), [&](const char * value, const int8_t id) { return set_temp(value, id); });
register_mqtt_cmd(F("mode"), [&](const char * value, const int8_t id) { set_mode(value, id); }); register_mqtt_cmd(F("mode"), [&](const char * value, const int8_t id) { return set_mode(value, id); });
uint8_t model = this->model(); uint8_t model = this->model();
switch (model) { switch (model) {
case EMS_DEVICE_FLAG_RC100: case EMS_DEVICE_FLAG_RC100:
case EMS_DEVICE_FLAG_RC300: case EMS_DEVICE_FLAG_RC300:
register_mqtt_cmd(F("manualtemp"), [&](const char * value, const int8_t id) { set_manualtemp(value, id); }); register_mqtt_cmd(F("manualtemp"), [&](const char * value, const int8_t id) { return set_manualtemp(value, id); });
register_mqtt_cmd(F("ecotemp"), [&](const char * value, const int8_t id) { set_ecotemp(value, id); }); register_mqtt_cmd(F("ecotemp"), [&](const char * value, const int8_t id) { return set_ecotemp(value, id); });
register_mqtt_cmd(F("comforttemp"), [&](const char * value, const int8_t id) { set_comforttemp(value, id); }); register_mqtt_cmd(F("comforttemp"), [&](const char * value, const int8_t id) { return set_comforttemp(value, id); });
break; break;
case EMS_DEVICE_FLAG_RC20_2: case EMS_DEVICE_FLAG_RC20_2:
register_mqtt_cmd(F("nighttemp"), [&](const char * value, const int8_t id) { set_nighttemp(value, id); }); register_mqtt_cmd(F("nighttemp"), [&](const char * value, const int8_t id) { return set_nighttemp(value, id); });
register_mqtt_cmd(F("daytemp"), [&](const char * value, const int8_t id) { set_daytemp(value, id); }); register_mqtt_cmd(F("daytemp"), [&](const char * value, const int8_t id) { return set_daytemp(value, id); });
break; break;
case EMS_DEVICE_FLAG_RC30_1: // only RC30_1 case EMS_DEVICE_FLAG_RC30_1: // only RC30_1
register_mqtt_cmd(F("clockoffset"), [&](const char * value, const int8_t id) { set_clockoffset(value, id); }); register_mqtt_cmd(F("clockoffset"), [&](const char * value, const int8_t id) { return set_clockoffset(value, id); });
register_mqtt_cmd(F("language"), [&](const char * value, const int8_t id) { set_language(value, id); }); register_mqtt_cmd(F("language"), [&](const char * value, const int8_t id) { return set_language(value, id); });
register_mqtt_cmd(F("display"), [&](const char * value, const int8_t id) { set_display(value, id); }); register_mqtt_cmd(F("display"), [&](const char * value, const int8_t id) { return set_display(value, id); });
case EMS_DEVICE_FLAG_RC35: // RC30 and RC35 case EMS_DEVICE_FLAG_RC35: // RC30 and RC35
register_mqtt_cmd(F("nighttemp"), [&](const char * value, const int8_t id) { set_nighttemp(value, id); }); register_mqtt_cmd(F("nighttemp"), [&](const char * value, const int8_t id) { return set_nighttemp(value, id); });
register_mqtt_cmd(F("daytemp"), [&](const char * value, const int8_t id) { set_daytemp(value, id); }); register_mqtt_cmd(F("daytemp"), [&](const char * value, const int8_t id) { return set_daytemp(value, id); });
register_mqtt_cmd(F("nofrosttemp"), [&](const char * value, const int8_t id) { set_nofrosttemp(value, id); }); register_mqtt_cmd(F("nofrosttemp"), [&](const char * value, const int8_t id) { return set_nofrosttemp(value, id); });
register_mqtt_cmd(F("remotetemp"), [&](const char * value, const int8_t id) { set_remotetemp(value, id); }); register_mqtt_cmd(F("remotetemp"), [&](const char * value, const int8_t id) { return set_remotetemp(value, id); });
register_mqtt_cmd(F("datetime"), [&](const char * value, const int8_t id) { set_datetime(value, id); }); register_mqtt_cmd(F("datetime"), [&](const char * value, const int8_t id) { return set_datetime(value, id); });
register_mqtt_cmd(F("minexttemp"), [&](const char * value, const int8_t id) { set_minexttemp(value, id); }); register_mqtt_cmd(F("minexttemp"), [&](const char * value, const int8_t id) { return set_minexttemp(value, id); });
register_mqtt_cmd(F("calinttemp"), [&](const char * value, const int8_t id) { set_calinttemp(value, id); }); register_mqtt_cmd(F("calinttemp"), [&](const char * value, const int8_t id) { return set_calinttemp(value, id); });
register_mqtt_cmd(F("building"), [&](const char * value, const int8_t id) { set_building(value, id); }); register_mqtt_cmd(F("building"), [&](const char * value, const int8_t id) { return set_building(value, id); });
register_mqtt_cmd(F("control"), [&](const char * value, const int8_t id) { set_control(value, id); }); register_mqtt_cmd(F("control"), [&](const char * value, const int8_t id) { return set_control(value, id); });
register_mqtt_cmd(F("pause"), [&](const char * value, const int8_t id) { set_pause(value, id); }); register_mqtt_cmd(F("pause"), [&](const char * value, const int8_t id) { return set_pause(value, id); });
register_mqtt_cmd(F("party"), [&](const char * value, const int8_t id) { set_party(value, id); }); register_mqtt_cmd(F("party"), [&](const char * value, const int8_t id) { return set_party(value, id); });
register_mqtt_cmd(F("holiday"), [&](const char * value, const int8_t id) { set_holiday(value, id); }); register_mqtt_cmd(F("holiday"), [&](const char * value, const int8_t id) { return set_holiday(value, id); });
register_mqtt_cmd(F("summertemp"), [&](const char * value, const int8_t id) { set_summertemp(value, id); }); register_mqtt_cmd(F("summertemp"), [&](const char * value, const int8_t id) { return set_summertemp(value, id); });
register_mqtt_cmd(F("designtemp"), [&](const char * value, const int8_t id) { set_designtemp(value, id); }); register_mqtt_cmd(F("designtemp"), [&](const char * value, const int8_t id) { return set_designtemp(value, id); });
register_mqtt_cmd(F("offsettemp"), [&](const char * value, const int8_t id) { set_offsettemp(value, id); }); register_mqtt_cmd(F("offsettemp"), [&](const char * value, const int8_t id) { return set_offsettemp(value, id); });
register_mqtt_cmd(F("holidaytemp"), [&](const char * value, const int8_t id) { set_holidaytemp(value, id); }); register_mqtt_cmd(F("holidaytemp"), [&](const char * value, const int8_t id) { return set_holidaytemp(value, id); });
break; break;
case EMS_DEVICE_FLAG_JUNKERS: case EMS_DEVICE_FLAG_JUNKERS:
register_mqtt_cmd(F("nofrosttemp"), [&](const char * value, const int8_t id) { set_nofrosttemp(value, id); }); register_mqtt_cmd(F("nofrosttemp"), [&](const char * value, const int8_t id) { return set_nofrosttemp(value, id); });
register_mqtt_cmd(F("ecotemp"), [&](const char * value, const int8_t id) { set_ecotemp(value, id); }); register_mqtt_cmd(F("ecotemp"), [&](const char * value, const int8_t id) { return set_ecotemp(value, id); });
register_mqtt_cmd(F("heattemp"), [&](const char * value, const int8_t id) { set_heattemp(value, id); }); register_mqtt_cmd(F("heattemp"), [&](const char * value, const int8_t id) { return set_heattemp(value, id); });
break; break;
default: default:
break; break;

View File

@@ -246,48 +246,47 @@ class Thermostat : public EMSdevice {
void process_RC300WWmode(std::shared_ptr<const Telegram> telegram); void process_RC300WWmode(std::shared_ptr<const Telegram> telegram);
// internal helper functions // internal helper functions
void set_mode_n(const uint8_t mode, const uint8_t hc_num); bool set_mode_n(const uint8_t mode, const uint8_t hc_num);
void set_temperature_value(const char * value, const int8_t id, const uint8_t mode); bool set_temperature_value(const char * value, const int8_t id, const uint8_t mode);
void set_temperature(const float temperature, const std::string & mode, const uint8_t hc_num); bool 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); bool set_temperature(const float temperature, const uint8_t mode, const uint8_t hc_num);
// for HA specifically. MQTT functions. // for HA specifically. MQTT functions.
void thermostat_cmd_temp(const char * message); bool thermostat_cmd_temp(const char * message);
void thermostat_cmd_mode(const char * message); bool thermostat_cmd_mode(const char * message);
// set functions - these use the id/hc // set functions - these use the id/hc
void set_mode(const char * value, const int8_t id); bool set_mode(const char * value, const int8_t id);
void set_control(const char * value, const int8_t id); bool set_control(const char * value, const int8_t id);
void set_holiday(const char * value, const int8_t id); bool set_holiday(const char * value, const int8_t id);
void set_pause(const char * value, const int8_t id); bool set_pause(const char * value, const int8_t id);
void set_party(const char * value, const int8_t id); bool set_party(const char * value, const int8_t id);
void set_temp(const char * value, const int8_t id); bool set_temp(const char * value, const int8_t id);
void set_nighttemp(const char * value, const int8_t id); bool set_nighttemp(const char * value, const int8_t id);
void set_daytemp(const char * value, const int8_t id); bool set_daytemp(const char * value, const int8_t id);
void set_comforttemp(const char * value, const int8_t id); bool set_comforttemp(const char * value, const int8_t id);
void set_nofrosttemp(const char * value, const int8_t id); bool set_nofrosttemp(const char * value, const int8_t id);
void set_ecotemp(const char * value, const int8_t id); bool set_ecotemp(const char * value, const int8_t id);
void set_heattemp(const char * value, const int8_t id); bool set_heattemp(const char * value, const int8_t id);
void set_summertemp(const char * value, const int8_t id); bool set_summertemp(const char * value, const int8_t id);
void set_designtemp(const char * value, const int8_t id); bool set_designtemp(const char * value, const int8_t id);
void set_offsettemp(const char * value, const int8_t id); bool set_offsettemp(const char * value, const int8_t id);
void set_holidaytemp(const char * value, const int8_t id); bool set_holidaytemp(const char * value, const int8_t id);
void set_manualtemp(const char * value, const int8_t id); bool set_manualtemp(const char * value, const int8_t id);
bool set_remotetemp(const char * value, const int8_t id);
void set_remotetemp(const char * value, const int8_t id);
// set functions - these don't use the id/hc, the parameters are ignored // set functions - these don't use the id/hc, the parameters are ignored
void set_wwmode(const char * value, const int8_t id); bool set_wwmode(const char * value, const int8_t id);
void set_datetime(const char * value, const int8_t id); bool set_datetime(const char * value, const int8_t id);
void set_minexttemp(const char * value, const int8_t id); bool set_minexttemp(const char * value, const int8_t id);
void set_clockoffset(const char * value, const int8_t id); bool set_clockoffset(const char * value, const int8_t id);
void set_calinttemp(const char * value, const int8_t id); bool set_calinttemp(const char * value, const int8_t id);
void set_display(const char * value, const int8_t id); bool set_display(const char * value, const int8_t id);
void set_building(const char * value, const int8_t id); bool set_building(const char * value, const int8_t id);
void set_language(const char * value, const int8_t id); bool set_language(const char * value, const int8_t id);
}; // namespace emsesp };
} // namespace emsesp } // namespace emsesp

View File

@@ -18,7 +18,6 @@
#include "emsdevice.h" #include "emsdevice.h"
#include "emsesp.h" #include "emsesp.h"
#include "mqtt.h" // for the mqtt_function_p
namespace emsesp { namespace emsesp {
@@ -54,30 +53,30 @@ std::string EMSdevice::brand_to_string() const {
} }
// returns the name of the MQTT topic to use for a specific device // 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) { std::string EMSdevice::device_type_2_device_name(const uint8_t device_type) {
switch (device_type) { switch (device_type) {
case DeviceType::SERVICEKEY: case DeviceType::SERVICEKEY:
return read_flash_string(F("system_cmd")); return read_flash_string(F("system"));
break; break;
case DeviceType::BOILER: case DeviceType::BOILER:
return read_flash_string(F("boiler_cmd")); return read_flash_string(F("boiler"));
break; break;
case DeviceType::THERMOSTAT: case DeviceType::THERMOSTAT:
return read_flash_string(F("thermostat_cmd")); return read_flash_string(F("thermostat"));
break; break;
case DeviceType::HEATPUMP: case DeviceType::HEATPUMP:
return read_flash_string(F("heatpump_cmd")); return read_flash_string(F("heatpump"));
break; break;
case DeviceType::SOLAR: case DeviceType::SOLAR:
return read_flash_string(F("solar_cmd")); return read_flash_string(F("solar"));
break; break;
case DeviceType::MIXING: case DeviceType::MIXING:
return read_flash_string(F("mixing_cmd")); return read_flash_string(F("mixing"));
break; break;
default: default:
@@ -86,6 +85,35 @@ std::string EMSdevice::device_type_topic_name(const uint8_t device_type) {
} }
} }
// returns device_type from a string
uint8_t EMSdevice::device_name_2_device_type(const char * topic) {
if (strcmp(topic, "boiler") == 0) {
return DeviceType::BOILER;
}
if (strcmp(topic, "thermostat") == 0) {
return DeviceType::THERMOSTAT;
}
if (strcmp(topic, "system") == 0) {
return DeviceType::SERVICEKEY;
}
if (strcmp(topic, "heatpump") == 0) {
return DeviceType::HEATPUMP;
}
if (strcmp(topic, "solar") == 0) {
return DeviceType::SOLAR;
}
if (strcmp(topic, "mixing") == 0) {
return DeviceType::MIXING;
}
return DeviceType::UNKNOWN;
}
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:
@@ -202,7 +230,7 @@ void EMSdevice::show_values(uuid::console::Shell & shell) {
// for each telegram that has the fetch value set (true) do a read request // for each telegram that has the fetch value set (true) do a read request
void EMSdevice::fetch_values() { void EMSdevice::fetch_values() {
LOG_DEBUG(F("Fetching values for device ID 0x%02X"), get_device_id()); LOG_DEBUG(F("Fetching values for device ID 0x%02X"), device_id());
for (const auto & tf : telegram_functions_) { for (const auto & tf : telegram_functions_) {
if (tf.fetch_) { if (tf.fetch_) {
@@ -213,7 +241,7 @@ void EMSdevice::fetch_values() {
// toggle on/off automatic fetch for a telegram id // toggle on/off automatic fetch for a telegram id
void EMSdevice::toggle_fetch(uint16_t telegram_id, bool toggle) { void EMSdevice::toggle_fetch(uint16_t telegram_id, bool toggle) {
LOG_DEBUG(F("Toggling fetch for device ID 0x%02X, telegram ID 0x%02X to %d"), get_device_id(), telegram_id, toggle); LOG_DEBUG(F("Toggling fetch for device ID 0x%02X, telegram ID 0x%02X to %d"), device_id(), telegram_id, toggle);
for (auto & tf : telegram_functions_) { for (auto & tf : telegram_functions_) {
if (tf.telegram_type_id_ == telegram_id) { if (tf.telegram_type_id_ == telegram_id) {
@@ -226,7 +254,7 @@ void EMSdevice::toggle_fetch(uint16_t telegram_id, bool toggle) {
bool EMSdevice::get_toggle_fetch(uint16_t telegram_id) { bool EMSdevice::get_toggle_fetch(uint16_t telegram_id) {
for (auto & tf : telegram_functions_) { for (auto & tf : telegram_functions_) {
if (tf.telegram_type_id_ == telegram_id) { if (tf.telegram_type_id_ == telegram_id) {
return tf.fetch_ ; return tf.fetch_;
} }
} }
return false; return false;
@@ -256,8 +284,9 @@ void EMSdevice::register_mqtt_topic(const std::string & topic, mqtt_subfunction_
Mqtt::subscribe(this->device_type_, topic, f); Mqtt::subscribe(this->device_type_, topic, f);
} }
void EMSdevice::register_mqtt_cmd(const __FlashStringHelper * cmd, mqtt_cmdfunction_p f) { // add command to library
Mqtt::add_command(this->device_type_, this->device_id_, cmd, f); void EMSdevice::register_mqtt_cmd(const __FlashStringHelper * cmd, cmdfunction_p f) {
Command::add_command(this->device_type_, this->device_id_, cmd, f);
} }
// register a call back function for a specific telegram type // register a call back function for a specific telegram type
@@ -306,22 +335,22 @@ bool EMSdevice::handle_telegram(std::shared_ptr<const Telegram> telegram) {
// send Tx write with a data block // send Tx write with a data block
void EMSdevice::write_command(const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid) { void EMSdevice::write_command(const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid) {
EMSESP::send_write_request(type_id, this->get_device_id(), offset, message_data, message_length, validate_typeid); EMSESP::send_write_request(type_id, this->device_id(), offset, message_data, message_length, validate_typeid);
} }
// send Tx write with a single value // send Tx write with a single value
void EMSdevice::write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid) { void EMSdevice::write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid) {
EMSESP::send_write_request(type_id, this->get_device_id(), offset, value, validate_typeid); EMSESP::send_write_request(type_id, this->device_id(), offset, value, validate_typeid);
} }
// send Tx write with a single value, with no post validation // send Tx write with a single value, with no post validation
void EMSdevice::write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value) { void EMSdevice::write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value) {
EMSESP::send_write_request(type_id, this->get_device_id(), offset, value, 0); EMSESP::send_write_request(type_id, this->device_id(), offset, value, 0);
} }
// send Tx read command to the device // send Tx read command to the device
void EMSdevice::read_command(const uint16_t type_id) { void EMSdevice::read_command(const uint16_t type_id) {
EMSESP::send_read_request(type_id, get_device_id()); EMSESP::send_read_request(type_id, device_id());
} }
// prints a string value to the console // prints a string value to the console
@@ -339,60 +368,4 @@ 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) {
// if we're adding commands for a thermostat or mixing, then include an additional optional paramter called heating circuit
flash_string_vector params;
if ((context == ShellContext::THERMOSTAT) || (context == ShellContext::MIXING)) {
params = flash_string_vector{F_(cmd_optional), F_(data_optional), F_(hc_optional)};
} else {
params = flash_string_vector{F_(cmd_optional), F_(data_optional)};
}
EMSESPShell::commands->add_command(
context,
CommandFlags::ADMIN,
flash_string_vector{F_(call)},
params,
[&](Shell & shell, const std::vector<std::string> & arguments) {
uint8_t device_type_ = device_type();
if (arguments.empty()) {
// list options
shell.print("Available commands:");
for (const auto & cf : Mqtt::commands()) {
if (cf.device_type_ == device_type_) {
shell.printf(" %s", uuid::read_flash_string(cf.cmd_).c_str());
}
}
shell.println();
return;
}
const char * cmd = arguments[0].c_str();
if (arguments.size() == 1) {
// no value specified
Mqtt::call_command(device_type_, cmd, nullptr, -1);
} else if (arguments.size() == 2) {
// has a value but no id
Mqtt::call_command(device_type_, cmd, arguments.back().c_str(), -1);
} else {
// use value, which could be an id or hc
Mqtt::call_command(device_type_, cmd, arguments[1].c_str(), atoi(arguments[2].c_str()));
}
},
[&](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) -> std::vector<std::string> {
if (arguments.size() > 0) {
return {};
}
std::vector<std::string> commands;
for (const auto & cf : Mqtt::commands()) {
if (cf.device_type_ == device_type()) {
commands.emplace_back(uuid::read_flash_string(cf.cmd_));
}
}
return commands;
});
}
} // namespace emsesp } // namespace emsesp

View File

@@ -47,12 +47,13 @@ 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
inline uint8_t get_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); static std::string device_type_2_device_name(const uint8_t device_type);
static uint8_t device_name_2_device_type(const char * topic);
inline uint8_t product_id() const { inline uint8_t product_id() const {
return product_id_; return product_id_;
@@ -127,13 +128,10 @@ class EMSdevice {
void write_command(const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid); void write_command(const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid);
void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid); void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid);
void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value); void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value);
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); void register_mqtt_cmd(const __FlashStringHelper * cmd, 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;
@@ -230,7 +228,7 @@ class EMSdevice {
}; };
enum DeviceType : uint8_t { enum DeviceType : uint8_t {
SERVICEKEY = 0, // this is us SERVICEKEY = 0, // this is us (EMS-ESP)
BOILER, BOILER,
THERMOSTAT, THERMOSTAT,
MIXING, MIXING,
@@ -239,8 +237,8 @@ class EMSdevice {
GATEWAY, GATEWAY,
SWITCH, SWITCH,
CONTROLLER, CONTROLLER,
CONNECT CONNECT,
UNKNOWN
}; };
// device IDs // device IDs

View File

@@ -36,6 +36,7 @@ EMSESPSettingsService EMSESP::emsespSettingsService = EMSESPSettingsService(&web
EMSESPStatusService EMSESP::emsespStatusService = EMSESPStatusService(&webServer, EMSESP::esp8266React.getSecurityManager()); EMSESPStatusService EMSESP::emsespStatusService = EMSESPStatusService(&webServer, EMSESP::esp8266React.getSecurityManager());
EMSESPDevicesService EMSESP::emsespDevicesService = EMSESPDevicesService(&webServer, EMSESP::esp8266React.getSecurityManager()); EMSESPDevicesService EMSESP::emsespDevicesService = EMSESPDevicesService(&webServer, EMSESP::esp8266React.getSecurityManager());
EMSESPAPIService EMSESP::emsespAPIService = EMSESPAPIService(&webServer);
using DeviceFlags = emsesp::EMSdevice; using DeviceFlags = emsesp::EMSdevice;
using DeviceType = emsesp::EMSdevice::DeviceType; using DeviceType = emsesp::EMSdevice::DeviceType;
@@ -615,7 +616,7 @@ void EMSESP::show_devices(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)) {
shell.printf(F("%s: %s"), emsdevice->device_type_name().c_str(), emsdevice->to_string().c_str()); shell.printf(F("%s: %s"), emsdevice->device_type_name().c_str(), emsdevice->to_string().c_str());
if ((emsdevice->device_type() == EMSdevice::DeviceType::THERMOSTAT) && (emsdevice->get_device_id() == actual_master_thermostat())) { if ((emsdevice->device_type() == EMSdevice::DeviceType::THERMOSTAT) && (emsdevice->device_id() == actual_master_thermostat())) {
shell.printf(F(" ** master device **")); shell.printf(F(" ** master device **"));
} }
shell.println(); shell.println();
@@ -727,7 +728,7 @@ void EMSESP::send_write_request(const uint16_t type_id, const uint8_t dest, cons
// we check if its a complete telegram or just a single byte (which could be a poll or a return status) // we check if its a complete telegram or just a single byte (which could be a poll or a return status)
// the CRC check is not done here, only when it's added to the Rx queue with add() // the CRC check is not done here, only when it's added to the Rx queue with add()
void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) { void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
#ifdef EMSESP_DEBUG #ifdef EMSESP_UART_DEBUG
static uint32_t rx_time_ = 0; static uint32_t rx_time_ = 0;
#endif #endif
// check first for echo // check first for echo
@@ -735,9 +736,9 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
if (((first_value & 0x7F) == txservice_.ems_bus_id()) && (length > 1)) { if (((first_value & 0x7F) == txservice_.ems_bus_id()) && (length > 1)) {
// if we ask ourself at roomcontrol for version e.g. 0B 98 02 00 20 // if we ask ourself at roomcontrol for version e.g. 0B 98 02 00 20
Roomctrl::check((data[1] ^ 0x80 ^ rxservice_.ems_mask()), data); Roomctrl::check((data[1] ^ 0x80 ^ rxservice_.ems_mask()), data);
#ifdef EMSESP_DEBUG #ifdef EMSESP_UART_DEBUG
// get_uptime is only updated once per loop, does not give the right time // get_uptime is only updated once per loop, does not give the right time
LOG_TRACE(F("[DEBUG] Echo after %d ms: %s"), ::millis() - rx_time_, Helpers::data_to_hex(data, length).c_str()); LOG_TRACE(F("[UART_DEBUG] Echo after %d ms: %s"), ::millis() - rx_time_, Helpers::data_to_hex(data, length).c_str());
#endif #endif
return; // it's an echo return; // it's an echo
} }
@@ -786,14 +787,14 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
if (length == 1) { if (length == 1) {
EMSbus::last_bus_activity(uuid::get_uptime()); // set the flag indication the EMS bus is active EMSbus::last_bus_activity(uuid::get_uptime()); // set the flag indication the EMS bus is active
#ifdef EMSESP_DEBUG #ifdef EMSESP_UART_DEBUG
char s[4]; char s[4];
if (first_value & 0x80) { if (first_value & 0x80) {
LOG_TRACE(F("[DEBUG] next Poll %s after %d ms"), Helpers::hextoa(s, first_value), ::millis() - rx_time_); LOG_TRACE(F("[UART_DEBUG] next Poll %s after %d ms"), Helpers::hextoa(s, first_value), ::millis() - rx_time_);
// time measurement starts here, use millis because get_uptime is only updated once per loop // time measurement starts here, use millis because get_uptime is only updated once per loop
rx_time_ = ::millis(); rx_time_ = ::millis();
} else { } else {
LOG_TRACE(F("[DEBUG] Poll ack %s after %d ms"), Helpers::hextoa(s, first_value), ::millis() - rx_time_); LOG_TRACE(F("[UART_DEBUG] Poll ack %s after %d ms"), Helpers::hextoa(s, first_value), ::millis() - rx_time_);
} }
#endif #endif
// check for poll to us, if so send top message from Tx queue immediately and quit // check for poll to us, if so send top message from Tx queue immediately and quit
@@ -805,8 +806,8 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
Roomctrl::send(first_value ^ 0x80 ^ rxservice_.ems_mask()); Roomctrl::send(first_value ^ 0x80 ^ rxservice_.ems_mask());
return; return;
} else { } else {
#ifdef EMSESP_DEBUG #ifdef EMSESP_UART_DEBUG
LOG_TRACE(F("[DEBUG] Reply after %d ms: %s"), ::millis() - rx_time_, Helpers::data_to_hex(data, length).c_str()); LOG_TRACE(F("[UART_DEBUG] Reply after %d ms: %s"), ::millis() - rx_time_, Helpers::data_to_hex(data, length).c_str());
#endif #endif
Roomctrl::check((data[1] ^ 0x80 ^ rxservice_.ems_mask()), data); // check if there is a message for the roomcontroller Roomctrl::check((data[1] ^ 0x80 ^ rxservice_.ems_mask()), data); // check if there is a message for the roomcontroller

View File

@@ -37,6 +37,7 @@
#include "EMSESPStatusService.h" #include "EMSESPStatusService.h"
#include "EMSESPDevicesService.h" #include "EMSESPDevicesService.h"
#include "EMSESPSettingsService.h" #include "EMSESPSettingsService.h"
#include "EMSESPAPIService.h"
#include "emsdevice.h" #include "emsdevice.h"
#include "emsfactory.h" #include "emsfactory.h"
@@ -47,6 +48,7 @@
#include "console.h" #include "console.h"
#include "shower.h" #include "shower.h"
#include "roomcontrol.h" #include "roomcontrol.h"
#include "command.h"
#define WATCH_ID_NONE 0 // no watch id set #define WATCH_ID_NONE 0 // no watch id set
@@ -162,6 +164,7 @@ class EMSESP {
static EMSESPSettingsService emsespSettingsService; static EMSESPSettingsService emsespSettingsService;
static EMSESPStatusService emsespStatusService; static EMSESPStatusService emsespStatusService;
static EMSESPDevicesService emsespDevicesService; static EMSESPDevicesService emsespDevicesService;
static EMSESPAPIService emsespAPIService;
static uuid::log::Logger logger() { static uuid::log::Logger logger() {
return logger_; return logger_;

View File

@@ -61,11 +61,20 @@ MAKE_PSTR_WORD(ssid)
MAKE_PSTR_WORD(heartbeat) MAKE_PSTR_WORD(heartbeat)
MAKE_PSTR_WORD(users) MAKE_PSTR_WORD(users)
MAKE_PSTR_WORD(master) MAKE_PSTR_WORD(master)
MAKE_PSTR_WORD(test)
MAKE_PSTR_WORD(pin) MAKE_PSTR_WORD(pin)
#if defined(EMSESP_DEBUG)
MAKE_PSTR_WORD(test)
#endif
// for commands // for commands
MAKE_PSTR_WORD(call) MAKE_PSTR_WORD(call)
MAKE_PSTR_WORD(cmd)
MAKE_PSTR_WORD(id)
MAKE_PSTR_WORD(device)
MAKE_PSTR_WORD(data)
MAKE_PSTR_WORD(command)
MAKE_PSTR_WORD(commands)
// devices // devices
MAKE_PSTR_WORD(boiler) MAKE_PSTR_WORD(boiler)
@@ -104,7 +113,6 @@ MAKE_PSTR(degrees, "°C")
MAKE_PSTR(asterisks, "********") MAKE_PSTR(asterisks, "********")
MAKE_PSTR(n_mandatory, "<n>") MAKE_PSTR(n_mandatory, "<n>")
MAKE_PSTR(n_optional, "[n]") MAKE_PSTR(n_optional, "[n]")
MAKE_PSTR(gpio_mandatory, "<gpio>")
MAKE_PSTR(data_optional, "[data]") MAKE_PSTR(data_optional, "[data]")
MAKE_PSTR(typeid_mandatory, "<type ID>") MAKE_PSTR(typeid_mandatory, "<type ID>")
MAKE_PSTR(deviceid_mandatory, "<device ID>") MAKE_PSTR(deviceid_mandatory, "<device ID>")

View File

@@ -37,7 +37,6 @@ uint32_t Mqtt::publish_time_other_;
uint32_t Mqtt::publish_time_sensor_; uint32_t Mqtt::publish_time_sensor_;
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;
@@ -67,10 +66,9 @@ void Mqtt::subscribe(const uint8_t device_type, const std::string & topic, mqtt_
mqtt_subfunctions_.emplace_back(device_type, std::move(topic), std::move(message->topic), std::move(cb)); mqtt_subfunctions_.emplace_back(device_type, std::move(topic), std::move(message->topic), std::move(cb));
} }
// adds a command and callback function for a specific device // subscribe to the command topic if it doesn't exist yet
void Mqtt::add_command(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, mqtt_cmdfunction_p cb) { void Mqtt::register_command(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cb) {
// subscribe to the command topic if it doesn't exist yet std::string cmd_topic = EMSdevice::device_type_2_device_name(device_type);
std::string cmd_topic = EMSdevice::device_type_topic_name(device_type); // cmd topic for a device like "<device_type>_cmd" e.g. "boiler_cmd"
bool exists = false; bool exists = false;
if (!mqtt_subfunctions_.empty()) { if (!mqtt_subfunctions_.empty()) {
@@ -84,9 +82,7 @@ void Mqtt::add_command(const uint8_t device_type, const uint8_t device_id, const
Mqtt::subscribe(device_type, cmd_topic, nullptr); // use an empty function handler to signal this is a command function Mqtt::subscribe(device_type, cmd_topic, nullptr); // use an empty function handler to signal this is a command function
} }
LOG_DEBUG(F("Registering MQTT cmd %s with topic %s"), uuid::read_flash_string(cmd).c_str(), EMSdevice::device_type_topic_name(device_type).c_str()); LOG_DEBUG(F("Registering MQTT cmd %s with topic %s"), uuid::read_flash_string(cmd).c_str(), EMSdevice::device_type_2_device_name(device_type).c_str());
mqtt_cmdfunctions_.emplace_back(device_type, device_id, cmd, cb);
} }
// subscribe to an MQTT topic, and store the associated callback function. For generic functions not tied to a specific device // subscribe to an MQTT topic, and store the associated callback function. For generic functions not tied to a specific device
@@ -156,25 +152,13 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) {
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) { system_heartbeat = settings.system_heartbeat; }); EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) { system_heartbeat = settings.system_heartbeat; });
shell.printfln(F_(system_heartbeat_fmt), system_heartbeat ? F_(enabled) : F_(disabled)); shell.printfln(F_(system_heartbeat_fmt), system_heartbeat ? F_(enabled) : F_(disabled));
shell.printfln(F("MQTT publish fails: %lu"), mqtt_publish_fails_); shell.printfln(F("MQTT publish fails count: %lu"), mqtt_publish_fails_);
shell.println(); shell.println();
// show subscriptions // show subscriptions
shell.printfln(F("MQTT subscriptions:")); shell.printfln(F("MQTT topic subscriptions:"));
for (const auto & mqtt_subfunction : mqtt_subfunctions_) { for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
// don't show commands if its homeassistant shell.printfln(F(" %s"), mqtt_subfunction.full_topic_.c_str());
if ((strncmp(mqtt_subfunction.full_topic_.c_str(), "homeassistant/", 13) == 0)) {
shell.printf(F(" topic: %s"), mqtt_subfunction.full_topic_.c_str());
} else {
// show the commands associated with this subscription
shell.printf(F(" topic: %s, [cmd]:"), mqtt_subfunction.full_topic_.c_str());
for (const auto & mqtt_cmdfunction : mqtt_cmdfunctions_) {
if (EMSdevice::device_type_topic_name(mqtt_cmdfunction.device_type_) == mqtt_subfunction.topic_) {
shell.printf(F(" %s"), uuid::read_flash_string(mqtt_cmdfunction.cmd_).c_str());
}
}
}
shell.println();
} }
shell.println(); shell.println();
@@ -224,31 +208,6 @@ void Mqtt::incoming(const char * topic, const char * payload) {
} }
#endif #endif
// calls a command, context is the device_type
// id may be used to represent a heating circuit for example
bool Mqtt::call_command(const uint8_t device_type, const char * cmd, const char * value, const int8_t id) {
#ifdef EMSESP_DEBUG
if (id == -1) {
LOG_DEBUG(F("[DEBUG] Calling command %s, value %s, id is default"), cmd, value);
} else {
LOG_DEBUG(F("[DEBUG] Calling command %s, value %s, id is %d"), cmd, value, id);
}
#endif
if (!mqtt_cmdfunctions_.empty()) {
for (const auto & cf : mqtt_cmdfunctions_) {
if (cf.device_type_ == device_type) {
const char * cf_cmd = uuid::read_flash_string(cf.cmd_).c_str();
if (strcmp(cf_cmd, cmd) == 0) {
(cf.mqtt_cmdfunction_)(value, id); // call function, data needs to be a string and can be null
return true;
}
}
}
}
return false;
}
// received an MQTT message that we subscribed too // received an MQTT message that we subscribed too
void Mqtt::on_message(const char * topic, const char * payload, size_t len) { void Mqtt::on_message(const char * topic, const char * payload, size_t len) {
if (len == 0) { if (len == 0) {
@@ -295,13 +254,13 @@ void Mqtt::on_message(const char * topic, const char * payload, size_t len) {
bool cmd_known = false; bool cmd_known = false;
JsonVariant data = doc["data"]; JsonVariant data = doc["data"];
if (data.is<char *>()) { if (data.is<char *>()) {
cmd_known = call_command(mf.device_type_, command, data.as<char *>(), n); cmd_known = Command::call_command(mf.device_type_, command, data.as<char *>(), n);
} else if (data.is<int>()) { } else if (data.is<int>()) {
char data_str[10]; char data_str[10];
cmd_known = call_command(mf.device_type_, command, Helpers::itoa(data_str, (int16_t)data.as<int>()), n); cmd_known = Command::call_command(mf.device_type_, command, Helpers::itoa(data_str, (int16_t)data.as<int>()), n);
} else if (data.is<float>()) { } else if (data.is<float>()) {
char data_str[10]; char data_str[10];
cmd_known = call_command(mf.device_type_, command, Helpers::render_value(data_str, (float)data.as<float>(), 2), n); cmd_known = Command::call_command(mf.device_type_, command, Helpers::render_value(data_str, (float)data.as<float>(), 2), n);
} }
if (!cmd_known) { if (!cmd_known) {
@@ -417,7 +376,6 @@ void Mqtt::start() {
}); });
// create space for command buffer, to avoid heap memory fragmentation // create space for command buffer, to avoid heap memory fragmentation
mqtt_cmdfunctions_.reserve(40); // current count with boiler+thermostat is 37
mqtt_subfunctions_.reserve(10); mqtt_subfunctions_.reserve(10);
} }
@@ -493,9 +451,9 @@ 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
// these commands respond to the topic "system_cmd" and take a payload like {cmd:"", data:"", id:""} // these commands respond to the topic "system" and take a payload like {cmd:"", data:"", id:""}
add_command(EMSdevice::DeviceType::SERVICEKEY, bus_id_, F("pin"), System::mqtt_command_pin); Command::add_command(EMSdevice::DeviceType::SERVICEKEY, bus_id_, F("pin"), System::command_pin);
add_command(EMSdevice::DeviceType::SERVICEKEY, bus_id_, F("send"), System::mqtt_command_send); Command::add_command(EMSdevice::DeviceType::SERVICEKEY, bus_id_, F("send"), System::command_send);
LOG_INFO(F("MQTT connected")); LOG_INFO(F("MQTT connected"));
} }

View File

@@ -32,6 +32,7 @@
#include "helpers.h" #include "helpers.h"
#include "system.h" #include "system.h"
#include "console.h" #include "console.h"
#include "command.h"
#include <uuid/log.h> #include <uuid/log.h>
@@ -44,7 +45,7 @@ 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 cmdfunction_p = std::function<bool(const char * data, const int8_t id)>;
struct MqttMessage { struct MqttMessage {
~MqttMessage() = default; ~MqttMessage() = default;
@@ -85,7 +86,7 @@ class Mqtt {
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 uint8_t device_id, const __FlashStringHelper * cmd, mqtt_cmdfunction_p cb); static void register_command(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cb);
static void publish(const std::string & topic, const std::string & payload); static void publish(const std::string & topic, const std::string & payload);
static void publish(const std::string & topic, const JsonDocument & payload); static void publish(const std::string & topic, const JsonDocument & payload);
@@ -104,8 +105,6 @@ class Mqtt {
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();
} }
@@ -126,24 +125,6 @@ class Mqtt {
mqtt_publish_fails_ = 0; mqtt_publish_fails_ = 0;
} }
struct MQTTCmdFunction {
uint8_t device_type_;
uint8_t device_id_;
const __FlashStringHelper * cmd_;
mqtt_cmdfunction_p mqtt_cmdfunction_;
MQTTCmdFunction(uint8_t device_type, uint8_t device_id, const __FlashStringHelper * cmd, mqtt_cmdfunction_p mqtt_cmdfunction)
: device_type_(device_type)
, device_id_(device_id)
, cmd_(cmd)
, mqtt_cmdfunction_(mqtt_cmdfunction) {
}
};
static std::vector<MQTTCmdFunction> commands() {
return mqtt_cmdfunctions_;
}
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;
@@ -199,7 +180,6 @@ class Mqtt {
}; };
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_boiler_ = 0; uint32_t last_publish_boiler_ = 0;

View File

@@ -39,18 +39,22 @@ uint16_t System::analog_ = 0;
// send on/off to a gpio pin // send on/off to a gpio pin
// value: true = HIGH, false = LOW // value: true = HIGH, false = LOW
void System::mqtt_command_pin(const char * value, const int8_t id) { bool System::command_pin(const char * value, const int8_t id) {
bool v = false; bool v = false;
if (Helpers::value2bool(value, v)) { if (Helpers::value2bool(value, v)) {
pinMode(id, OUTPUT); pinMode(id, OUTPUT);
digitalWrite(id, v); digitalWrite(id, v);
LOG_INFO(F("GPIO %d set to %s"), id, v ? "HIGH" : "LOW"); LOG_INFO(F("GPIO %d set to %s"), id, v ? "HIGH" : "LOW");
return true;
} }
return false;
} }
// send raw // send raw
void System::mqtt_command_send(const char * value, const int8_t id) { bool System::command_send(const char * value, const int8_t id) {
EMSESP::send_raw_telegram(value); // ignore id EMSESP::send_raw_telegram(value); // ignore id
return true;
} }
// restart EMS-ESP // restart EMS-ESP
@@ -590,32 +594,12 @@ void System::console_commands(Shell & shell, unsigned int context) {
shell.printfln(F_(wifi_password_fmt), wifiSettings.ssid.isEmpty() ? F_(unset) : F_(asterisks)); shell.printfln(F_(wifi_password_fmt), wifiSettings.ssid.isEmpty() ? F_(unset) : F_(asterisks));
}); });
}); });
/*
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
CommandFlags::USER,
flash_string_vector{F_(show), F_(mqtt)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { Mqtt::show_mqtt(shell); });
*/
EMSESPShell::commands->add_command(ShellContext::SYSTEM, EMSESPShell::commands->add_command(ShellContext::SYSTEM,
CommandFlags::ADMIN, CommandFlags::ADMIN,
flash_string_vector{F_(show), F_(users)}, flash_string_vector{F_(show), F_(users)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { System::show_users(shell); }); [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { System::show_users(shell); });
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
CommandFlags::ADMIN,
flash_string_vector{F_(pin)},
flash_string_vector{F_(gpio_mandatory), F_(data_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.size() == 1) {
shell.printfln(F("use on/off, 1/0 or true/false"));
return;
}
int pin = 0;
if (Helpers::value2number(arguments[0].c_str(), pin)) {
System::mqtt_command_pin(arguments[1].c_str(), pin);
}
});
// enter the context // enter the context
Console::enter_custom_context(shell, context); Console::enter_custom_context(shell, context);
} }
@@ -820,4 +804,81 @@ bool System::check_upgrade() {
#endif #endif
} }
// export all settings to JSON text
// http://ems-esp/api?device=system&cmd=info
String System::export_settings() {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc;
#ifndef EMSESP_STANDALONE
JsonObject root = doc.to<JsonObject>();
EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & settings) {
JsonObject node = root.createNestedObject("WIFI");
node["ssid"] = settings.ssid;
// node["password"] = settings.password;
node["hostname"] = settings.hostname;
node["static_ip_config"] = settings.staticIPConfig;
JsonUtils::writeIP(node, "local_ip", settings.localIP);
JsonUtils::writeIP(node, "gateway_ip", settings.gatewayIP);
JsonUtils::writeIP(node, "subnet_mask", settings.subnetMask);
JsonUtils::writeIP(node, "dns_ip_1", settings.dnsIP1);
JsonUtils::writeIP(node, "dns_ip_2", settings.dnsIP2);
});
EMSESP::esp8266React.getAPSettingsService()->read([&](APSettings & settings) {
JsonObject node = root.createNestedObject("AP");
node["provision_mode"] = settings.provisionMode;
node["ssid"] = settings.ssid;
// node["password"] = settings.password;
node["local_ip"] = settings.localIP.toString();
node["gateway_ip"] = settings.gatewayIP.toString();
node["subnet_mask"] = settings.subnetMask.toString();
});
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
JsonObject node = root.createNestedObject("MQTT");
node["enabled"] = settings.enabled;
node["host"] = settings.host;
node["port"] = settings.port;
node["username"] = settings.username;
// node["password"] = settings.password;
node["client_id"] = settings.clientId;
node["keep_alive"] = settings.keepAlive;
node["clean_session"] = settings.cleanSession;
node["max_topic_length"] = settings.maxTopicLength;
node["system_heartbeat"] = settings.system_heartbeat;
node["publish_time_boiler"] = settings.publish_time_boiler;
node["publish_time_thermostat"] = settings.publish_time_thermostat;
node["publish_time_solar"] = settings.publish_time_solar;
node["publish_time_mixing"] = settings.publish_time_mixing;
node["publish_time_other"] = settings.publish_time_other;
node["publish_time_sensor"] = settings.publish_time_sensor;
node["mqtt_format"] = settings.mqtt_format;
node["mqtt_qos"] = settings.mqtt_qos;
node["mqtt_retain"] = settings.mqtt_retain;
});
EMSESP::esp8266React.getNTPSettingsService()->read([&](NTPSettings & settings) {
JsonObject node = root.createNestedObject("NTP");
node["enabled"] = settings.enabled;
node["server"] = settings.server;
node["tz_label"] = settings.tzLabel;
node["tz_format"] = settings.tzFormat;
});
EMSESP::esp8266React.getOTASettingsService()->read([&](OTASettings & settings) {
JsonObject node = root.createNestedObject("OTA");
node["enabled"] = settings.enabled;
node["port"] = settings.port;
// node["password"] = settings.password;
});
#endif
String buffer;
serializeJsonPretty(doc, buffer);
return buffer;
}
} // namespace emsesp } // namespace emsesp

View File

@@ -47,19 +47,20 @@ 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 bool command_pin(const char * value, const int8_t id);
static void mqtt_command_pin(const char * value, const int8_t id); static bool command_send(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);
static bool upload_status(); static bool upload_status();
static void show_mem(const char * note);
static void set_led();
static String export_settings();
bool check_upgrade();
void syslog_init(); void syslog_init();
void set_heartbeat(bool system_heartbeat); void set_heartbeat(bool system_heartbeat);
void send_heartbeat(); void send_heartbeat();
static void show_mem(const char * note);
static void set_led();
bool check_upgrade();
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;

View File

@@ -601,12 +601,12 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
// test publish and adding to queue // test publish and adding to queue
EMSESP::txservice_.flush_tx_queue(); EMSESP::txservice_.flush_tx_queue();
EMSESP::EMSESP::mqtt_.publish("boiler_cmd", "test me"); EMSESP::EMSESP::mqtt_.publish("boiler", "test me");
Mqtt::show_mqtt(shell); // show queue Mqtt::show_mqtt(shell); // show queue
strcpy(boiler_topic, "ems-esp/boiler_cmd"); strcpy(boiler_topic, "ems-esp/boiler");
strcpy(thermostat_topic, "ems-esp/thermostat_cmd"); strcpy(thermostat_topic, "ems-esp/thermostat");
strcpy(system_topic, "ems-esp/saystem_cmd"); strcpy(system_topic, "ems-esp/saystem");
EMSESP::mqtt_.incoming(boiler_topic, "12345"); // invalid format EMSESP::mqtt_.incoming(boiler_topic, "12345"); // invalid format
EMSESP::mqtt_.incoming("bad_topic", "12345"); // no matching topic EMSESP::mqtt_.incoming("bad_topic", "12345"); // no matching topic

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "2.0.2b2" #define EMSESP_APP_VERSION "2.0.2b3"