Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
hpanther
2020-11-14 22:54:53 +01:00
66 changed files with 2648 additions and 1854 deletions

View File

@@ -71,7 +71,7 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request) {
id = "-1";
}
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_LARGE);
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_DYN);
JsonObject json = doc.to<JsonObject>();
bool ok = false;
@@ -98,10 +98,10 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request) {
cmd.c_str(),
data.c_str(),
id.c_str(),
ok ? F("OK") : F("Invalid"));
ok ? PSTR("OK") : PSTR("Invalid"));
EMSESP::logger().info(debug.c_str());
if (json.size()) {
char buffer2[EMSESP_MAX_JSON_SIZE_LARGE];
char buffer2[EMSESP_MAX_JSON_SIZE_DYN];
serializeJson(doc, buffer2);
EMSESP::logger().info("json (max 255 chars): %s", buffer2);
}
@@ -110,7 +110,7 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request) {
// if we have returned data in JSON format, send this to the WEB
if (json.size()) {
doc.shrinkToFit();
char buffer[EMSESP_MAX_JSON_SIZE_LARGE];
char buffer[EMSESP_MAX_JSON_SIZE_DYN];
serializeJsonPretty(doc, buffer);
request->send(200, "text/plain", buffer);
} else {

View File

@@ -30,6 +30,7 @@ WebSettingsService::WebSettingsService(AsyncWebServer * server, FS * fs, Securit
void WebSettings::read(WebSettings & settings, JsonObject & root) {
root["tx_mode"] = settings.tx_mode;
root["ems_bus_id"] = settings.ems_bus_id;
root["syslog_enabled"] = settings.syslog_enabled;
root["syslog_level"] = settings.syslog_level;
root["syslog_mark_interval"] = settings.syslog_mark_interval;
root["syslog_host"] = settings.syslog_host;
@@ -50,6 +51,7 @@ void WebSettings::read(WebSettings & settings, JsonObject & root) {
StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings) {
settings.tx_mode = root["tx_mode"] | EMSESP_DEFAULT_TX_MODE;
settings.ems_bus_id = root["ems_bus_id"] | EMSESP_DEFAULT_EMS_BUS_ID;
settings.syslog_enabled = root["syslog_enabled"] | EMSESP_DEFAULT_SYSLOG_ENABLED;
settings.syslog_level = root["syslog_level"] | EMSESP_DEFAULT_SYSLOG_LEVEL;
settings.syslog_mark_interval = root["syslog_mark_interval"] | EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL;
settings.syslog_host = root["syslog_host"] | EMSESP_DEFAULT_SYSLOG_HOST;

View File

@@ -27,7 +27,8 @@
#define EMSESP_DEFAULT_TX_MODE 1 // EMS1.0
#define EMSESP_DEFAULT_EMS_BUS_ID 0x0B // service key
#define EMSESP_DEFAULT_SYSLOG_LEVEL -1 // OFF
#define EMSESP_DEFAULT_SYSLOG_ENABLED false
#define EMSESP_DEFAULT_SYSLOG_LEVEL 3 // ERR
#define EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL 0
#define EMSESP_DEFAULT_SYSLOG_HOST ""
#define EMSESP_DEFAULT_MASTER_THERMOSTAT 0 // not set
@@ -67,6 +68,7 @@ class WebSettings {
uint8_t master_thermostat;
bool shower_timer;
bool shower_alert;
bool syslog_enabled;
int8_t syslog_level; // uuid::log::Level
uint32_t syslog_mark_interval;
String syslog_host;

View File

@@ -68,7 +68,9 @@ void Command::add(const uint8_t device_type, const uint8_t device_id, const __Fl
cmdfunctions_.emplace_back(device_type, cmd, cb, nullptr);
// see if we need to subscribe
Mqtt::register_command(device_type, device_id, cmd, cb);
if (Mqtt::enabled()) {
Mqtt::register_command(device_type, device_id, cmd, cb);
}
}
// add a command to the list, which does return json object as output

View File

@@ -20,7 +20,7 @@
#include "emsesp.h"
#include "version.h"
#if defined(EMSESP_DEBUG)
#if defined(EMSESP_TEST)
#include "test/test.h"
#endif
@@ -110,28 +110,6 @@ void EMSESPShell::add_console_commands() {
// commands->remove_context_commands(ShellContext::MAIN);
commands->remove_all_commands();
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(fetch)},
[&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.printfln(F("Requesting data from EMS devices"));
EMSESP::fetch_device_values();
});
commands->add_command(ShellContext::MAIN,
CommandFlags::ADMIN,
flash_string_vector{F_(publish)},
flash_string_vector{F_(ha_optional)},
[&](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.empty()) {
EMSESP::publish_all();
shell.printfln(F("Published all data to MQTT"));
} else {
EMSESP::publish_all(true);
shell.printfln(F("Published all data to MQTT, including HA configs"));
}
});
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(show)},
@@ -302,13 +280,17 @@ void EMSESPShell::add_console_commands() {
"local");
});
#ifndef EMSESP_STANDALONE
commands->add_command(ShellContext::MAIN,
CommandFlags::ADMIN,
flash_string_vector{F_(send), F_(telegram)},
flash_string_vector{F_(data_mandatory)},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
EMSESP::send_raw_telegram(arguments.front().c_str());
CommandFlags::USER,
flash_string_vector{F_(set), F_(timeout)},
flash_string_vector{F_(n_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
uint16_t value = Helpers::atoint(arguments.front().c_str());
telnet_.initial_idle_timeout(value * 60);
shell.printfln(F("Telnet timout is %d minutes"), value);
});
#endif
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
@@ -446,11 +428,7 @@ void EMSESPShell::add_console_commands() {
return {};
});
/*
* add all the submenu contexts...
*/
// System
// System context menu
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(system)},
@@ -491,33 +469,21 @@ void Console::enter_custom_context(Shell & shell, unsigned int context) {
// each custom context has the common commands like log, help, exit, su etc
void Console::load_standard_commands(unsigned int context) {
#if defined(EMSESP_DEBUG)
#if defined(EMSESP_TEST)
EMSESPShell::commands->add_command(context,
CommandFlags::USER,
flash_string_vector{F_(test)},
flash_string_vector{F_(name_optional)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
[](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.size() == 0) {
Test::run_test(shell, "default");
Test::run_test_shell(shell, "default");
} else {
Test::run_test(shell, arguments.front());
Test::run_test_shell(shell, arguments.front());
}
});
#endif
#if defined(EMSESP_STANDALONE)
EMSESPShell::commands->add_command(context,
CommandFlags::USER,
flash_string_vector{F("t")},
flash_string_vector{F_(name_optional)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
if (arguments.size() == 0) {
Test::run_test(shell, "default");
} else {
Test::run_test(shell, arguments.front());
}
});
#endif
EMSESPShell::commands->add_command(
context,

View File

@@ -84,12 +84,12 @@ void DallasSensor::loop() {
bus_.reset_search();
state_ = State::SCANNING;
} else if (time_now - last_activity_ > READ_TIMEOUT_MS) {
LOG_ERROR(F("Sensor read timeout"));
LOG_WARNING(F("Dallas sensor read timeout"));
state_ = State::IDLE;
}
} else if (state_ == State::SCANNING) {
if (time_now - last_activity_ > SCAN_TIMEOUT_MS) {
LOG_ERROR(F("Sensor scan timeout"));
LOG_ERROR(F("Dallas sensor scan timeout"));
state_ = State::IDLE;
} else {
uint8_t addr[ADDR_LEN] = {0};
@@ -129,11 +129,11 @@ void DallasSensor::loop() {
break;
default:
LOG_ERROR(F("Unknown sensor %s"), Sensor(addr).to_string().c_str());
LOG_ERROR(F("Unknown dallas sensor %s"), Sensor(addr).to_string().c_str());
break;
}
} else {
LOG_ERROR(F("Invalid sensor %s"), Sensor(addr).to_string().c_str());
LOG_ERROR(F("Invalid dallas sensor %s"), Sensor(addr).to_string().c_str());
}
} else {
if (!parasite_) {
@@ -309,7 +309,7 @@ bool DallasSensor::export_values(JsonObject & json) {
}
// send all dallas sensor values as a JSON package to MQTT
void DallasSensor::publish_values() {
void DallasSensor::publish_values(const bool force) {
uint8_t num_sensors = sensors_.size();
if (num_sensors == 0) {
@@ -340,7 +340,7 @@ void DallasSensor::publish_values() {
// create the HA MQTT config
// to e.g. homeassistant/sensor/ems-esp/dallas_28-233D-9497-0C03/config
if (mqtt_format_ == Mqtt::Format::HA) {
if (!(registered_ha_[sensor_no - 1])) {
if (!(registered_ha_[sensor_no - 1]) || force) {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> config;
config["dev_cla"] = F("temperature");

View File

@@ -58,7 +58,7 @@ class DallasSensor {
void start();
void loop();
void publish_values();
void publish_values(const bool force);
void reload();
bool updated_values();

View File

@@ -87,7 +87,7 @@
{107, DeviceType::THERMOSTAT, F("FR100"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_2}, // older model
{108, DeviceType::THERMOSTAT, F("FR110"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_2}, // older model
{111, DeviceType::THERMOSTAT, F("FR10"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},
{147, DeviceType::THERMOSTAT, F("FR50"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},
{147, DeviceType::THERMOSTAT, F("FR50"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_2},
{191, DeviceType::THERMOSTAT, F("FR120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_2}, // older model
{192, DeviceType::THERMOSTAT, F("FW120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},

View File

@@ -74,11 +74,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
// create the config topics for Home Assistant MQTT Discovery
// for each of the main elements
void Boiler::register_mqtt_ha_config(bool force) {
if ((mqtt_ha_config_ && !force)) {
return;
}
void Boiler::register_mqtt_ha_config() {
if (!Mqtt::connected()) {
return;
}
@@ -141,8 +137,17 @@ void Boiler::register_mqtt_ha_config(bool force) {
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(burnWorkMin), this->device_type(), "burnWorkMin", F_(min), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(heatWorkMin), this->device_type(), "heatWorkMin", F_(min), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(UBAuptime), this->device_type(), "UBAuptime", F_(min), nullptr);
mqtt_ha_config_ = true; // done
}
// ww
// create the config topics for Home Assistant MQTT Discovery
// for each of the ww elements
void Boiler::register_mqtt_ha_config_ww() {
if (!Mqtt::connected()) {
return;
}
// ww
Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWSelTemp), this->device_type(), "wWSelTemp", F_(degrees), F_(iconcruise));
Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWSetTemp), this->device_type(), "wWSetTemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWDisinfectionTemp), this->device_type(), "wWDisinfectionTemp", F_(degrees), F_(icontemperature));
@@ -170,14 +175,15 @@ void Boiler::register_mqtt_ha_config(bool force) {
Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWStarts), this->device_type(), "wWStarts", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, F_(mqtt_suffix_ww), F_(wWWorkM), this->device_type(), "wWWorkM", F_(min), nullptr);
mqtt_ha_config_ = true; // done
mqtt_ha_config_ww_ = true; // done
}
// send stuff to the Web UI
void Boiler::device_info_web(JsonArray & root) {
// fetch the values into a JSON document
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_LARGE);
JsonObject json = doc.to<JsonObject>();
// DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_LARGE);
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc;
JsonObject json = doc.to<JsonObject>();
if (!export_values_main(json)) {
return; // empty
}
@@ -618,19 +624,29 @@ bool Boiler::export_values_main(JsonObject & json) {
void Boiler::publish_values(JsonObject & json, bool force) {
// handle HA first
if (Mqtt::mqtt_format() == Mqtt::Format::HA) {
register_mqtt_ha_config(force);
if (force) {
mqtt_ha_config_ = false;
mqtt_ha_config_ww_ = false;
}
// register ww in next cycle if both unregistered
if (!mqtt_ha_config_) {
register_mqtt_ha_config();
return;
} else if (!mqtt_ha_config_ww_) {
register_mqtt_ha_config_ww();
return;
}
}
DynamicJsonDocument doc_main(EMSESP_MAX_JSON_SIZE_LARGE);
JsonObject json_main = doc_main.to<JsonObject>();
if (export_values_main(json_main)) {
Mqtt::publish(F("boiler_data"), doc_main.as<JsonObject>());
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc;
JsonObject json_data = doc.to<JsonObject>();
if (export_values_main(json_data)) {
Mqtt::publish(F("boiler_data"), json_data);
}
json_data.clear();
DynamicJsonDocument doc_ww(EMSESP_MAX_JSON_SIZE_LARGE);
JsonObject json_ww = doc_ww.to<JsonObject>();
if (export_values_ww(json_ww)) {
Mqtt::publish(F("boiler_data_ww"), doc_ww.as<JsonObject>());
if (export_values_ww(json_data)) {
Mqtt::publish(F("boiler_data_ww"), json_data);
}
// send out heating and tapwater status
@@ -651,13 +667,14 @@ void Boiler::show_values(uuid::console::Shell & shell) {
EMSdevice::show_values(shell); // for showing the header
// fetch the values into a JSON document
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_LARGE);
JsonObject json = doc.to<JsonObject>();
// DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_LARGE);
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc;
JsonObject json = doc.to<JsonObject>();
if (!export_values_main(json)) {
return; // empty
}
export_values_ww(json); // append ww values
doc.shrinkToFit();
// doc.shrinkToFit();
print_value_json(shell, F("heatingActive"), nullptr, F_(heatingActive), nullptr, json);
print_value_json(shell, F("tapwaterActive"), nullptr, F_(tapwaterActive), nullptr, json);

View File

@@ -47,13 +47,15 @@ class Boiler : public EMSdevice {
private:
static uuid::log::Logger logger_;
void register_mqtt_ha_config(bool force);
void register_mqtt_ha_config();
void register_mqtt_ha_config_ww();
void check_active();
bool export_values_main(JsonObject & doc);
bool export_values_ww(JsonObject & doc);
bool changed_ = false;
bool mqtt_ha_config_ = false; // HA MQTT Discovery
bool changed_ = false;
bool mqtt_ha_config_ = false; // HA MQTT Discovery
bool mqtt_ha_config_ww_ = false; // HA MQTT Discovery
static constexpr uint8_t EMS_TYPE_UBAParameterWW = 0x33;
static constexpr uint8_t EMS_TYPE_UBAFunctionTest = 0x1D;

View File

@@ -49,8 +49,8 @@ bool Heatpump::export_values(JsonObject & json) {
void Heatpump::device_info_web(JsonArray & root) {
// fetch the values into a JSON document
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
JsonObject json = doc.to<JsonObject>();
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
JsonObject json = doc.to<JsonObject>();
if (!export_values(json)) {
return; // empty
}
@@ -64,8 +64,8 @@ void Heatpump::show_values(uuid::console::Shell & shell) {
EMSdevice::show_values(shell); // always call this to show header
// fetch the values into a JSON document
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
JsonObject json = doc.to<JsonObject>();
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
JsonObject json = doc.to<JsonObject>();
if (!export_values(json)) {
return; // empty
}
@@ -78,27 +78,26 @@ void Heatpump::show_values(uuid::console::Shell & shell) {
void Heatpump::publish_values(JsonObject & json, bool force) {
// handle HA first
if (Mqtt::mqtt_format() == Mqtt::Format::HA) {
register_mqtt_ha_config(force);
if (!mqtt_ha_config_ || force) {
register_mqtt_ha_config();
return;
}
}
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
JsonObject json_data = doc.to<JsonObject>();
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
JsonObject json_data = doc.to<JsonObject>();
if (export_values(json_data)) {
Mqtt::publish(F("heatpump_data"), doc.as<JsonObject>());
}
}
void Heatpump::register_mqtt_ha_config(bool force) {
if ((mqtt_ha_config_ && !force)) {
return;
}
void Heatpump::register_mqtt_ha_config() {
if (!Mqtt::connected()) {
return;
}
// Create the Master device
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
doc["name"] = F_(EMSESP);
doc["uniq_id"] = F_(heatpump);
doc["ic"] = F_(iconheatpump);

View File

@@ -45,7 +45,7 @@ class Heatpump : public EMSdevice {
private:
static uuid::log::Logger logger_;
void register_mqtt_ha_config(bool force);
void register_mqtt_ha_config();
uint8_t airHumidity_ = EMS_VALUE_UINT_NOTSET;
uint8_t dewTemperature_ = EMS_VALUE_UINT_NOTSET;

View File

@@ -62,7 +62,7 @@ void Mixer::device_info_web(JsonArray & root) {
}
// fetch the values into a JSON document
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
JsonObject json = doc.to<JsonObject>();
if (!export_values_format(Mqtt::Format::SINGLE, json)) {
return; // empty
@@ -101,7 +101,7 @@ void Mixer::show_values(uuid::console::Shell & shell) {
}
// fetch the values into a JSON document
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
JsonObject json = doc.to<JsonObject>();
if (!export_values_format(Mqtt::Format::SINGLE, json)) {
return; // empty
@@ -128,13 +128,16 @@ void Mixer::show_values(uuid::console::Shell & shell) {
void Mixer::publish_values(JsonObject & json, bool force) {
// handle HA first
if (Mqtt::mqtt_format() == Mqtt::Format::HA) {
register_mqtt_ha_config(force);
if (!mqtt_ha_config_ || force) {
register_mqtt_ha_config();
return;
}
}
if (Mqtt::mqtt_format() == Mqtt::Format::SINGLE) {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
JsonObject json = doc.to<JsonObject>();
if (export_values_format(Mqtt::mqtt_format(), json)) {
JsonObject json_data = doc.to<JsonObject>();
if (export_values_format(Mqtt::mqtt_format(), json_data)) {
char topic[30];
if (type() == Type::HC) {
snprintf_P(topic, 30, PSTR("mixer_data_hc%d"), hc_);
@@ -150,18 +153,13 @@ void Mixer::publish_values(JsonObject & json, bool force) {
}
// publish config topic for HA MQTT Discovery
void Mixer::register_mqtt_ha_config(bool force) {
if ((mqtt_ha_config_ && !force)) {
return;
}
void Mixer::register_mqtt_ha_config() {
if (!Mqtt::connected()) {
return;
}
// if we don't have valid values for this HC don't add it ever again
if (!Helpers::hasValue(status_)) {
mqtt_ha_config_ = true;
if (!Helpers::hasValue(pumpStatus_)) {
return;
}
@@ -194,7 +192,7 @@ void Mixer::register_mqtt_ha_config(bool force) {
std::string topic(100, '\0');
if (this->type() == Type::HC) {
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/mixer_hc%d/config"), hc_);
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/sensor/ems-esp/mixer_hc%d/config"), hc_);
Mqtt::publish_retain(topic, doc.as<JsonObject>(), true); // publish the config payload with retain flag
char hc_name[10];
snprintf_P(hc_name, sizeof(hc_name), PSTR("hc%d"), hc_);
@@ -204,7 +202,7 @@ void Mixer::register_mqtt_ha_config(bool force) {
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(valveStatus), this->device_type(), "valveStatus", nullptr, nullptr);
} else {
// WWC
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/mixer_wwc%d/config"), hc_);
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/sensor/ems-esp/mixer_wwc%d/config"), hc_);
Mqtt::publish_retain(topic, doc.as<JsonObject>(), true); // publish the config payload with retain flag
char wwc_name[10];
snprintf_P(wwc_name, sizeof(wwc_name), PSTR("wwc%d"), hc_);

View File

@@ -46,7 +46,7 @@ class Mixer : public EMSdevice {
static uuid::log::Logger logger_;
bool export_values_format(uint8_t mqtt_format, JsonObject & doc);
void register_mqtt_ha_config(bool force);
void register_mqtt_ha_config();
void process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram);
void process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> telegram);

View File

@@ -49,6 +49,7 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
register_telegram_type(0x036A, F("SM100Status2"), false, [&](std::shared_ptr<const Telegram> t) { process_SM100Status2(t); });
register_telegram_type(0x0380, F("SM100CollectorConfig"), true, [&](std::shared_ptr<const Telegram> t) { process_SM100CollectorConfig(t); });
register_telegram_type(0x038E, F("SM100Energy"), true, [&](std::shared_ptr<const Telegram> t) { process_SM100Energy(t); });
register_telegram_type(0x0391, F("SM100Time"), true, [&](std::shared_ptr<const Telegram> t) { process_SM100Time(t); });
register_mqtt_cmd(F("SM100Tank1MaxTemp"), [&](const char * value, const int8_t id) { return set_SM100Tank1MaxTemp(value, id); });
}
@@ -83,6 +84,7 @@ void Solar::device_info_web(JsonArray & root) {
print_value_json(root, F("energyLastHour"), nullptr, F_(energyLastHour), F_(wh), json);
print_value_json(root, F("energyToday"), nullptr, F_(energyToday), F_(wh), json);
print_value_json(root, F("energyTotal"), nullptr, F_(energyTotal), F_(kwh), json);
print_value_json(root, F("pumpWorkMin"), nullptr, F_(pumpWorkMin), F_(min), json);
if (Helpers::hasValue(pumpWorkMin_)) {
JsonObject dataElement = root.createNestedObject();
@@ -118,6 +120,7 @@ void Solar::show_values(uuid::console::Shell & shell) {
print_value_json(shell, F("energyLastHour"), nullptr, F_(energyLastHour), F_(wh), json);
print_value_json(shell, F("energyToday"), nullptr, F_(energyToday), F_(wh), json);
print_value_json(shell, F("energyTotal"), nullptr, F_(energyTotal), F_(kwh), json);
print_value_json(shell, F("pumpWorkMin"), nullptr, F_(pumpWorkMin), F_(min), json);
if (Helpers::hasValue(pumpWorkMin_)) {
shell.printfln(F(" %s: %d days %d hours %d minutes"),
@@ -132,7 +135,10 @@ void Solar::show_values(uuid::console::Shell & shell) {
void Solar::publish_values(JsonObject & json, bool force) {
// handle HA first
if (Mqtt::mqtt_format() == Mqtt::Format::HA) {
register_mqtt_ha_config(force);
if ((!mqtt_ha_config_ || force)) {
register_mqtt_ha_config();
return;
}
}
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
@@ -147,17 +153,13 @@ void Solar::publish_values(JsonObject & json, bool force) {
}
// publish config topic for HA MQTT Discovery
void Solar::register_mqtt_ha_config(bool force) {
if ((mqtt_ha_config_ && !force)) {
return;
}
void Solar::register_mqtt_ha_config() {
if (!Mqtt::connected()) {
return;
}
// Create the Master device
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
doc["name"] = F_(EMSESP);
doc["uniq_id"] = F_(solar);
doc["ic"] = F_(iconthermostat);
@@ -183,10 +185,10 @@ void Solar::register_mqtt_ha_config(bool force) {
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(heatExchangerTemp), this->device_type(), "heatExchangerTemp", F_(degrees), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(solarPumpModulation), this->device_type(), "solarPumpModulation", F_(percent), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(cylinderPumpModulation), this->device_type(), "cylinderPumpModulation", F_(percent), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(pumpWorkMin), this->device_type(), "pumpWorkMin", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(energyLastHour), this->device_type(), "energyLastHour", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(energyToday), this->device_type(), "energyToday", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(energyTotal), this->device_type(), "energyTotal", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(pumpWorkMin), this->device_type(), "pumpWorkMin", F_(min), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(energyLastHour), this->device_type(), "energyLastHour", F_(wh), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(energyToday), this->device_type(), "energyToday", F_(wh), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(energyTotal), this->device_type(), "energyTotal", F_(kwh), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(solarPump), this->device_type(), "solarPump", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(valveStatus), this->device_type(), "valveStatus", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(tankHeated), this->device_type(), "tankHeated", nullptr, nullptr);
@@ -450,6 +452,13 @@ void Solar::process_SM100Energy(std::shared_ptr<const Telegram> telegram) {
changed_ |= telegram->read_value(energyTotal_, 8); // total / 10 in kWh
}
/*
* SM100Time - type 0x0391 EMS+ for pump working time
*/
void Solar::process_SM100Time(std::shared_ptr<const Telegram> telegram) {
changed_ |= telegram->read_value(pumpWorkMin_, 1, 3);
}
/*
* Junkers ISM1 Solar Module - type 0x0103 EMS+ for energy readings
* e.g. B0 00 FF 00 00 03 32 00 00 00 00 13 00 D6 00 00 00 FB D0 F0

View File

@@ -44,7 +44,7 @@ class Solar : public EMSdevice {
private:
static uuid::log::Logger logger_;
void register_mqtt_ha_config(bool force);
void register_mqtt_ha_config();
int16_t collectorTemp_ = EMS_VALUE_SHORT_NOTSET; // TS1: Temperature sensor for collector array 1
int16_t tankBottomTemp_ = EMS_VALUE_SHORT_NOTSET; // TS2: Temperature sensor 1 cylinder, bottom (solar thermal system)
@@ -106,6 +106,7 @@ class Solar : public EMSdevice {
void process_SM100Status2(std::shared_ptr<const Telegram> telegram);
void process_SM100CollectorConfig(std::shared_ptr<const Telegram> telegram);
void process_SM100Energy(std::shared_ptr<const Telegram> telegram);
void process_SM100Time(std::shared_ptr<const Telegram> telegram);
void process_SM100wwTemperature(std::shared_ptr<const Telegram> telegram);
void process_SM100wwStatus(std::shared_ptr<const Telegram> telegram);

View File

@@ -114,10 +114,12 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
monitor_typeids = {0x02A5, 0x02A6, 0x02A7, 0x02A8};
set_typeids = {0x02B9, 0x02BA, 0x02BB, 0x02BC};
summer_typeids = {0x02AF, 0x02B0, 0x02B1, 0x02B2};
curve_typeids = {0x029B, 0x029C, 0x029D, 0x029E};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(monitor_typeids[i], F("RC300Monitor"), false, [&](std::shared_ptr<const Telegram> t) { process_RC300Monitor(t); });
register_telegram_type(set_typeids[i], F("RC300Set"), false, [&](std::shared_ptr<const Telegram> t) { process_RC300Set(t); });
register_telegram_type(summer_typeids[i], F("RC300Summer"), false, [&](std::shared_ptr<const Telegram> t) { process_RC300Summer(t); });
register_telegram_type(curve_typeids[i], F("RC300Curves"), false, [&](std::shared_ptr<const Telegram> t) { process_RC300Curve(t); });
}
register_telegram_type(0x2F5, F("RC300WWmode"), true, [&](std::shared_ptr<const Telegram> t) { process_RC300WWmode(t); });
register_telegram_type(0x31B, F("RC300WWtemp"), true, [&](std::shared_ptr<const Telegram> t) { process_RC300WWtemp(t); });
@@ -171,12 +173,15 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
for (uint8_t i = 0; i < summer_typeids.size(); i++) {
EMSESP::send_read_request(summer_typeids[i], device_id);
}
} // namespace emsesp
for (uint8_t i = 0; i < curve_typeids.size(); i++) {
EMSESP::send_read_request(curve_typeids[i], device_id);
}
}
// prepare data for Web UI
void Thermostat::device_info_web(JsonArray & root) {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc_main;
JsonObject json_main = doc_main.to<JsonObject>();
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc_main;
JsonObject json_main = doc_main.to<JsonObject>();
if (export_values_main(json_main)) {
print_value_json(root, F("time"), nullptr, F_(time), nullptr, json_main);
print_value_json(root, F("errorcode"), nullptr, F_(error), nullptr, json_main);
@@ -194,6 +199,7 @@ void Thermostat::device_info_web(JsonArray & root) {
print_value_json(root, F("wwmode"), nullptr, F_(wwmode), nullptr, json_main);
print_value_json(root, F("wwtemp"), nullptr, F_(wwtemp), nullptr, json_main);
print_value_json(root, F("wwtemplow"), nullptr, F_(wwtemplow), nullptr, json_main);
print_value_json(root, F("wwextra1"), nullptr, F_(wwextra1), nullptr, json_main);
print_value_json(root, F("wwcircmode"), nullptr, F_(wwcircmode), nullptr, json_main);
}
@@ -226,6 +232,8 @@ void Thermostat::device_info_web(JsonArray & root) {
print_value_json(root, F("designtemp"), FPSTR(prefix_str), F_(designtemp), F_(degrees), json);
print_value_json(root, F("roominfluence"), FPSTR(prefix_str), F_(roominfluence), F_(degrees), json);
print_value_json(root, F("flowtempoffset"), FPSTR(prefix_str), F_(flowtempoffset), F_(degrees), json);
print_value_json(root, F("minflowtemp"), FPSTR(prefix_str), F_(minflowtemp), F_(degrees), json);
print_value_json(root, F("maxflowtemp"), FPSTR(prefix_str), F_(maxflowtemp), F_(degrees), json);
print_value_json(root, F("summertemp"), FPSTR(prefix_str), F_(summertemp), F_(degrees), json);
print_value_json(root, F("summermode"), FPSTR(prefix_str), F_(summermode), F_(degrees), json);
print_value_json(root, F("mode"), FPSTR(prefix_str), F_(mode), nullptr, json);
@@ -278,6 +286,7 @@ void Thermostat::show_values(uuid::console::Shell & shell) {
print_value_json(shell, F("wwmode"), nullptr, F_(wwmode), nullptr, json_main);
print_value_json(shell, F("wwtemp"), nullptr, F_(wwtemp), nullptr, json_main);
print_value_json(shell, F("wwtemplow"), nullptr, F_(wwtemplow), nullptr, json_main);
print_value_json(shell, F("wwextra1"), nullptr, F_(wwextra1), nullptr, json_main);
print_value_json(shell, F("wwcircmode"), nullptr, F_(wwcircmode), nullptr, json_main);
}
@@ -310,6 +319,8 @@ void Thermostat::show_values(uuid::console::Shell & shell) {
print_value_json(shell, F("designtemp"), F_(2spaces), F_(designtemp), F_(degrees), json);
print_value_json(shell, F("roominfluence"), F_(2spaces), F_(roominfluence), F_(degrees), json);
print_value_json(shell, F("flowtempoffset"), F_(2spaces), F_(flowtempoffset), F_(degrees), json);
print_value_json(shell, F("minflowtemp"), F_(2spaces), F_(minflowtemp), F_(degrees), json);
print_value_json(shell, F("maxflowtemp"), F_(2spaces), F_(maxflowtemp), F_(degrees), json);
print_value_json(shell, F("summertemp"), F_(2spaces), F_(summertemp), F_(degrees), json);
print_value_json(shell, F("summermode"), F_(2spaces), F_(summermode), F_(degrees), json);
print_value_json(shell, F("mode"), F_(2spaces), F_(mode), nullptr, json);
@@ -325,10 +336,16 @@ void Thermostat::publish_values(JsonObject & json, bool force) {
if (EMSESP::actual_master_thermostat() != this->device_id()) {
return;
}
// see if we have already registered this with HA MQTT Discovery, if not send the config
if (Mqtt::mqtt_format() == Mqtt::Format::HA) {
if (!ha_config(force)) {
return;
}
}
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
JsonObject json_data = doc.to<JsonObject>();
bool has_data = false;
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc;
JsonObject json_data = doc.to<JsonObject>();
bool has_data = false;
// if MQTT is in single mode send out the main data to the thermostat_data topic
has_data |= export_values_main(json_data);
@@ -343,10 +360,6 @@ void Thermostat::publish_values(JsonObject & json, bool force) {
// if we're in HA or CUSTOM, send out the complete topic with all the data
if (Mqtt::mqtt_format() != Mqtt::Format::SINGLE && has_data) {
// see if we have already registered this with HA MQTT Discovery, if not send the config
if (Mqtt::mqtt_format() == Mqtt::Format::HA) {
ha_config(force);
}
Mqtt::publish(F("thermostat_data"), json_data);
}
}
@@ -473,6 +486,16 @@ bool Thermostat::export_values_main(JsonObject & rootThermostat) {
rootThermostat["wwtemplow"] = wwTempLow_;
}
// Warm water extra1
if (Helpers::hasValue(wwExtra1_)) {
rootThermostat["wwextra1"] = wwExtra1_;
}
// Warm water extra2
if (Helpers::hasValue(wwExtra2_)) {
rootThermostat["wwextra2"] = wwExtra2_;
}
// Warm Water circulation mode
if (Helpers::hasValue(wwCircMode_)) {
char s[7];
@@ -600,6 +623,16 @@ bool Thermostat::export_values_hc(uint8_t mqtt_format, JsonObject & rootThermost
dataThermostat["flowtempoffset"] = hc->flowtempoffset;
}
// Min Flow temperature offset
if (Helpers::hasValue(hc->minflowtemp)) {
dataThermostat["minflowtemp"] = hc->minflowtemp;
}
// Max Flow temperature offset
if (Helpers::hasValue(hc->maxflowtemp)) {
dataThermostat["maxflowtemp"] = hc->maxflowtemp;
}
// Summer temperature
if (Helpers::hasValue(hc->summertemp)) {
dataThermostat["summertemp"] = hc->summertemp;
@@ -654,23 +687,32 @@ bool Thermostat::export_values_hc(uint8_t mqtt_format, JsonObject & rootThermost
}
// set up HA MQTT Discovery
void Thermostat::ha_config(bool force) {
bool Thermostat::ha_config(bool force) {
if (!Mqtt::connected()) {
return;
return false;
}
if (force) {
for (const auto & hc : heating_circuits_) {
hc->ha_registered(false);
}
ha_registered(false);
}
if (force || !ha_registered()) {
if (!ha_registered()) {
register_mqtt_ha_config();
ha_registered(true);
return false;
}
// check to see which heating circuits need publishing
for (const auto & hc : heating_circuits_) {
if (force || (hc->is_active() && !hc->ha_registered())) {
if (hc->is_active() && !hc->ha_registered()) {
register_mqtt_ha_config(hc->hc_num());
hc->ha_registered(true);
return false;
}
}
return true;
}
// returns the heating circuit object based on the hc number
@@ -679,13 +721,15 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(const ui
// if hc_num is 0 then return the first existing hc in the list
if (hc_num == AUTO_HEATING_CIRCUIT) {
for (const auto & heating_circuit : heating_circuits_) {
return heating_circuit;
if (heating_circuit->is_active()) {
return heating_circuit;
}
}
}
// otherwise find a match
for (const auto & heating_circuit : heating_circuits_) {
if (heating_circuit->hc_num() == hc_num) {
if ((heating_circuit->hc_num() == hc_num) && heating_circuit->is_active()) {
return heating_circuit;
}
}
@@ -773,10 +817,10 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
return heating_circuits_.back(); // even after sorting, this should still point back to the newly created HC
}
// publish config topic for HA MQTT Discovery
// publish config topic for HA MQTT Discovery for main thermostat values
// homeassistant/climate/ems-esp/thermostat/config
void Thermostat::register_mqtt_ha_config() {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
doc["uniq_id"] = F("thermostat");
doc["ic"] = F("mdi:home-thermometer-outline");
@@ -808,6 +852,7 @@ void Thermostat::register_mqtt_ha_config() {
if (model == EMS_DEVICE_FLAG_RC300 || model == EMS_DEVICE_FLAG_RC100) {
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(dampedtemp), this->device_type(), "dampedtemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(building), this->device_type(), "building", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(minexttemp), this->device_type(), "minexttemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(floordry), this->device_type(), "floordry", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(floordrytemp), this->device_type(), "floordrytemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(wwmode), this->device_type(), "wwmode", nullptr, nullptr);
@@ -826,18 +871,18 @@ void Thermostat::register_mqtt_ha_config() {
}
}
// publish config topic for HA MQTT Discovery
// publish config topic for HA MQTT Discovery for each of the heating circuit
// e.g. homeassistant/climate/ems-esp/thermostat_hc1/config
void Thermostat::register_mqtt_ha_config(uint8_t hc_num) {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
char str1[40];
char str1[20];
snprintf_P(str1, sizeof(str1), PSTR("Thermostat hc%d"), hc_num);
char str2[40];
char str2[20];
snprintf_P(str2, sizeof(str2), PSTR("thermostat_hc%d"), hc_num);
char str3[40];
char str3[25];
snprintf_P(str3, sizeof(str3), PSTR("~/%s"), str2);
doc["mode_cmd_t"] = str3;
doc["temp_cmd_t"] = str3;
@@ -909,7 +954,7 @@ void Thermostat::register_mqtt_ha_config(uint8_t hc_num) {
// for each of the heating circuits
std::string topic2(100, '\0');
snprintf_P(&topic2[0], topic2.capacity() + 1, PSTR("thermostat_hc%d"), hc_num);
register_mqtt_topic(topic2, [=](const char * m) { return thermostat_ha_cmd(m, hc_num); });
register_mqtt_topic(topic2, [&](const char * m) { return thermostat_ha_cmd(m, hc_num); });
char hc_name[10]; // hc{1-4}
strlcpy(hc_name, "hc", 10);
@@ -918,6 +963,9 @@ void Thermostat::register_mqtt_ha_config(uint8_t hc_num) {
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(mode), this->device_type(), "mode", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(seltemp), this->device_type(), "seltemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(currtemp), this->device_type(), "currtemp", F_(degrees), F_(icontemperature));
uint8_t model = this->model();
switch (model) {
case EMS_DEVICE_FLAG_RC100:
@@ -927,6 +975,12 @@ void Thermostat::register_mqtt_ha_config(uint8_t hc_num) {
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(manualtemp), this->device_type(), "manualtemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(comforttemp), this->device_type(), "comforttemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(summertemp), this->device_type(), "summertemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(designtemp), this->device_type(), "designtemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(offsettemp), this->device_type(), "offsettemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(minflowtemp), this->device_type(), "minflowtemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(maxflowtemp), this->device_type(), "maxflowtemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(roominfluence), this->device_type(), "roominfluence", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(nofrosttemp), this->device_type(), "nofrosttemp", F_(degrees), F_(icontemperature));
break;
case EMS_DEVICE_FLAG_RC20_2:
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(daytemp), this->device_type(), "daytemp", F_(degrees), F_(icontemperature));
@@ -944,6 +998,8 @@ void Thermostat::register_mqtt_ha_config(uint8_t hc_num) {
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(summertemp), this->device_type(), "summertemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(nofrosttemp), this->device_type(), "nofrosttemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(roominfluence), this->device_type(), "roominfluence", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(minflowtemp), this->device_type(), "minflowtemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(maxflowtemp), this->device_type(), "maxflowtemp", F_(degrees), F_(icontemperature));
break;
case EMS_DEVICE_FLAG_JUNKERS:
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(modetype), this->device_type(), "modetype", nullptr, nullptr);
@@ -1094,6 +1150,15 @@ std::string Thermostat::mode_tostring(uint8_t mode) {
case HeatingCircuit::Mode::DESIGN:
return read_flash_string(F("design"));
break;
case HeatingCircuit::Mode::MINFLOW:
return read_flash_string(F("minflow"));
break;
case HeatingCircuit::Mode::MAXFLOW:
return read_flash_string(F("maxflow"));
break;
case HeatingCircuit::Mode::ROOMINFLUENCE:
return read_flash_string(F("roominfluence"));
break;
default:
case HeatingCircuit::Mode::UNKNOWN:
return read_flash_string(F("unknown"));
@@ -1268,8 +1333,28 @@ void Thermostat::process_RC300Set(std::shared_ptr<const Telegram> telegram) {
// types 0x2AF ff
void Thermostat::process_RC300Summer(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
changed_ |= telegram->read_value(hc->roominfluence, 0);
changed_ |= telegram->read_value(hc->offsettemp, 2);
changed_ |= telegram->read_value(hc->summertemp, 6);
changed_ |= telegram->read_value(hc->summer_setmode, 7);
if (hc->heatingtype < 3) {
changed_ |= telegram->read_value(hc->designtemp, 4);
} else {
changed_ |= telegram->read_value(hc->designtemp, 5);
}
changed_ |= telegram->read_value(hc->minflowtemp, 8);
}
// types 0x29B ff
void Thermostat::process_RC300Curve(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
changed_ |= telegram->read_value(hc->heatingtype, 1); // 1=radiator, 2=convector, 3=floor
changed_ |= telegram->read_value(hc->nofrosttemp, 6);
if (hc->heatingtype < 3) {
changed_ |= telegram->read_value(hc->maxflowtemp, 8);
} else {
changed_ |= telegram->read_value(hc->maxflowtemp, 7);
}
}
// types 0x31B (and 0x31C?)
@@ -1306,6 +1391,7 @@ void Thermostat::process_RC300OutdoorTemp(std::shared_ptr<const Telegram> telegr
// 0x240 RC300 parameter
void Thermostat::process_RC300Settings(std::shared_ptr<const Telegram> telegram) {
changed_ |= telegram->read_value(ibaBuildingType_, 9); // 1=light, 2=medium, 3=heavy
changed_ |= telegram->read_value(ibaMinExtTemperature_, 10);
}
// 0x267 RC300 floordrying
@@ -1370,10 +1456,13 @@ void Thermostat::process_RC35Set(std::shared_ptr<const Telegram> telegram) {
changed_ |= telegram->read_value(hc->summertemp, 22); // is * 1
changed_ |= telegram->read_value(hc->nofrosttemp, 23); // is * 1
changed_ |= telegram->read_value(hc->flowtempoffset, 24); // is * 1, only in mixed circuits
changed_ |= telegram->read_value(hc->minflowtemp, 16);
if (hc->heatingtype == 3) {
changed_ |= telegram->read_value(hc->designtemp, 36); // is * 1
changed_ |= telegram->read_value(hc->designtemp, 36); // is * 1
changed_ |= telegram->read_value(hc->maxflowtemp, 35); // is * 1
} else {
changed_ |= telegram->read_value(hc->designtemp, 17); // is * 1
changed_ |= telegram->read_value(hc->designtemp, 17); // is * 1
changed_ |= telegram->read_value(hc->maxflowtemp, 15); // is * 1
}
}
@@ -1564,24 +1653,6 @@ bool Thermostat::set_control(const char * value, const int8_t id) {
return true;
}
// Set roominfluence
bool Thermostat::set_roominfluence(const char * value, const int8_t id) {
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id;
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num);
if (hc == nullptr) {
LOG_WARNING(F("Set roominfluence: Heating Circuit %d not found or activated"), hc_num);
return false;
}
int t = 0;
if (!Helpers::value2number(value, t)) {
LOG_WARNING(F("Set roominfluence: Invalid value"));
return false;
}
LOG_INFO(F("Setting roominfluence to %d"), t);
write_command(set_typeids[hc->hc_num() - 1], 4, t, set_typeids[hc->hc_num() - 1]);
return true;
}
// sets the thermostat ww working mode, where mode is a string, ems and ems+
bool Thermostat::set_wwmode(const char * value, const int8_t id) {
uint8_t set = 0xFF;
@@ -1627,6 +1698,19 @@ bool Thermostat::set_wwtemplow(const char * value, const int8_t id) {
return true;
}
// Set ww onetime RC300, ems+
bool Thermostat::set_wwonetime(const char * value, const int8_t id) {
bool b = false;
if (!Helpers::value2bool(value, b)) {
LOG_WARNING(F("Set warm water onetime: Invalid value"));
return false;
}
char s[7];
LOG_INFO(F("Setting warm water onetime to %s"), Helpers::render_boolean(s, b));
write_command(0x02F5, 11, b ? 0xFF : 0x00, 0x031D);
return true;
}
// sets the thermostat ww circulation working mode, where mode is a string
bool Thermostat::set_wwcircmode(const char * value, const int8_t id) {
@@ -1957,6 +2041,15 @@ bool Thermostat::set_temperature(const float temperature, const std::string & mo
if (mode_tostring(HeatingCircuit::Mode::DESIGN) == mode) {
return set_temperature(temperature, HeatingCircuit::Mode::DESIGN, hc_num);
}
if (mode_tostring(HeatingCircuit::Mode::MINFLOW) == mode) {
return set_temperature(temperature, HeatingCircuit::Mode::MINFLOW, hc_num);
}
if (mode_tostring(HeatingCircuit::Mode::MAXFLOW) == mode) {
return set_temperature(temperature, HeatingCircuit::Mode::MAXFLOW, hc_num);
}
if (mode_tostring(HeatingCircuit::Mode::ROOMINFLUENCE) == mode) {
return set_temperature(temperature, HeatingCircuit::Mode::ROOMINFLUENCE, hc_num);
}
LOG_WARNING(F("Set temperature: Invalid mode"));
return false;
@@ -2005,6 +2098,49 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
case HeatingCircuit::Mode::ECO:
offset = 0x04; // eco offset
break;
case HeatingCircuit::Mode::OFFSET:
offset = 2;
set_typeid = summer_typeids[hc->hc_num() - 1];
validate_typeid = set_typeid;
break;
case HeatingCircuit::Mode::DESIGN:
set_typeid = summer_typeids[hc->hc_num() - 1];
validate_typeid = set_typeid;
if (hc->heatingtype == 3) {
offset = 5;
} else {
offset = 4;
}
factor = 1;
break;
case HeatingCircuit::Mode::MINFLOW:
set_typeid = summer_typeids[hc->hc_num() - 1];
validate_typeid = set_typeid;
offset = 8;
factor = 1;
break;
case HeatingCircuit::Mode::MAXFLOW:
set_typeid = curve_typeids[hc->hc_num() - 1];
validate_typeid = set_typeid;
if (hc->heatingtype == 3) {
offset = 7;
} else {
offset = 8;
}
factor = 1;
break;
case HeatingCircuit::Mode::NOFROST:
set_typeid = curve_typeids[hc->hc_num() - 1];
validate_typeid = set_typeid;
offset = 6;
factor = 1;
break;
case HeatingCircuit::Mode::ROOMINFLUENCE:
set_typeid = summer_typeids[hc->hc_num() - 1];
validate_typeid = set_typeid;
offset = 0;
factor = 1;
break;
default:
case HeatingCircuit::Mode::AUTO:
uint8_t mode_ = hc->get_mode(this->flags());
@@ -2067,6 +2203,22 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
offset = EMS_OFFSET_RC35Set_temp_nofrost;
factor = 1;
break;
case HeatingCircuit::Mode::ROOMINFLUENCE:
offset = 4;
factor = 1;
break;
case HeatingCircuit::Mode::MINFLOW:
offset = 16;
factor = 1;
break;
case HeatingCircuit::Mode::MAXFLOW:
if (hc->heatingtype == 3) {
offset = 35;
} else {
offset = 15;
}
factor = 1;
break;
default:
case HeatingCircuit::Mode::AUTO: // automatic selection, if no type is defined, we use the standard code
validate_typeid = monitor_typeids[hc->hc_num() - 1]; //get setpoint roomtemp back
@@ -2226,6 +2378,18 @@ bool Thermostat::set_flowtempoffset(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::FLOWOFFSET);
}
bool Thermostat::set_maxflowtemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::MAXFLOW);
}
bool Thermostat::set_minflowtemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::MINFLOW);
}
bool Thermostat::set_roominfluence(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::ROOMINFLUENCE);
}
// API commands for MQTT and Console
void Thermostat::add_commands() {
// if this thermostat doesn't support write, don't add the commands
@@ -2250,7 +2414,15 @@ void Thermostat::add_commands() {
register_mqtt_cmd(F("wwmode"), [&](const char * value, const int8_t id) { return set_wwmode(value, id); });
register_mqtt_cmd(F("wwtemp"), [&](const char * value, const int8_t id) { return set_wwtemp(value, id); });
register_mqtt_cmd(F("wwtemplow"), [&](const char * value, const int8_t id) { return set_wwtemplow(value, id); });
register_mqtt_cmd(F("wwonetime"), [&](const char * value, const int8_t id) { return set_wwonetime(value, id); });
register_mqtt_cmd(F("building"), [&](const char * value, const int8_t id) { return set_building(value, id); });
register_mqtt_cmd(F("nofrosttemp"), [&](const char * value, const int8_t id) { return set_nofrosttemp(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) { return set_offsettemp(value, id); });
register_mqtt_cmd(F("minflowtemp"), [&](const char * value, const int8_t id) { return set_minflowtemp(value, id); });
register_mqtt_cmd(F("maxflowtemp"), [&](const char * value, const int8_t id) { return set_maxflowtemp(value, id); });
register_mqtt_cmd(F("minexttemp"), [&](const char * value, const int8_t id) { return set_minexttemp(value, id); });
register_mqtt_cmd(F("roominfluence"), [&](const char * value, const int8_t id) { return set_roominfluence(value, id); });
break;
case EMS_DEVICE_FLAG_RC20_2:
register_mqtt_cmd(F("nighttemp"), [&](const char * value, const int8_t id) { return set_nighttemp(value, id); });
@@ -2280,6 +2452,8 @@ void Thermostat::add_commands() {
register_mqtt_cmd(F("wwcircmode"), [&](const char * value, const int8_t id) { return set_wwcircmode(value, id); });
register_mqtt_cmd(F("roominfluence"), [&](const char * value, const int8_t id) { return set_roominfluence(value, id); });
register_mqtt_cmd(F("flowtempoffset"), [&](const char * value, const int8_t id) { return set_flowtempoffset(value, id); });
register_mqtt_cmd(F("minflowtemp"), [&](const char * value, const int8_t id) { return set_minflowtemp(value, id); });
register_mqtt_cmd(F("maxflowtemp"), [&](const char * value, const int8_t id) { return set_maxflowtemp(value, id); });
break;
case EMS_DEVICE_FLAG_JUNKERS:
register_mqtt_cmd(F("nofrosttemp"), [&](const char * value, const int8_t id) { return set_nofrosttemp(value, id); });
@@ -2291,5 +2465,4 @@ void Thermostat::add_commands() {
}
}
} // namespace emsesp

View File

@@ -58,13 +58,15 @@ class Thermostat : public EMSdevice {
uint8_t heatingtype = EMS_VALUE_UINT_NOTSET; // type of heating: 1 radiator, 2 convectors, 3 floors, 4 room supply
uint8_t targetflowtemp = EMS_VALUE_UINT_NOTSET;
uint8_t summertemp = EMS_VALUE_UINT_NOTSET;
uint8_t nofrosttemp = EMS_VALUE_UINT_NOTSET;
int8_t nofrosttemp = EMS_VALUE_INT_NOTSET; // signed -20°C to +10°C
uint8_t designtemp = EMS_VALUE_UINT_NOTSET; // heating curve design temp at MinExtTemp
int8_t offsettemp = EMS_VALUE_INT_NOTSET; // heating curve offest temp at roomtemp signed!
uint8_t manualtemp = EMS_VALUE_UINT_NOTSET;
uint8_t summer_setmode = EMS_VALUE_UINT_NOTSET;
uint8_t roominfluence = EMS_VALUE_UINT_NOTSET;
uint8_t flowtempoffset = EMS_VALUE_UINT_NOTSET;
uint8_t minflowtemp = EMS_VALUE_UINT_NOTSET;
uint8_t maxflowtemp = EMS_VALUE_UINT_NOTSET;
uint8_t hc_num() const {
return hc_num_;
@@ -86,7 +88,7 @@ class Thermostat : public EMSdevice {
uint8_t get_mode(uint8_t flags) const;
uint8_t get_mode_type(uint8_t flags) const;
enum Mode : uint8_t { UNKNOWN, OFF, MANUAL, AUTO, DAY, NIGHT, HEAT, NOFROST, ECO, HOLIDAY, COMFORT, OFFSET, DESIGN, SUMMER, FLOWOFFSET };
enum Mode : uint8_t { UNKNOWN, OFF, MANUAL, AUTO, DAY, NIGHT, HEAT, NOFROST, ECO, HOLIDAY, COMFORT, OFFSET, DESIGN, SUMMER, FLOWOFFSET, MINFLOW, MAXFLOW, ROOMINFLUENCE };
// for sorting based on hc number
friend inline bool operator<(const std::shared_ptr<HeatingCircuit> & lhs, const std::shared_ptr<HeatingCircuit> & rhs) {
@@ -131,6 +133,7 @@ class Thermostat : public EMSdevice {
std::vector<uint16_t> set_typeids;
std::vector<uint16_t> timer_typeids;
std::vector<uint16_t> summer_typeids;
std::vector<uint16_t> curve_typeids;
std::string datetime_; // date and time stamp
std::string errorCode_; // code from 0xA2 as string i.e. "A22(816)"
@@ -245,7 +248,7 @@ class Thermostat : public EMSdevice {
void register_mqtt_ha_config();
void register_mqtt_ha_config(uint8_t hc_num);
void ha_config(bool force = false);
bool ha_config(bool force = false);
bool thermostat_ha_cmd(const char * message, uint8_t hc_num);
void process_RCOutdoorTemp(std::shared_ptr<const Telegram> telegram);
@@ -273,6 +276,7 @@ class Thermostat : public EMSdevice {
void process_RC300OutdoorTemp(std::shared_ptr<const Telegram> telegram);
void process_RC300Settings(std::shared_ptr<const Telegram> telegram);
void process_RC300Floordry(std::shared_ptr<const Telegram> telegram);
void process_RC300Curve(std::shared_ptr<const Telegram> telegram);
void process_JunkersMonitor(std::shared_ptr<const Telegram> telegram);
void process_JunkersSet(std::shared_ptr<const Telegram> telegram);
void process_JunkersSet2(std::shared_ptr<const Telegram> telegram);
@@ -308,11 +312,14 @@ class Thermostat : public EMSdevice {
bool set_remotetemp(const char * value, const int8_t id);
bool set_roominfluence(const char * value, const int8_t id);
bool set_flowtempoffset(const char * value, const int8_t id);
bool set_minflowtemp(const char * value, const int8_t id);
bool set_maxflowtemp(const char * value, const int8_t id);
// set functions - these don't use the id/hc, the parameters are ignored
bool set_wwmode(const char * value, const int8_t id);
bool set_wwtemp(const char * value, const int8_t id);
bool set_wwtemplow(const char * value, const int8_t id);
bool set_wwonetime(const char * value, const int8_t id);
bool set_wwcircmode(const char * value, const int8_t id);
bool set_datetime(const char * value, const int8_t id);
bool set_minexttemp(const char * value, const int8_t id);

View File

@@ -284,8 +284,7 @@ class EMSdevice {
uint16_t telegram_type_id_; // it's type_id
const __FlashStringHelper * telegram_type_name_; // e.g. RC20Message
bool fetch_; // if this type_id be queried automatically
process_function_p process_function_;
process_function_p process_function_;
TelegramFunction(uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, process_function_p process_function)
: telegram_type_id_(telegram_type_id)

View File

@@ -63,6 +63,7 @@ bool EMSESP::read_next_ = false;
uint16_t EMSESP::publish_id_ = 0;
bool EMSESP::tap_water_active_ = false; // for when Boiler states we having running warm water. used in Shower()
uint32_t EMSESP::last_fetch_ = 0;
uint8_t EMSESP::publish_all_idx_ = 0;
uint8_t EMSESP::unique_id_count_ = 0;
// for a specific EMS device go and request data values
@@ -215,10 +216,9 @@ void EMSESP::show_ems(uuid::console::Shell & shell) {
shell.printfln(F(" #tx fails (after %d retries): %d"), TxService::MAXIMUM_TX_RETRIES, txservice_.telegram_fail_count());
shell.printfln(F(" Rx line quality: %d%%"), rxservice_.quality());
shell.printfln(F(" Tx line quality: %d%%"), txservice_.quality());
shell.println();
}
shell.println();
// Rx queue
auto rx_telegrams = rxservice_.queue();
if (rx_telegrams.empty()) {
@@ -290,29 +290,74 @@ void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
// MQTT publish everything, immediately
void EMSESP::publish_all(bool force) {
if (force) {
publish_all_idx_ = 1;
return;
}
if (Mqtt::connected()) {
publish_device_values(EMSdevice::DeviceType::BOILER, force);
publish_device_values(EMSdevice::DeviceType::THERMOSTAT, force);
publish_device_values(EMSdevice::DeviceType::SOLAR, force);
publish_device_values(EMSdevice::DeviceType::MIXER, force);
publish_device_values(EMSdevice::DeviceType::BOILER, false);
publish_device_values(EMSdevice::DeviceType::THERMOSTAT, false);
publish_device_values(EMSdevice::DeviceType::SOLAR, false);
publish_device_values(EMSdevice::DeviceType::MIXER, false);
publish_other_values();
publish_sensor_values(true);
publish_sensor_values(true, false);
system_.send_heartbeat();
}
}
// on command "publish HA" loop and wait between devices for publishing all sensors
void EMSESP::publish_all_loop() {
static uint32_t last = 0;
if (!Mqtt::connected() || !publish_all_idx_) {
return;
}
// every HA-sensor takes 20 ms, wait ~2 sec to finish (boiler have ~70 sensors)
if ((uuid::get_uptime() - last < 2000)) {
return;
}
last = uuid::get_uptime();
switch (publish_all_idx_++) {
case 1:
publish_device_values(EMSdevice::DeviceType::BOILER, true);
break;
case 2:
publish_device_values(EMSdevice::DeviceType::THERMOSTAT, true);
break;
case 3:
publish_device_values(EMSdevice::DeviceType::SOLAR, true);
break;
case 4:
publish_device_values(EMSdevice::DeviceType::MIXER, true);
break;
case 5:
publish_other_values();
break;
case 6:
publish_sensor_values(true, true);
break;
case 7:
system_.send_heartbeat();
break;
default:
// all finished
publish_all_idx_ = 0;
last = 0;
}
}
// create json doc for the devices values and add to MQTT publish queue
// special case for Mixer units, since we want to bundle all devices together into one payload
void EMSESP::publish_device_values(uint8_t device_type, bool force) {
if (device_type == EMSdevice::DeviceType::MIXER && Mqtt::mqtt_format() != Mqtt::Format::SINGLE) {
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_LARGE);
JsonObject json = doc.to<JsonObject>();
// DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_LARGE);
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc;
JsonObject json = doc.to<JsonObject>();
for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_type)) {
emsdevice->publish_values(json, force);
}
}
doc.shrinkToFit();
// doc.shrinkToFit();
Mqtt::publish("mixer_data", doc.as<JsonObject>());
return;
}
@@ -335,9 +380,9 @@ void EMSESP::publish_other_values() {
}
}
void EMSESP::publish_sensor_values(const bool force) {
if (dallassensor_.updated_values() || force) {
dallassensor_.publish_values();
void EMSESP::publish_sensor_values(const bool time, const bool force) {
if (dallassensor_.updated_values() || time || force) {
dallassensor_.publish_values(force);
}
}
@@ -649,7 +694,7 @@ void EMSESP::show_devices(uuid::console::Shell & shell) {
// shell.printf(F("[factory ID: %d] "), device_class.first);
for (const auto & emsdevice : emsdevices) {
if ((emsdevice) && (emsdevice->device_type() == device_class.first)) {
shell.printf(F("%s: %s"), emsdevice->device_type_name().c_str(), emsdevice->to_string().c_str());
shell.printf(F("(%d) %s: %s"), emsdevice->unique_id(), emsdevice->device_type_name().c_str(), emsdevice->to_string().c_str());
if ((emsdevice->device_type() == EMSdevice::DeviceType::THERMOSTAT) && (emsdevice->device_id() == actual_master_thermostat())) {
shell.printf(F(" ** master device **"));
}
@@ -722,17 +767,20 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::
return false; // not found
}
std::string name = uuid::read_flash_string(device_p->name);
emsdevices.push_back(EMSFactory::add(device_p->device_type, device_id, device_p->product_id, version, name, device_p->flags, brand));
emsdevices.back()->unique_id(++unique_id_count_);
auto name = uuid::read_flash_string(device_p->name);
auto device_type = device_p->device_type;
auto flags = device_p->flags;
LOG_DEBUG(F("Adding new device %s (device ID 0x%02X, product ID %d, version %s)"), name.c_str(), device_id, product_id, version.c_str());
emsdevices.push_back(EMSFactory::add(device_type, device_id, product_id, version, name, flags, brand));
emsdevices.back()->unique_id(++unique_id_count_);
fetch_device_values(device_id); // go and fetch its data
// add info command, but not for all devices
uint8_t device_type = device_p->device_type;
if ((device_type == DeviceType::CONNECT) || (device_type == DeviceType::CONTROLLER) || (device_type == DeviceType::GATEWAY)) {
return true;
}
Command::add_with_json(device_type, F_(info), [device_type](const char * value, const int8_t id, JsonObject & json) {
return command_info(device_type, json);
});
@@ -934,11 +982,12 @@ void EMSESP::loop() {
}
system_.loop(); // does LED and checks system health, and syslog service
rxservice_.loop(); // process any incoming Rx telegrams
shower_.loop(); // check for shower on/off
dallassensor_.loop(); // this will also send out via MQTT
mqtt_.loop(); // sends out anything in the queue via MQTT
console_.loop(); // telnet/serial console
rxservice_.loop(); // process any incoming Rx telegrams
publish_all_loop();
mqtt_.loop(); // sends out anything in the queue via MQTT
console_.loop(); // telnet/serial console
// force a query on the EMS devices to fetch latest data at a set interval (1 min)
if ((uuid::get_uptime() - last_fetch_ > EMS_FETCH_FREQUENCY)) {

View File

@@ -63,7 +63,7 @@ class EMSESP {
static void publish_device_values(uint8_t device_type, bool force = false);
static void publish_other_values();
static void publish_sensor_values(const bool force = false);
static void publish_sensor_values(const bool time, const bool force = false);
static void publish_all(bool force = false);
#ifdef EMSESP_STANDALONE
@@ -184,6 +184,7 @@ class EMSESP {
static void process_UBADevices(std::shared_ptr<const Telegram> telegram);
static void process_version(std::shared_ptr<const Telegram> telegram);
static void publish_response(std::shared_ptr<const Telegram> telegram);
static void publish_all_loop();
static bool command_info(uint8_t device_type, JsonObject & json);
@@ -206,6 +207,7 @@ class EMSESP {
static bool read_next_;
static uint16_t publish_id_;
static bool tap_water_active_;
static uint8_t publish_all_idx_;
static uint8_t unique_id_count_;
};

View File

@@ -66,10 +66,7 @@ MAKE_PSTR_WORD(publish)
MAKE_PSTR_WORD(bar)
MAKE_PSTR_WORD(min)
MAKE_PSTR_WORD(uA)
#if defined(EMSESP_DEBUG)
MAKE_PSTR_WORD(test)
#endif
MAKE_PSTR_WORD(timeout)
// for commands
MAKE_PSTR_WORD(call)
@@ -81,6 +78,7 @@ MAKE_PSTR_WORD(command)
MAKE_PSTR_WORD(commands)
MAKE_PSTR_WORD(info)
MAKE_PSTR_WORD(report)
MAKE_PSTR_WORD(test)
// devices
MAKE_PSTR_WORD(boiler)
@@ -116,7 +114,7 @@ MAKE_PSTR(bus_id_fmt, "Bus ID = %02X")
MAKE_PSTR(watchid_optional, "[ID]")
MAKE_PSTR(watch_format_optional, "[off | on | raw]")
MAKE_PSTR(invalid_watch, "Invalid watch type")
MAKE_PSTR(data_mandatory, "<\"XX XX ...\">")
MAKE_PSTR(data_mandatory, "\"XX XX ...\"")
MAKE_PSTR(percent, "%")
MAKE_PSTR(degrees, "°C")
MAKE_PSTR(asterisks, "********")
@@ -246,6 +244,8 @@ MAKE_PSTR(floordrytemp, "Floordrying temperature")
MAKE_PSTR(wwmode, "Warm water mode")
MAKE_PSTR(wwtemp, "Warm water high temperature")
MAKE_PSTR(wwtemplow, "Warm water low temperature")
MAKE_PSTR(wwextra1, "Warm water circuit 1 extra")
MAKE_PSTR(wwextra2, "Warm water circuit 2 extra")
MAKE_PSTR(wwcircmode, "Warm water circulation mode")
// thermostat - per heating circuit
@@ -267,6 +267,8 @@ MAKE_PSTR(summertemp, "Summer temperature")
MAKE_PSTR(summermode, "Summer mode")
MAKE_PSTR(roominfluence, "Room influence")
MAKE_PSTR(flowtempoffset, "Flow temperature offset")
MAKE_PSTR(minflowtemp, "Min. flow temperature")
MAKE_PSTR(maxflowtemp, "Max. flow temperature")
MAKE_PSTR(mode, "Mode")
MAKE_PSTR(modetype, "Mode type")

View File

@@ -39,9 +39,9 @@ bool Mqtt::mqtt_enabled_;
std::vector<Mqtt::MQTTSubFunction> Mqtt::mqtt_subfunctions_;
uint16_t Mqtt::mqtt_publish_fails_ = 0;
size_t Mqtt::maximum_mqtt_messages_ = Mqtt::MAX_MQTT_MESSAGES;
uint16_t Mqtt::mqtt_message_id_ = 0;
uint16_t Mqtt::mqtt_publish_fails_ = 0;
// size_t Mqtt::maximum_mqtt_messages_ = Mqtt::MAX_MQTT_MESSAGES;
uint16_t Mqtt::mqtt_message_id_ = 0;
std::list<Mqtt::QueuedMqttMessage> Mqtt::mqtt_messages_;
char will_topic_[Mqtt::MQTT_TOPIC_MAX_SIZE]; // because MQTT library keeps only char pointer
@@ -181,7 +181,7 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) {
return;
}
shell.printfln(F("MQTT queue (%d messages):"), mqtt_messages_.size());
shell.printfln(F("MQTT queue (%d/%d messages):"), mqtt_messages_.size(), MAX_MQTT_MESSAGES);
for (const auto & message : mqtt_messages_) {
auto content = message.content_;
@@ -211,14 +211,12 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) {
}
}
shell.println();
} // namespace emsesp
}
#if defined(EMSESP_DEBUG)
// simulate receiving a MQTT message, used only for testing
void Mqtt::incoming(const char * topic, const char * payload) {
on_message(topic, payload, strlen(payload));
}
#endif
// received an MQTT message that we subscribed too
void Mqtt::on_message(const char * topic, const char * payload, size_t len) {
@@ -358,6 +356,11 @@ void Mqtt::start() {
mqtt_enabled_ = mqttSettings.enabled;
});
// if MQTT disabled, quit
if (!mqtt_enabled_) {
return;
}
mqttClient_->onConnect([this](bool sessionPresent) { on_connect(); });
mqttClient_->onDisconnect([this](AsyncMqttClientDisconnectReason reason) {
@@ -529,7 +532,7 @@ std::shared_ptr<const MqttMessage> Mqtt::queue_message(const uint8_t operation,
}
// if the queue is full, make room but removing the last one
if (mqtt_messages_.size() >= maximum_mqtt_messages_) {
if (mqtt_messages_.size() >= MAX_MQTT_MESSAGES) {
mqtt_messages_.pop_front();
}
mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message));
@@ -611,26 +614,6 @@ void Mqtt::process_queue() {
return;
}
// show queue - Debug only
/*
Serial.printf("MQTT queue:\n\r");
for (const auto & message : mqtt_messages_) {
auto content = message.content_;
if (content->operation == Operation::PUBLISH) {
// Publish messages
Serial.printf(" [%02d] (Pub) topic=%s payload=%s (pid %d, retry #%d)\n\r",
message.id_,
content->topic.c_str(),
content->payload.c_str(),
message.packet_id_,
message.retry_count_);
} else {
// Subscribe messages
Serial.printf(" [%02d] (Sub) topic=%s\n\r", message.id_, content->topic.c_str());
}
}
*/
// fetch first from queue and create the full topic name
auto mqtt_message = mqtt_messages_.front();
auto message = mqtt_message.content_;
@@ -697,9 +680,8 @@ void Mqtt::register_mqtt_ha_binary_sensor(const __FlashStringHelper * name, cons
return;
}
return; // TODO
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_SMALL);
// StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
doc["name"] = name;
doc["uniq_id"] = entity;
@@ -727,11 +709,13 @@ void Mqtt::register_mqtt_ha_binary_sensor(const __FlashStringHelper * name, cons
snprintf_P(ha_device, sizeof(ha_device), PSTR("ems-esp-%s"), EMSdevice::device_type_2_device_name(device_type).c_str());
ids.add(ha_device);
doc.shrinkToFit();
char topic[MQTT_TOPIC_MAX_SIZE];
snprintf_P(topic, sizeof(topic), PSTR("homeassistant/binary_sensor/ems-esp/%s/config"), entity);
// convert json to string and publish immediately with retain forced to true
char payload_text[300];
char payload_text[256];
serializeJson(doc, payload_text); // convert json to string
uint16_t packet_id = mqttClient_->publish(topic, 0, true, payload_text);
#if defined(EMSESP_STANDALONE)
@@ -744,7 +728,8 @@ void Mqtt::register_mqtt_ha_binary_sensor(const __FlashStringHelper * name, cons
}
#endif
delay(MQTT_PUBLISH_WAIT);
// delay(MQTT_PUBLISH_WAIT);
delay(50);
}
// HA config for a normal 'sensor' type
@@ -765,14 +750,15 @@ void Mqtt::register_mqtt_ha_sensor(const char * prefix,
if (prefix != nullptr) {
snprintf_P(new_entity, sizeof(new_entity), PSTR("%s.%s"), prefix, entity);
} else {
strcpy(new_entity, entity);
strncpy(new_entity, entity, sizeof(new_entity));
}
std::string device_name = EMSdevice::device_type_2_device_name(device_type);
char device_name[50];
strncpy(device_name, EMSdevice::device_type_2_device_name(device_type).c_str(), sizeof(device_name));
// build unique identifier, replacing all . with _ as not to break HA
std::string uniq(50, '\0');
snprintf_P(&uniq[0], uniq.capacity() + 1, PSTR("%s_%s"), device_name.c_str(), new_entity);
snprintf_P(&uniq[0], uniq.capacity() + 1, PSTR("%s_%s"), device_name, new_entity);
std::replace(uniq.begin(), uniq.end(), '.', '_');
// topic
@@ -782,9 +768,9 @@ void Mqtt::register_mqtt_ha_sensor(const char * prefix,
// state topic
char stat_t[MQTT_TOPIC_MAX_SIZE];
if (suffix != nullptr) {
snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s_data%s"), hostname_.c_str(), device_name.c_str(), uuid::read_flash_string(suffix).c_str());
snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s_data%s"), hostname_.c_str(), device_name, uuid::read_flash_string(suffix).c_str());
} else {
snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s_data"), hostname_.c_str(), device_name.c_str());
snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s_data"), hostname_.c_str(), device_name);
}
// value template
@@ -793,20 +779,22 @@ void Mqtt::register_mqtt_ha_sensor(const char * prefix,
// ha device
char ha_device[40];
snprintf_P(ha_device, sizeof(ha_device), PSTR("ems-esp-%s"), device_name.c_str());
snprintf_P(ha_device, sizeof(ha_device), PSTR("ems-esp-%s"), device_name);
// name
char new_name[50];
if (prefix != nullptr) {
snprintf_P(new_name, sizeof(new_name), PSTR("%s %s %s"), device_name.c_str(), prefix, uuid::read_flash_string(name).c_str());
snprintf_P(new_name, sizeof(new_name), PSTR("%s %s %s"), device_name, prefix, uuid::read_flash_string(name).c_str());
} else {
snprintf_P(new_name, sizeof(new_name), PSTR("%s %s"), device_name.c_str(), uuid::read_flash_string(name).c_str());
snprintf_P(new_name, sizeof(new_name), PSTR("%s %s"), device_name, uuid::read_flash_string(name).c_str());
}
new_name[0] = toupper(new_name[0]); // capitalize first letter
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_SMALL);
// StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
doc["name"] = new_name;
doc["uniq_id"] = uniq;
doc["uniq_id"] = uniq.c_str();
if (uom != nullptr) {
doc["unit_of_meas"] = uom;
}
@@ -819,9 +807,9 @@ void Mqtt::register_mqtt_ha_sensor(const char * prefix,
JsonArray ids = dev.createNestedArray("ids");
ids.add(ha_device);
doc.shrinkToFit();
// convert json to string and publish immediately with retain forced to true
// std::string payload_text;
char payload_text[300];
char payload_text[256];
serializeJson(doc, payload_text); // convert json to string
uint16_t packet_id = mqttClient_->publish(topic, 0, true, payload_text);
@@ -836,6 +824,6 @@ void Mqtt::register_mqtt_ha_sensor(const char * prefix,
}
// delay(MQTT_PUBLISH_WAIT); // don't flood asynctcp
delay(50); // enough time to send the short message out
}
} // namespace emsesp

View File

@@ -38,9 +38,10 @@
using uuid::console::Shell;
#define EMSESP_MAX_JSON_SIZE_SMALL 256 // for smaller json docs when using StaticJsonDocument
#define EMSESP_MAX_JSON_SIZE_MEDIUM 768 // for smaller json docs from ems devices, when using StaticJsonDocument
#define EMSESP_MAX_JSON_SIZE_LARGE 2048 // for large json docs from ems devices, like boiler or thermostat data. Using DynamicJsonDocument
#define EMSESP_MAX_JSON_SIZE_SMALL 384 // for smaller json docs when using StaticJsonDocument
#define EMSESP_MAX_JSON_SIZE_MEDIUM 768 // for medium json docs from ems devices, when using StaticJsonDocument
#define EMSESP_MAX_JSON_SIZE_LARGE 1024 // for large json docs from ems devices, like boiler or thermostat data. Using StaticJsonDocument
#define EMSESP_MAX_JSON_SIZE_DYN 2048 // for large json docs from web. Using DynamicJsonDocument
namespace emsesp {
@@ -118,9 +119,7 @@ class Mqtt {
mqttClient_->disconnect();
}
#if defined(EMSESP_DEBUG)
void incoming(const char * topic, const char * payload); // for testing only
#endif
static bool connected() {
#if defined(EMSESP_STANDALONE)
@@ -171,11 +170,14 @@ class Mqtt {
static std::list<QueuedMqttMessage> mqtt_messages_;
static AsyncMqttClient * mqttClient_;
static uint16_t mqtt_message_id_;
static size_t maximum_mqtt_messages_;
static uint16_t mqtt_message_id_;
#if defined(EMSESP_STANDALONE)
static constexpr size_t MAX_MQTT_MESSAGES = 70; // size of queue
#else
static constexpr size_t MAX_MQTT_MESSAGES = 20; // size of queue
#endif
static constexpr size_t MAX_MQTT_MESSAGES = 70; // size of queue
static constexpr uint32_t MQTT_PUBLISH_WAIT = 200; // delay between sending publishes, to account for large payloads
static constexpr uint8_t MQTT_PUBLISH_MAX_RETRY = 3; // max retries for giving up on publishing

View File

@@ -28,7 +28,9 @@ void Shower::start() {
shower_alert_ = settings.shower_alert;
});
send_mqtt_stat(false); // send first MQTT publish
if (Mqtt::enabled()) {
send_mqtt_stat(false); // send first MQTT publish
}
}
void Shower::loop() {

View File

@@ -21,6 +21,10 @@
#include "version.h" // firmware version of EMS-ESP
#if defined(EMSESP_TEST)
#include "test/test.h"
#endif
namespace emsesp {
uuid::log::Logger System::logger_{F_(system), uuid::log::Facility::KERN};
@@ -37,11 +41,22 @@ bool System::hide_led_ = false;
uint8_t System::led_gpio_ = 0;
uint16_t System::analog_ = 0;
bool System::analog_enabled_ = false;
bool System::syslog_enabled_ = false;
std::string System::hostname_;
int8_t System::syslog_level_ = -1;
uint32_t System::syslog_mark_interval_ = 0;
String System::syslog_host_;
// send on/off to a gpio pin
// value: true = HIGH, false = LOW
// http://ems-esp/api?device=system&cmd=pin&data=1&id=2
bool System::command_pin(const char * value, const int8_t id) {
if (id < 0) {
return false;
}
bool v = false;
if (Helpers::value2bool(value, v)) {
pinMode(id, OUTPUT);
@@ -53,12 +68,34 @@ bool System::command_pin(const char * value, const int8_t id) {
return false;
}
// send raw
// send raw to ems
bool System::command_send(const char * value, const int8_t id) {
EMSESP::send_raw_telegram(value); // ignore id
return true;
}
// fetch device values
bool System::command_fetch(const char * value, const int8_t id) {
LOG_INFO(F("Requesting data from EMS devices"));
EMSESP::fetch_device_values();
return true;
}
// mqtt publish
bool System::command_publish(const char * value, const int8_t id) {
std::string ha(10, '\0');
if (Helpers::value2string(value, ha)) {
if (ha == "ha") {
EMSESP::publish_all(true); // includes HA
LOG_INFO(F("Publishing all data to MQTT, including HA configs"));
return true;
}
}
EMSESP::publish_all(); // ignore value and id
LOG_INFO(F("Publishing all data to MQTT"));
return true;
}
// restart EMS-ESP
void System::restart() {
LOG_NOTICE(F("Restarting system..."));
@@ -112,12 +149,20 @@ uint8_t System::free_mem() {
void System::syslog_init() {
// fetch settings
EMSESP::webSettingsService.read([&](WebSettings & settings) {
syslog_enabled_ = settings.syslog_enabled;
syslog_level_ = settings.syslog_level;
syslog_mark_interval_ = settings.syslog_mark_interval;
syslog_host_ = settings.syslog_host;
});
#ifndef EMSESP_STANDALONE
if (!syslog_enabled_) {
syslog_.log_level((uuid::log::Level)-1);
syslog_.mark_interval(0);
syslog_.destination((IPAddress)((uint32_t)0));
return;
}
syslog_.start(); // syslog service re-start
// configure syslog
@@ -151,11 +196,16 @@ void System::start() {
EMSESP::webSettingsService.read([&](WebSettings & settings) {
Command::add(EMSdevice::DeviceType::SYSTEM, settings.ems_bus_id, F_(pin), System::command_pin);
Command::add(EMSdevice::DeviceType::SYSTEM, settings.ems_bus_id, F_(send), System::command_send);
Command::add(EMSdevice::DeviceType::SYSTEM, settings.ems_bus_id, F_(publish), System::command_publish);
Command::add(EMSdevice::DeviceType::SYSTEM, settings.ems_bus_id, F_(fetch), System::command_fetch);
Command::add_with_json(EMSdevice::DeviceType::SYSTEM, F_(info), System::command_info);
Command::add_with_json(EMSdevice::DeviceType::SYSTEM, F_(report), System::command_report);
#if defined(EMSESP_TEST)
Command::add(EMSdevice::DeviceType::SYSTEM, settings.ems_bus_id, F_(test), System::command_test);
#endif
});
syslog_init(); // init SysLog
init();
}
@@ -169,6 +219,7 @@ void System::init() {
Helpers::bool_format(settings.bool_format);
analog_enabled_ = settings.analog_enabled;
});
syslog_init(); // init SysLog
EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & settings) { hostname(settings.hostname.c_str()); });
@@ -207,8 +258,11 @@ void System::upload_status(bool in_progress) {
// checks system health and handles LED flashing wizardry
void System::loop() {
#ifndef EMSESP_STANDALONE
syslog_.loop();
#endif
if (syslog_enabled_) {
syslog_.loop();
}
led_monitor(); // check status and report back using the LED
system_check(); // check system health
if (analog_enabled_) {
@@ -230,6 +284,8 @@ void System::loop() {
show_mem("core");
}
#endif
#endif
#endif
}
@@ -481,13 +537,18 @@ void System::show_system(uuid::console::Shell & shell) {
EMSESP::webSettingsService.read([&](WebSettings & settings) {
shell.println();
shell.printfln(F("Syslog:"));
shell.print(F_(1space));
shell.printfln(F_(host_fmt), !settings.syslog_host.isEmpty() ? settings.syslog_host.c_str() : uuid::read_flash_string(F_(unset)).c_str());
shell.print(F_(1space));
shell.printfln(F_(log_level_fmt), uuid::log::format_level_lowercase(static_cast<uuid::log::Level>(settings.syslog_level)));
shell.print(F_(1space));
shell.printfln(F_(mark_interval_fmt), settings.syslog_mark_interval);
if (!settings.syslog_enabled) {
shell.printfln(F("Syslog: disabled"));
} else {
shell.printfln(F("Syslog:"));
shell.print(F_(1space));
shell.printfln(F_(host_fmt), !settings.syslog_host.isEmpty() ? settings.syslog_host.c_str() : uuid::read_flash_string(F_(unset)).c_str());
shell.print(F_(1space));
shell.printfln(F_(log_level_fmt), uuid::log::format_level_lowercase(static_cast<uuid::log::Level>(settings.syslog_level)));
shell.print(F_(1space));
shell.printfln(F_(mark_interval_fmt), settings.syslog_mark_interval);
}
});
#endif
@@ -652,7 +713,7 @@ bool System::check_upgrade() {
l_cfg.setAutoFormat(false);
LittleFS.setConfig(l_cfg); // do not auto format if it can't find LittleFS
if (LittleFS.begin()) {
#if defined(EMSESP_DEBUG)
#if defined(EMSESP_FORCE_SERIAL)
Serial.begin(115200);
Serial.println(F("FS is Littlefs"));
Serial.end();
@@ -667,7 +728,7 @@ bool System::check_upgrade() {
cfg.setAutoFormat(false); // prevent formatting when opening SPIFFS filesystem
SPIFFS.setConfig(cfg);
if (!SPIFFS.begin()) {
#if defined(EMSESP_DEBUG)
#if defined(EMSESP_FORCE_SERIAL)
Serial.begin(115200);
Serial.println(F("No old SPIFFS found!"));
Serial.end();
@@ -766,9 +827,7 @@ bool System::check_upgrade() {
file.close();
if (failed) {
#if defined(EMSESP_DEBUG)
Serial.println(F("Failed to read system config. Quitting."));
#endif
SPIFFS.end();
Serial.end();
return false;
@@ -791,10 +850,6 @@ bool System::check_upgrade() {
Serial.printf(PSTR("Error. Failed to deserialize custom json, error %s\n"), error.c_str());
failed = true;
} else {
#if defined(EMSESP_DEBUG)
serializeJson(doc, Serial);
Serial.println();
#endif
custom_settings = doc["settings"];
EMSESP::webSettingsService.update(
[&](WebSettings & settings) {
@@ -803,6 +858,7 @@ bool System::check_upgrade() {
settings.shower_timer = custom_settings["shower_timer"] | EMSESP_DEFAULT_SHOWER_TIMER;
settings.master_thermostat = custom_settings["master_thermostat"] | EMSESP_DEFAULT_MASTER_THERMOSTAT;
settings.ems_bus_id = custom_settings["bus_id"] | EMSESP_DEFAULT_EMS_BUS_ID;
settings.syslog_enabled = false;
settings.syslog_host = EMSESP_DEFAULT_SYSLOG_HOST;
settings.syslog_level = EMSESP_DEFAULT_SYSLOG_LEVEL;
settings.syslog_mark_interval = EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL;
@@ -821,9 +877,7 @@ bool System::check_upgrade() {
SPIFFS.end();
if (failed) {
#if defined(EMSESP_DEBUG)
Serial.println(F("Failed to read custom config. Quitting."));
#endif
Serial.end();
return false;
}
@@ -918,6 +972,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & json
JsonObject node = json.createNestedObject("Settings");
node["tx_mode"] = settings.tx_mode;
node["ems_bus_id"] = settings.ems_bus_id;
node["syslog_enabled"] = settings.syslog_enabled;
node["syslog_level"] = settings.syslog_level;
node["syslog_mark_interval"] = settings.syslog_mark_interval;
node["syslog_host"] = settings.syslog_host;
@@ -1030,4 +1085,13 @@ bool System::command_report(const char * value, const int8_t id, JsonObject & js
return true;
}
#if defined(EMSESP_TEST)
// run a test
// e.g. http://ems-esp/api?device=system&cmd=test&data=boiler
bool System::command_test(const char * value, const int8_t id) {
Test::run_test(value, id);
return true;
}
#endif
} // namespace emsesp

View File

@@ -50,18 +50,24 @@ class System {
static bool command_pin(const char * value, const int8_t id);
static bool command_send(const char * value, const int8_t id);
static bool command_publish(const char * value, const int8_t id);
static bool command_fetch(const char * value, const int8_t id);
static bool command_info(const char * value, const int8_t id, JsonObject & json);
static bool command_report(const char * value, const int8_t id, JsonObject & json);
#if defined(EMSESP_TEST)
static bool command_test(const char * value, const int8_t id);
#endif
static uint8_t free_mem();
static void upload_status(bool in_progress);
static bool upload_status();
static void show_mem(const char * note);
static void set_led();
static void init();
static void syslog_init();
bool check_upgrade();
void syslog_init();
void send_heartbeat();
static std::string hostname() {
@@ -108,12 +114,13 @@ class System {
static std::string hostname_;
// settings
static bool hide_led_;
uint8_t syslog_level_;
uint32_t syslog_mark_interval_;
String syslog_host_;
static uint8_t led_gpio_;
static bool analog_enabled_;
static bool hide_led_;
static bool syslog_enabled_;
static int8_t syslog_level_;
static uint32_t syslog_mark_interval_;
static String syslog_host_;
static uint8_t led_gpio_;
static bool analog_enabled_;
};
} // namespace emsesp

View File

@@ -429,6 +429,11 @@ void TxService::add(const uint8_t operation,
// format is EMS 1.0 (src, dest, type_id, offset, data)
// length is the length of the whole telegram data, excluding the CRC
void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t length, const bool front) {
// check length
if (length < 5) {
return;
}
// build header. src, dest and offset have fixed positions
uint8_t src = data[0];
uint8_t dest = data[1];

View File

@@ -384,7 +384,7 @@ class TxService : public EMSbus {
void send_telegram(const QueuedTxTelegram & tx_telegram);
void send_telegram(const uint8_t * data, const uint8_t length);
}; // namespace emsesp
};
} // namespace emsesp

View File

@@ -17,24 +17,145 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#if defined(EMSESP_DEBUG)
#if defined(EMSESP_TEST)
#include "test.h"
// create some fake test data
namespace emsesp {
// create some fake test data
// used with the 'test' command, under su/admin
void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
if (command == "default") {
run_test(shell, "mixer"); // add the default test case here
// no shell
void Test::run_test(const char * command, int8_t id) {
if ((command == nullptr) || (strlen(command) == 0)) {
return;
}
if (command.empty()) {
run_test(shell, "default");
if (strcmp(command, "general") == 0) {
EMSESP::logger().info(F("Testing general..."));
add_device(0x08, 123); // Nefit Trendline
add_device(0x18, 157); // Bosch CR100
// add some data
// Boiler -> Me, UBAMonitorFast(0x18), telegram: 08 00 18 00 00 02 5A 73 3D 0A 10 65 40 02 1A 80 00 01 E1 01 76 0E 3D 48 00 C9 44 02 00 (#data=25)
uart_telegram({0x08, 0x00, 0x18, 0x00, 0x00, 0x02, 0x5A, 0x73, 0x3D, 0x0A, 0x10, 0x65, 0x40, 0x02, 0x1A,
0x80, 0x00, 0x01, 0xE1, 0x01, 0x76, 0x0E, 0x3D, 0x48, 0x00, 0xC9, 0x44, 0x02, 0x00});
// Boiler -> Thermostat, UBAParameterWW(0x33), telegram: 08 97 33 00 23 24 (#data=2)
uart_telegram({0x08, 0x98, 0x33, 0x00, 0x23, 0x24});
// Boiler -> Me, UBAParameterWW(0x33), telegram: 08 0B 33 00 08 FF 34 FB 00 28 00 00 46 00 FF FF 00 (#data=13)
uart_telegram({0x08, 0x0B, 0x33, 0x00, 0x08, 0xFF, 0x34, 0xFB, 0x00, 0x28, 0x00, 0x00, 0x46, 0x00, 0xFF, 0xFF, 0x00});
// Thermostat RCPLUSStatusMessage_HC1(0x01A5)
uart_telegram({0x98, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
return;
}
if (strcmp(command, "gateway") == 0) {
EMSESP::logger().info(F("Testing gateway..."));
// add 0x48 KM200, via a version command
rx_telegram({0x48, 0x0B, 0x02, 0x00, 0xBD, 0x04, 0x06, 00, 00, 00, 00, 00, 00, 00});
// Boiler(0x08) -> All(0x00), UBADevices(0x07), data: 09 01 00 00 00 00 00 00 01 00 00 00 00
// check: make sure 0x48 is not detected again !
rx_telegram({0x08, 0x00, 0x07, 0x00, 0x09, 01, 00, 00, 00, 00, 00, 00, 01, 00, 00, 00, 00});
// add thermostat - Thermostat: RC300/RC310/Moduline 3000/CW400/Sense II (DeviceID:0x10, ProductID:158, Version:03.03) ** master device **
add_device(0x10, 158); // Nefit Trendline
// simulate incoming telegram
// Thermostat(0x10) -> 48(0x48), ?(0x26B), data: 6B 08 4F 00 00 00 02 00 00 00 02 00 03 00 03 00 03
rx_telegram({0x10, 0x48, 0xFF, 00, 01, 0x6B, 00, 0x6B, 0x08, 0x4F, 00, 00, 00, 02, 00, 00, 00, 02, 00, 03, 00, 03, 00, 03});
return;
}
if (strcmp(command, "boiler") == 0) {
// EMSESP::logger().info(F("Testing boiler..."));
add_device(0x08, 123); // Nefit Trendline
// UBAuptime
uart_telegram({0x08, 0x0B, 0x14, 00, 0x3C, 0x1F, 0xAC, 0x70});
return;
}
if (strcmp(command, "thermostat") == 0) {
EMSESP::logger().info(F("Testing thermostat..."));
add_device(0x10, 192); // FW120
// HC1
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x6F, 0x00, 0xCF, 0x21, 0x2E, 0x20, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
// HC2
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x70, 0x00, 0xCF, 0x22, 0x2F, 0x10, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
// HC3
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
return;
}
if (strcmp(command, "solar") == 0) {
EMSESP::logger().info(F("Testing solar..."));
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
add_device(0x30, 163); // SM100
// SM100Monitor - type 0x0362 EMS+ - for SM100 and SM200
// B0 0B FF 00 02 62 00 44 02 7A 80 00 80 00 80 00 80 00 80 00 80 00 00 7C 80 00 80 00 80 00 80
rx_telegram({0xB0, 0x0B, 0xFF, 00, 0x02, 0x62, 00, 0x44, 0x02, 0x7A, 0x80, 00, 0x80, 0x00, 0x80, 00,
0x80, 00, 0x80, 00, 0x80, 00, 00, 0x7C, 0x80, 00, 0x80, 00, 0x80, 00, 0x80});
rx_telegram({0xB0, 0x0B, 0xFF, 0x00, 0x02, 0x62, 0x01, 0x44, 0x03, 0x30, 0x80, 00, 0x80, 00, 0x80, 00,
0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 0x33});
rx_telegram({0xB0, 00, 0xFF, 0x18, 02, 0x62, 0x80, 00, 0xB8});
EMSESP::send_raw_telegram("B0 00 FF 18 02 62 80 00 B8");
return;
}
if (strcmp(command, "heatpump") == 0) {
EMSESP::logger().info(F("Testing heatpump..."));
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
add_device(0x38, 200); // Enviline module
add_device(0x10, 192); // FW120 thermostat
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x6F, 0x00, 0xCF, 0x21, 0x2E, 0x20, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03}); // HC1
uart_telegram("38 0B FF 00 03 7B 0C 34 00 74");
return;
}
}
// used with the 'test' command, under su/admin
void Test::run_test_shell(uuid::console::Shell & shell, const std::string & command) {
// switch to su
shell.add_flags(CommandFlags::ADMIN);
if ((command == "default") || (command == "general") || (command.empty())) {
shell.printfln(F("Testing adding a general boiler & thermostat..."));
run_test("general");
shell.invoke_command("show devices");
shell.invoke_command("show");
shell.invoke_command("call system publish");
shell.invoke_command("show mqtt");
}
if (command == "render") {
shell.printfln(F("Testing render..."));
uint8_t test1 = 12;
int8_t test2 = -12;
uint16_t test3 = 456;
@@ -130,47 +251,34 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
}
if (command == "devices") {
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS); // this is important otherwise nothing will be picked up!
shell.printfln(F("Testing devices..."));
//emsdevices.push_back(EMSFactory::add(EMSdevice::DeviceType::BOILER, EMSdevice::EMS_DEVICE_ID_BOILER, 0, "", "My Boiler", 0, 0));
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS); // this is important otherwise nothing will be picked up!
// A fake response - UBADevices(0x07)
rx_telegram({0x08, 0x00, 0x07, 0x00, 0x0B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
}
if (command == "boiler") {
// question: do we need to set the mask?
std::string version("1.2.3");
EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline
// UBAuptime
uart_telegram({0x08, 0x0B, 0x14, 00, 0x3C, 0x1F, 0xAC, 0x70});
shell.invoke_command("show");
shell.invoke_command("call boiler info");
}
// check for boiler and controller on same product_id
if (command == "double") {
// question: do we need to set the mask?
std::string version("1.2.3");
EMSESP::add_device(0x08, 206, version, EMSdevice::Brand::BUDERUS); // Nefit Excellent HR30
EMSESP::add_device(0x09, 206, version, EMSdevice::Brand::BUDERUS); // Nefit Excellent HR30 Controller
shell.printfln(F("Testing double..."));
add_device(0x08, 206); // Nefit Excellent HR30
add_device(0x09, 206); // Nefit Excellent HR30 Controller
// UBAuptime
uart_telegram({0x08, 0x0B, 0x14, 00, 0x3C, 0x1F, 0xAC, 0x70});
}
// unknown device -
// unknown device
if (command == "unknown") {
// question: do we need to set the mask?
std::string version("1.2.3");
shell.printfln(F("Testing unknown..."));
// add boiler
EMSESP::add_device(0x08, 84, version, EMSdevice::Brand::BUDERUS);
add_device(0x08, 84);
// add Controller - BC10 GB142 - but using the same device_id to see what happens
EMSESP::add_device(0x09, 84, version, EMSdevice::Brand::BUDERUS);
// add Controller - BC10 GB142 - but using the same product_id to see what happens
add_device(0x09, 84);
// simulate getting version information back from an unknown device
// note there is no brand (byte 9)
@@ -181,33 +289,22 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
}
if (command == "unknown2") {
shell.printfln(F("Testing unknown2..."));
// simulate getting version information back from an unknown device
rx_telegram({0x09, 0x0B, 0x02, 0x00, 0x5A, 0x01, 0x02}); // product id is 90 which doesn't exist
}
if (command == "gateway") {
// add 0x48 KM200, via a version command
rx_telegram({0x48, 0x0B, 0x02, 0x00, 0xBD, 0x04, 0x06, 00, 00, 00, 00, 00, 00, 00});
// Boiler(0x08) -> All(0x00), UBADevices(0x07), data: 09 01 00 00 00 00 00 00 01 00 00 00 00
// check: make sure 0x48 is not detected again !
rx_telegram({0x08, 0x00, 0x07, 0x00, 0x09, 01, 00, 00, 00, 00, 00, 00, 01, 00, 00, 00, 00});
// add thermostat - Thermostat: RC300/RC310/Moduline 3000/CW400/Sense II (DeviceID:0x10, ProductID:158, Version:03.03) ** master device **
std::string version("01.03");
EMSESP::add_device(0x10, 158, version, EMSdevice::Brand::BUDERUS);
// simulate incoming telegram
// Thermostat(0x10) -> 48(0x48), ?(0x26B), data: 6B 08 4F 00 00 00 02 00 00 00 02 00 03 00 03 00 03
rx_telegram({0x10, 0x48, 0xFF, 00, 01, 0x6B, 00, 0x6B, 0x08, 0x4F, 00, 00, 00, 02, 00, 00, 00, 02, 00, 03, 00, 03, 00, 03});
shell.printfln(F("Testing Gateway..."));
run_test("gateway");
}
if (command == "web") {
shell.printfln(F("Testing Web..."));
std::string version("1.2.3");
EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline
EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
add_device(0x08, 123); // Nefit Trendline
add_device(0x18, 157); // Bosch CR100
// add some data
// Boiler -> Me, UBAMonitorFast(0x18), telegram: 08 00 18 00 00 02 5A 73 3D 0A 10 65 40 02 1A 80 00 01 E1 01 76 0E 3D 48 00 C9 44 02 00 (#data=25)
@@ -228,57 +325,25 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
uart_telegram({0x98, 0x00, 0x06, 0x00, 0x00, 0x03, 0x04, 0x0C, 0x02, 0x33, 0x06, 00, 00, 00, 00, 00, 00});
shell.invoke_command("show");
StaticJsonDocument<2000> doc;
JsonObject root = doc.to<JsonObject>();
StaticJsonDocument<500> doc;
JsonObject root = doc.to<JsonObject>();
EMSESP::device_info_web(2, root); // show thermostat. use 1 for boiler
serializeJsonPretty(doc, shell);
shell.println();
}
if (command == "general") {
shell.printfln(F("Testing adding a boiler & thermostat..."));
std::string version("1.2.3");
EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline
EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
// add some data
// Boiler -> Me, UBAMonitorFast(0x18), telegram: 08 00 18 00 00 02 5A 73 3D 0A 10 65 40 02 1A 80 00 01 E1 01 76 0E 3D 48 00 C9 44 02 00 (#data=25)
uart_telegram({0x08, 0x00, 0x18, 0x00, 0x00, 0x02, 0x5A, 0x73, 0x3D, 0x0A, 0x10, 0x65, 0x40, 0x02, 0x1A,
0x80, 0x00, 0x01, 0xE1, 0x01, 0x76, 0x0E, 0x3D, 0x48, 0x00, 0xC9, 0x44, 0x02, 0x00});
// Boiler -> Thermostat, UBAParameterWW(0x33), telegram: 08 97 33 00 23 24 (#data=2)
uart_telegram({0x08, 0x98, 0x33, 0x00, 0x23, 0x24});
// Boiler -> Me, UBAParameterWW(0x33), telegram: 08 0B 33 00 08 FF 34 FB 00 28 00 00 46 00 FF FF 00 (#data=13)
uart_telegram({0x08, 0x0B, 0x33, 0x00, 0x08, 0xFF, 0x34, 0xFB, 0x00, 0x28, 0x00, 0x00, 0x46, 0x00, 0xFF, 0xFF, 0x00});
// Thermostat RCPLUSStatusMessage_HC1(0x01A5)
uart_telegram({0x98, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
if (command == "boiler") {
shell.printfln(F("Testing boiler..."));
run_test("boiler");
shell.invoke_command("show");
shell.invoke_command("publish");
shell.invoke_command("show mqtt");
shell.invoke_command("call boiler info");
}
if (command == "fr120") {
shell.printfln(F("Testing adding a thermostat FR120..."));
// add_device(0x10, 165, version, EMSdevice::Brand::BUDERUS);
// add_device(0x17, 125, version, EMSdevice::Brand::BUDERUS); // test unknown class test
// add_device(0x17, 93, version, EMSdevice::Brand::BUDERUS);
// add_device(0x17, 254, version, EMSdevice::Brand::BUDERUS); // test unknown product_id
// EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
std::string version("1.2.3");
// add a boiler
// EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline
// add a thermostat
EMSESP::add_device(0x10, 191, version, EMSdevice::Brand::JUNKERS); // FR120
add_device(0x10, 191); // FR120 thermostat
// HC1
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x6F, 0x00, 0xCF, 0x21, 0x2E, 0x20, 0x00, 0x2E, 0x24,
@@ -290,35 +355,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
if (command == "thermostat") {
shell.printfln(F("Testing adding a thermostat FW120..."));
// add_device(0x10, 165, version, EMSdevice::Brand::BUDERUS);
// add_device(0x17, 125, version, EMSdevice::Brand::BUDERUS); // test unknown class test
// add_device(0x17, 93, version, EMSdevice::Brand::BUDERUS);
// add_device(0x17, 254, version, EMSdevice::Brand::BUDERUS); // test unknown product_id
// EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
std::string version("1.2.3");
// add a boiler
// EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline
// add a thermostat
EMSESP::add_device(0x10, 192, version, EMSdevice::Brand::JUNKERS); // FW120
// HC1
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x6F, 0x00, 0xCF, 0x21, 0x2E, 0x20, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
// HC2
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x70, 0x00, 0xCF, 0x22, 0x2F, 0x10, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
// HC3
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
run_test("thermostat");
shell.invoke_command("show");
EMSESP::mqtt_.incoming("ems-esp/thermostat_hc1", "heat");
EMSESP::mqtt_.incoming("ems-esp/thermostat_hc2", "28.8");
EMSESP::mqtt_.incoming("ems-esp/thermostat", "{\"cmd\":\"temp\",\"id\":2,\"data\":22}");
@@ -327,13 +365,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
if (command == "tc100") {
shell.printfln(F("Testing adding a TC100 thermostat to the EMS bus..."));
std::string version("02.21");
// add a boiler
// EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline
// add a thermostat
EMSESP::add_device(0x18, 202, version, EMSdevice::Brand::BOSCH); // Bosch TC100 - https://github.com/proddy/EMS-ESP/issues/474
add_device(0x18, 202); // Bosch TC100 - https://github.com/proddy/EMS-ESP/issues/474
// 0x0A
uart_telegram({0x98, 0x0B, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -342,51 +375,19 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
if (command == "solar") {
shell.printfln(F("Testing Solar"));
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
std::string version("1.2.3");
EMSESP::add_device(0x30, 163, version, EMSdevice::Brand::BUDERUS); // SM100
// SM100Monitor - type 0x0362 EMS+ - for SM100 and SM200
// B0 0B FF 00 02 62 00 44 02 7A 80 00 80 00 80 00 80 00 80 00 80 00 00 7C 80 00 80 00 80 00 80
rx_telegram({0xB0, 0x0B, 0xFF, 00, 0x02, 0x62, 00, 0x44, 0x02, 0x7A, 0x80, 00, 0x80, 0x00, 0x80, 00,
0x80, 00, 0x80, 00, 0x80, 00, 00, 0x7C, 0x80, 00, 0x80, 00, 0x80, 00, 0x80});
rx_telegram({0xB0, 0x0B, 0xFF, 0x00, 0x02, 0x62, 0x01, 0x44, 0x03, 0x30, 0x80, 00, 0x80, 00, 0x80, 00,
0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 0x33});
rx_telegram({0xB0, 00, 0xFF, 0x18, 02, 0x62, 0x80, 00, 0xB8});
EMSESP::send_raw_telegram("B0 00 FF 18 02 62 80 00 B8");
run_test("solar");
uart_telegram("30 00 FF 0A 02 6A 04"); // SM100 pump on 1
uart_telegram("30 00 FF 00 02 64 00 00 00 04 00 00 FF 00 00 1E 0B 09 64 00 00 00 00"); // SM100 modulation
EMSESP::show_device_values(shell);
uart_telegram("30 00 FF 0A 02 6A 03"); // SM100 pump off 0
EMSESP::show_device_values(shell);
}
if (command == "heatpump") {
shell.printfln(F("Testing Heat Pump"));
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
std::string version("1.2.3");
// add heatpump
EMSESP::add_device(0x38, 200, version, EMSdevice::Brand::BUDERUS); // Enviline module
// add a thermostat
EMSESP::add_device(0x10, 192, version, EMSdevice::Brand::JUNKERS); // FW120
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x6F, 0x00, 0xCF, 0x21, 0x2E, 0x20, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03}); // HC1
uart_telegram("38 0B FF 00 03 7B 0C 34 00 74");
run_test("heatpump");
shell.invoke_command("call");
shell.invoke_command("call heatpump info");
EMSESP::show_device_values(shell);
}
if (command == "solar200") {
@@ -394,32 +395,26 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
std::string version("1.2.3");
EMSESP::add_device(0x30, 164, version, EMSdevice::Brand::BUDERUS); // SM200
add_device(0x30, 164); // SM200
// SM100Monitor - type 0x0362 EMS+ - for SM100 and SM200
// B0 0B FF 00 02 62 00 44 02 7A 80 00 80 00 80 00 80 00 80 00 80 00 00 7C 80 00 80 00 80 00 80
rx_telegram({0xB0, 0x0B, 0xFF, 00, 0x02, 0x62, 00, 0x44, 0x02, 0x7A, 0x80, 00, 0x80, 0x00, 0x80, 00,
0x80, 00, 0x80, 00, 0x80, 00, 00, 0x7C, 0x80, 00, 0x80, 00, 0x80, 00, 0x80});
EMSESP::show_device_values(shell);
rx_telegram({0xB0, 0x0B, 0xFF, 0x00, 0x02, 0x62, 0x01, 0x44, 0x03, 0x30, 0x80, 00, 0x80, 00, 0x80, 00,
0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 0x33});
EMSESP::show_device_values(shell);
rx_telegram({0xB0, 00, 0xFF, 0x18, 02, 0x62, 0x80, 00, 0xB8});
EMSESP::show_device_values(shell);
EMSESP::send_raw_telegram("B0 00 FF 18 02 62 80 00 B8");
uart_telegram("30 00 FF 0A 02 6A 04"); // SM100 pump on 1
uart_telegram("30 00 FF 00 02 64 00 00 00 04 00 00 FF 00 00 1E 0B 09 64 00 00 00 00"); // SM100 modulation
EMSESP::show_device_values(shell);
uart_telegram("30 00 FF 0A 02 6A 03"); // SM100 pump off 0
EMSESP::show_device_values(shell);
shell.invoke_command("show");
}
if (command == "km") {
@@ -429,13 +424,11 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
std::string version("1.2.3");
EMSESP::add_device(0x10, 158, version, EMSdevice::Brand::BUDERUS); // RC300
EMSESP::add_device(0x48, 189, version, EMSdevice::Brand::BUDERUS); // KM200
add_device(0x10, 158); // RC300
add_device(0x48, 189); // KM200
// see https://github.com/proddy/EMS-ESP/issues/390
/*
uart_telegram_withCRC("90 48 FF 04 01 A6 5C");
uart_telegram_withCRC("90 48 FF 00 01 A6 4C");
uart_telegram_withCRC("90 48 FF 08 01 A7 6D");
@@ -473,7 +466,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
uart_telegram_withCRC("C8 90 FF 00 02 01 A6 D0");
// uart_telegram_withCRC("10 00 FF 00 01 A5 00 D7 21 00 00 00 00 30 01 84 01 01 03 01 84 01 F1 00 00 11 01 00 08 63 00");
*/
uart_telegram_withCRC("C8 90 F7 02 01 FF 01 A6 BA");
@@ -494,8 +486,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_HT3); // switch to junkers
std::string version("1.2.3");
EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
add_device(0x18, 157); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
// RCPLUSStatusMessage_HC1(0x01A5)
// 98 00 FF 00 01 A5 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03 (no CRC)
@@ -582,12 +573,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
uart_telegram({0x88, 00, 0x2A, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0xD2, 00, 00, 0x80, 00, 00, 01, 0x9D, 0x80, 0x00, 0x02, 0x79, 00});
}
if (command == "send") {
shell.printfln(F("Sending to Tx..."));
EMSESP::show_ems(shell);
EMSESP::txservice_.send(); // send it to UART
}
if (command == "tx") {
shell.printfln(F("Testing Tx..."));
@@ -626,8 +611,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
EMSESP::txservice_.send(); // send it to UART
}
shell.loop_all();
EMSESP::txservice_.flush_tx_queue();
}
@@ -665,8 +648,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
shell.printfln(F("Testing Commands..."));
// add a thermostat with 3 HCs
std::string version("1.2.3");
EMSESP::add_device(0x10, 192, version, EMSdevice::Brand::JUNKERS); // FW120
add_device(0x10, 192); // FW120
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x6F, 0x00, 0xCF, 0x21, 0x2E, 0x20, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03}); // HC1
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x70, 0x00, 0xCF, 0x22, 0x2F, 0x10, 0x00, 0x2E, 0x24,
@@ -678,9 +661,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
shell.invoke_command("call");
shell.invoke_command("call system info");
char system_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
strcpy(system_topic, "ems-esp/system");
EMSESP::mqtt_.incoming(system_topic, "{\"cmd\":\"info\"}"); // this should fail
EMSESP::mqtt_.incoming("ems-esp/system", "{\"cmd\":\"info\"}"); // this should fail
shell.invoke_command("call thermostat wwmode"); // should do nothing
shell.invoke_command("call thermostat mode auto 2"); // should error, no hc2
@@ -689,8 +670,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
if (command == "pin") {
shell.printfln(F("Testing pin..."));
shell.invoke_command("su");
shell.invoke_command("call system pin");
shell.invoke_command("call system pin 1 true");
}
@@ -707,12 +686,10 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
});
// add a boiler
// question: do we need to set the mask?
std::string version("1.2.3");
EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline
add_device(0x08, 123); // Nefit Trendline
// add a thermostat
EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
add_device(0x18, 157); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
// RCPLUSStatusMessage_HC1(0x01A5) - HC1
uart_telegram({0x98, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24,
@@ -787,23 +764,31 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
}
if (command == "rx2") {
shell.printfln(F("Testing rx2..."));
uart_telegram({0x1B, 0x5B, 0xFD, 0x2D, 0x9E, 0x3A, 0xB6, 0xE5, 0x02, 0x20, 0x33, 0x30, 0x32, 0x3A, 0x20, 0x5B,
0x73, 0xFF, 0xFF, 0xCB, 0xDF, 0xB7, 0xA7, 0xB5, 0x67, 0x77, 0x77, 0xE4, 0xFF, 0xFD, 0x77, 0xFF});
}
// https://github.com/proddy/EMS-ESP/issues/380#issuecomment-633663007
if (command == "rx3") {
shell.printfln(F("Testing rx3..."));
uart_telegram({0x21, 0x0B, 0xFF, 0x00});
}
// testing the UART tx command, without a queue
if (command == "tx2") {
shell.printfln(F("Testing tx2..."));
uint8_t t[] = {0x0B, 0x88, 0x18, 0x00, 0x20, 0xD4}; // including CRC
EMSuart::transmit(t, sizeof(t));
}
// send read request with offset
if (command == "offset") {
shell.printfln(F("Testing offset..."));
// send_read_request(0x18, 0x08);
EMSESP::txservice_.read_request(0x18, 0x08, 27); // no offset
}
@@ -820,14 +805,13 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
});
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
std::string version("1.2.3");
// add controller
EMSESP::add_device(0x09, 114, version, EMSdevice::Brand::BUDERUS);
add_device(0x09, 114);
EMSESP::add_device(0x28, 160, version, EMSdevice::Brand::BUDERUS); // MM100, WWC
EMSESP::add_device(0x29, 161, version, EMSdevice::Brand::BUDERUS); // MM200, WWC
EMSESP::add_device(0x20, 160, version, EMSdevice::Brand::BOSCH); // MM100
add_device(0x28, 160); // MM100, WWC
add_device(0x29, 161); // MM200, WWC
add_device(0x20, 160); // MM100
// WWC1 on 0x29
uart_telegram({0xA9, 0x00, 0xFF, 0x00, 0x02, 0x32, 0x02, 0x6C, 0x00, 0x3C, 0x00, 0x3C, 0x3C, 0x46, 0x02, 0x03, 0x03, 0x00, 0x3C});
@@ -838,16 +822,11 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
// check for error "No telegram type handler found for ID 0x255 (src 0x20)"
uart_telegram({0xA0, 0x00, 0xFF, 0x00, 0x01, 0x55, 0x00, 0x1A});
shell.invoke_command("show");
shell.invoke_command("call");
shell.invoke_command("call mixer info");
shell.invoke_command("publish");
shell.invoke_command("show mqtt");
shell.invoke_command("call mixer");
// shell.invoke_command("show");
// shell.invoke_command("call mixer info");
// shell.invoke_command("call system publish");
// shell.invoke_command("show mqtt");
}
// finally dump to console
EMSESP::loop();
}
// simulates a telegram in the Rx queue, but without the CRC which is added automatically
@@ -861,6 +840,7 @@ void Test::rx_telegram(const std::vector<uint8_t> & rx_data) {
}
data[i] = EMSESP::rxservice_.calculate_crc(data, i);
EMSESP::rxservice_.add(data, len + 1);
EMSESP::loop();
}
// simulates a telegram straight from UART, but without the CRC which is added automatically
@@ -874,7 +854,7 @@ void Test::uart_telegram(const std::vector<uint8_t> & rx_data) {
}
data[i] = EMSESP::rxservice_.calculate_crc(data, i);
EMSESP::incoming_telegram(data, i + 1);
EMSESP::rxservice_.loop();
EMSESP::loop();
}
// takes raw string, assuming it contains the CRC. This is what is output from 'watch raw'
@@ -912,7 +892,7 @@ void Test::uart_telegram_withCRC(const char * rx_data) {
}
EMSESP::incoming_telegram(data, count + 1);
EMSESP::rxservice_.loop();
EMSESP::loop();
}
// takes raw string, adds CRC to end
@@ -952,14 +932,14 @@ void Test::uart_telegram(const char * rx_data) {
data[count + 1] = EMSESP::rxservice_.calculate_crc(data, count + 1); // add CRC
EMSESP::incoming_telegram(data, count + 2);
EMSESP::rxservice_.loop();
EMSESP::loop();
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic pop
// Sends version telegram. Version is hardcoded to 1.0
void Test::add_device(uint8_t device_id, uint8_t product_id) {
// Send version: 09 0B 02 00 PP V1 V2
uart_telegram({device_id, EMSESP_DEFAULT_EMS_BUS_ID, EMSdevice::EMS_TYPE_VERSION, 0, product_id, 1, 0});
}
} // namespace emsesp

View File

@@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#if defined(EMSESP_DEBUG)
#if defined(EMSESP_TEST)
#ifndef EMSESP_TEST_H
#define EMSESP_TEST_H
@@ -40,12 +40,14 @@ namespace emsesp {
class Test {
public:
static void run_test(uuid::console::Shell & shell, const std::string & command); // only for testing
static void run_test_shell(uuid::console::Shell & shell, const std::string & command);
static void run_test(const char * command, int8_t id = 0);
static void dummy_mqtt_commands(const char * message);
static void rx_telegram(const std::vector<uint8_t> & data);
static void uart_telegram(const std::vector<uint8_t> & rx_data);
static void uart_telegram(const char * rx_data);
static void uart_telegram_withCRC(const char * rx_data);
static void add_device(uint8_t device_id, uint8_t product_id);
};
} // namespace emsesp

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "2.1.1b1"
#define EMSESP_APP_VERSION "2.1.1b2"