v2.0.1 - Merge remote-tracking branch 'origin/dev' into main

This commit is contained in:
proddy
2020-09-13 11:20:40 +02:00
90 changed files with 4959 additions and 1150 deletions

View File

@@ -81,7 +81,7 @@ void EMSESPDevicesService::device_data(AsyncWebServerRequest * request, JsonVari
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_EMSESP_DEVICE_SIZE);
#ifndef EMSESP_STANDALONE
uint8_t id = json["id"]; // get id from selected table row
EMSESP::device_info(id, (JsonObject &)response->getRoot());
EMSESP::device_info_web(id, (JsonObject &)response->getRoot());
#endif
response->setLength();
request->send(response);

View File

@@ -24,7 +24,7 @@
#include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#define MAX_EMSESP_DEVICE_SIZE 1536
#define MAX_EMSESP_DEVICE_SIZE 1700
#define EMSESP_DEVICES_SERVICE_PATH "/rest/allDevices"
#define SCAN_DEVICES_SERVICE_PATH "/rest/scanDevices"

View File

@@ -36,11 +36,12 @@ void EMSESPSettings::read(EMSESPSettings & settings, JsonObject & root) {
root["master_thermostat"] = settings.master_thermostat;
root["shower_timer"] = settings.shower_timer;
root["shower_alert"] = settings.shower_alert;
root["hide_led"] = settings.hide_led;
root["rx_gpio"] = settings.rx_gpio;
root["tx_gpio"] = settings.tx_gpio;
root["dallas_gpio"] = settings.dallas_gpio;
root["dallas_parasite"] = settings.dallas_parasite;
root["led_gpio"] = settings.led_gpio;
root["hide_led"] = settings.hide_led;
}
StateUpdateResult EMSESPSettings::update(JsonObject & root, EMSESPSettings & settings) {
@@ -52,11 +53,12 @@ StateUpdateResult EMSESPSettings::update(JsonObject & root, EMSESPSettings & set
settings.master_thermostat = root["master_thermostat"] | EMSESP_DEFAULT_MASTER_THERMOSTAT;
settings.shower_timer = root["shower_timer"] | EMSESP_DEFAULT_SHOWER_TIMER;
settings.shower_alert = root["shower_alert"] | EMSESP_DEFAULT_SHOWER_ALERT;
settings.hide_led = root["hide_led"] | EMSESP_DEFAULT_HIDE_LED;
settings.rx_gpio = root["rx_gpio"] | EMSESP_DEFAULT_RX_GPIO;
settings.tx_gpio = root["tx_gpio"] | EMSESP_DEFAULT_TX_GPIO;
settings.dallas_gpio = root["dallas_gpio"] | EMSESP_DEFAULT_DALLAS_GPIO;
settings.dallas_parasite = root["dallas_parasite"] | EMSESP_DEFAULT_DALLAS_PARASITE;
settings.led_gpio = root["led_gpio"] | EMSESP_DEFAULT_LED_GPIO;
settings.hide_led = root["hide_led"] | EMSESP_DEFAULT_HIDE_LED;
return StateUpdateResult::CHANGED;
}
@@ -68,6 +70,8 @@ void EMSESPSettingsService::onUpdate() {
// EMSESP::system_.syslog_init(); // changing SysLog will require a restart
EMSESP::init_tx();
System::set_led();
Sensors sensors_; // Dallas sensors
sensors_.start();
}
void EMSESPSettingsService::begin() {

View File

@@ -34,6 +34,7 @@
#define EMSESP_DEFAULT_SHOWER_TIMER false
#define EMSESP_DEFAULT_SHOWER_ALERT false
#define EMSESP_DEFAULT_HIDE_LED false
#define EMSESP_DEFAULT_DALLAS_PARASITE false
// Default GPIO PIN definitions
#if defined(ESP8266)
@@ -65,14 +66,15 @@ class EMSESPSettings {
uint8_t master_thermostat;
bool shower_timer;
bool shower_alert;
bool hide_led;
int8_t syslog_level; // uuid::log::Level
uint32_t syslog_mark_interval;
String syslog_host;
uint8_t rx_gpio;
uint8_t tx_gpio;
uint8_t dallas_gpio;
bool dallas_parasite;
uint8_t led_gpio;
bool hide_led;
static void read(EMSESPSettings & settings, JsonObject & root);
static StateUpdateResult update(JsonObject & root, EMSESPSettings & settings);

View File

@@ -88,9 +88,13 @@ void EMSESPShell::display_banner() {
// load the list of commands
add_console_commands();
// turn off watch
// turn off watch, unless is test mode
emsesp::EMSESP::watch_id(WATCH_ID_NONE);
#if defined(EMSESP_STANDALONE)
emsesp::EMSESP::watch(EMSESP::WATCH_ON);
#else
emsesp::EMSESP::watch(EMSESP::WATCH_OFF);
#endif
}
// pre-loads all the console commands into the MAIN context
@@ -240,6 +244,17 @@ void EMSESPShell::add_console_commands() {
});
});
commands->add_command(ShellContext::MAIN,
CommandFlags::ADMIN,
flash_string_vector{F_(read)},
flash_string_vector{F_(deviceid_mandatory), F_(typeid_mandatory)},
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
uint8_t device_id = Helpers::hextoint(arguments.front().c_str());
uint16_t type_id = Helpers::hextoint(arguments.back().c_str());
EMSESP::set_read_id(type_id);
EMSESP::send_read_request(type_id, device_id);
});
/*
* add all the submenu contexts...
*/
@@ -393,7 +408,7 @@ void Console::load_standard_commands(unsigned int context) {
flash_string_vector{F_(watch)},
flash_string_vector{F_(watch_format_optional), F_(watchid_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) {
uint16_t watch_id;
uint16_t watch_id = WATCH_ID_NONE;
if (!arguments.empty()) {
// get raw/pretty
@@ -403,16 +418,16 @@ void Console::load_standard_commands(unsigned int context) {
emsesp::EMSESP::watch(EMSESP::WATCH_ON); // on
} else if (arguments[0] == read_flash_string(F_(off))) {
emsesp::EMSESP::watch(EMSESP::WATCH_OFF); // off
} else {
} else if (emsesp::EMSESP::watch() == EMSESP::WATCH_OFF) {
shell.printfln(F_(invalid_watch));
return;
} else {
watch_id = Helpers::hextoint(arguments[0].c_str());
}
if (arguments.size() == 2) {
// get the watch_id if its set
watch_id = Helpers::hextoint(arguments[1].c_str());
} else {
watch_id = WATCH_ID_NONE;
}
emsesp::EMSESP::watch_id(watch_id);
@@ -436,7 +451,9 @@ void Console::load_standard_commands(unsigned int context) {
}
watch_id = emsesp::EMSESP::watch_id();
if (watch_id != WATCH_ID_NONE) {
if (watch_id > 0x80) {
shell.printfln(F("Filtering only telegrams that match a telegram type of 0x%02X"), watch_id);
} else if (watch_id != WATCH_ID_NONE) {
shell.printfln(F("Filtering only telegrams that match a device ID or telegram type of 0x%02X"), watch_id);
}
});
@@ -526,6 +543,7 @@ std::string EMSESPStreamConsole::console_name() {
}
// Start up telnet and logging
// Log order is off, err, warning, notice, info, debug, trace, all
void Console::start() {
// if we've detected a boot into safe mode on ESP8266, start the Serial console too
// Serial is always on with the ESP32 as it has 2 UARTs
@@ -540,10 +558,15 @@ void Console::start() {
#ifndef ESP8266
#if defined(EMSESP_DEBUG)
shell->log_level(uuid::log::Level::DEBUG); // order is: err, warning, notice, info, debug, trace, all
shell->log_level(uuid::log::Level::DEBUG);
#endif
#endif
#if defined(EMSESP_FORCE_SERIAL)
shell->log_level(uuid::log::Level::DEBUG);
#endif
#if defined(EMSESP_STANDALONE)
// always start in su/admin mode when running tests
shell->add_flags(CommandFlags::ADMIN);
@@ -558,7 +581,7 @@ void Console::start() {
#endif
// turn watch off in case it was still set in the last session
emsesp::EMSESP::watch(EMSESP::WATCH_OFF);
// emsesp::EMSESP::watch(EMSESP::WATCH_OFF);
}
// handles telnet sync and logging to console

View File

@@ -39,7 +39,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_telegram_type(0x1C, F("UBAMaintenanceStatus"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAMaintenanceStatus(t); });
register_telegram_type(0x2A, F("MC10Status"), false, [&](std::shared_ptr<const Telegram> t) { process_MC10Status(t); });
register_telegram_type(0x33, F("UBAParameterWW"), true, [&](std::shared_ptr<const Telegram> t) { process_UBAParameterWW(t); });
register_telegram_type(0x14, F("UBATotalUptime"), false, [&](std::shared_ptr<const Telegram> t) { process_UBATotalUptime(t); });
register_telegram_type(0x14, F("UBATotalUptime"), true, [&](std::shared_ptr<const Telegram> t) { process_UBATotalUptime(t); });
register_telegram_type(0x35, F("UBAFlags"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAFlags(t); });
register_telegram_type(0x15, F("UBAMaintenanceData"), false, [&](std::shared_ptr<const Telegram> t) { process_UBAMaintenanceData(t); });
register_telegram_type(0x16, F("UBAParameters"), true, [&](std::shared_ptr<const Telegram> t) { process_UBAParameters(t); });
@@ -64,6 +64,14 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_mqtt_cmd(F("boilhystoff"), [&](const char * value, const int8_t id) { set_hyst_off(value, id); });
register_mqtt_cmd(F("burnperiod"), [&](const char * value, const int8_t id) { set_burn_period(value, id); });
register_mqtt_cmd(F("pumpdelay"), [&](const char * value, const int8_t id) { set_pump_delay(value, id); });
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
mqtt_format_ = settings.mqtt_format; // single, nested or ha
if (mqtt_format_ == MQTT_format::HA) {
register_mqtt_ha_config();
}
});
}
// add submenu context
@@ -77,9 +85,36 @@ void Boiler::add_context_menu() {
});
}
void Boiler::device_info(JsonArray & root) {
// create the config topic for Home Assistant MQTT Discovery
// homeassistant/sensor/ems-esp/boiler
// state is /state
// config is /config
void Boiler::register_mqtt_ha_config() {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
/*
* not finished yet - see https://github.com/proddy/EMS-ESP/issues/288
doc["name"] = "boiler";
doc["uniq_id"] = "boiler";
// Mqtt::publish(topic); // empty payload, this remove any previous config sent to HA
Mqtt::publish("homeassistant/sensor/ems-esp/boiler/config", doc, true); // publish the config payload with retain flag
*/
}
void Boiler::device_info_web(JsonArray & root) {
JsonObject dataElement;
if (serviceCodeChar_[0] && Helpers::hasValue(serviceCode_)) {
dataElement = root.createNestedObject();
dataElement["name"] = F("Service Code");
char s[12];
snprintf_P(s, 12, PSTR("%s (%d)"), serviceCodeChar_, serviceCode_);
dataElement["value"] = s;
}
if (Helpers::hasValue(tap_water_active_, EMS_VALUE_BOOL)) {
dataElement = root.createNestedObject();
dataElement["name"] = F("Hot tap water");
@@ -98,6 +133,8 @@ void Boiler::device_info(JsonArray & root) {
render_value_json(root, "", F("Warm Water set temperature"), wWSetTmp_, F_(degrees));
render_value_json(root, "", F("Warm Water current temperature (intern)"), wWCurTmp_, F_(degrees), 10);
render_value_json(root, "", F("Warm Water current temperature (extern)"), wWCurTmp2_, F_(degrees), 10);
render_value_json(root, "", F("Pump modulation"), pumpMod_, F_(percent));
render_value_json(root, "", F("Heat Pump modulation"), pumpMod2_, F_(percent));
}
// publish values via MQTT
@@ -196,8 +233,8 @@ void Boiler::publish_values() {
if (Helpers::hasValue(wWOneTime_, EMS_VALUE_BOOL)) {
doc["wWOnetime"] = Helpers::render_value(s, wWOneTime_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWDesinfecting_, EMS_VALUE_BOOL)) {
doc["wWDesinfecting"] = Helpers::render_value(s, wWDesinfecting_, EMS_VALUE_BOOL);
if (Helpers::hasValue(wWDisinfecting_, EMS_VALUE_BOOL)) {
doc["wWDisinfecting"] = Helpers::render_value(s, wWDisinfecting_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(wWReadiness_, EMS_VALUE_BOOL)) {
doc["wWReady"] = Helpers::render_value(s, wWReadiness_, EMS_VALUE_BOOL);
@@ -287,12 +324,16 @@ void Boiler::publish_values() {
// if we have data, publish it
if (!doc.isNull()) {
Mqtt::publish("boiler_data", doc);
Mqtt::publish(F("boiler_data"), doc);
}
}
// called after a process command is called, to check values and see if we need to force an MQTT publish
bool Boiler::updated_values() {
if (changed_) {
changed_ = false;
return true;
}
return false;
}
@@ -353,7 +394,7 @@ void Boiler::show_values(uuid::console::Shell & shell) {
shell.printfln(F(" Warm Water active time: %d days %d hours %d minutes"), wWWorkM_ / 1440, (wWWorkM_ % 1440) / 60, wWWorkM_ % 60);
}
print_value(shell, 2, F("Warm Water charging"), wWHeat_, nullptr, EMS_VALUE_BOOL);
print_value(shell, 2, F("Warm Water disinfecting"), wWDesinfecting_, nullptr, EMS_VALUE_BOOL);
print_value(shell, 2, F("Warm Water disinfecting"), wWDisinfecting_, nullptr, EMS_VALUE_BOOL);
print_value(shell, 2, F("Selected flow temperature"), selFlowTemp_, F_(degrees));
print_value(shell, 2, F("Current flow temperature"), curFlowTemp_, F_(degrees), 10);
print_value(shell, 2, F("Max boiler temperature"), boilTemp_, F_(degrees), 10);
@@ -396,7 +437,7 @@ void Boiler::show_values(uuid::console::Shell & shell) {
print_value(shell, 2, F("Exhaust temperature"), exhaustTemp_, F_(degrees), 10);
print_value(shell, 2, F("Pump modulation"), pumpMod_, F_(percent));
print_value(shell, 2, F("Pump modulation2"), pumpMod2_, F_(percent));
print_value(shell, 2, F("Heat Pump modulation"), pumpMod2_, F_(percent));
print_value(shell, 2, F("Burner # starts"), burnStarts_, nullptr);
if (Helpers::hasValue(burnWorkMin_)) {
shell.printfln(F(" Total burner operating time: %d days %d hours %d minutes"), burnWorkMin_ / 1440, (burnWorkMin_ % 1440) / 60, burnWorkMin_ % 60);
@@ -435,54 +476,54 @@ void Boiler::check_active() {
uint8_t latest_boilerState = (tap_water_active_ << 1) + heating_active_;
if (latest_boilerState != last_boilerState) {
last_boilerState = latest_boilerState;
Mqtt::publish("tapwater_active", tap_water_active_);
Mqtt::publish("heating_active", heating_active_);
Mqtt::publish(F("tapwater_active"), tap_water_active_);
Mqtt::publish(F("heating_active"), heating_active_);
}
}
}
// 0x33
void Boiler::process_UBAParameterWW(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(wWActivated_, 1); // 0xFF means on
telegram->read_value(wWCircPump_, 6); // 0xFF means on
telegram->read_value(wWCircPumpMode_, 7); // 1=1x3min... 6=6x3min, 7=continuous
telegram->read_value(wWCircPumpType_, 10); // 0 = charge pump, 0xff = 3-way valve
telegram->read_value(wWSelTemp_, 2);
telegram->read_value(wWDisinfectTemp_, 8);
telegram->read_value(wWComfort_, 9);
changed_ |= telegram->read_value(wWActivated_, 1); // 0xFF means on
changed_ |= telegram->read_value(wWCircPump_, 6); // 0xFF means on
changed_ |= telegram->read_value(wWCircPumpMode_, 7); // 1=1x3min... 6=6x3min, 7=continuous
changed_ |= telegram->read_value(wWCircPumpType_, 10); // 0 = charge pump, 0xff = 3-way valve
changed_ |= telegram->read_value(wWSelTemp_, 2);
changed_ |= telegram->read_value(wWDisinfectTemp_, 8);
changed_ |= telegram->read_value(wWComfort_, 9);
}
// 0x18
void Boiler::process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(selFlowTemp_, 0);
telegram->read_value(curFlowTemp_, 1);
telegram->read_value(selBurnPow_, 3); // burn power max setting
telegram->read_value(curBurnPow_, 4);
changed_ |= telegram->read_value(selFlowTemp_, 0);
changed_ |= telegram->read_value(curFlowTemp_, 1);
changed_ |= telegram->read_value(selBurnPow_, 3); // burn power max setting
changed_ |= telegram->read_value(curBurnPow_, 4);
telegram->read_bitvalue(burnGas_, 7, 0);
telegram->read_bitvalue(fanWork_, 7, 2);
telegram->read_bitvalue(ignWork_, 7, 3);
telegram->read_bitvalue(heatPmp_, 7, 5);
telegram->read_bitvalue(wWHeat_, 7, 6);
telegram->read_bitvalue(wWCirc_, 7, 7);
changed_ |= telegram->read_bitvalue(burnGas_, 7, 0);
changed_ |= telegram->read_bitvalue(fanWork_, 7, 2);
changed_ |= telegram->read_bitvalue(ignWork_, 7, 3);
changed_ |= telegram->read_bitvalue(heatPmp_, 7, 5);
changed_ |= telegram->read_bitvalue(wWHeat_, 7, 6);
changed_ |= telegram->read_bitvalue(wWCirc_, 7, 7);
// warm water storage sensors (if present)
// wwStorageTemp2 is also used by some brands as the boiler temperature - see https://github.com/proddy/EMS-ESP/issues/206
telegram->read_value(wwStorageTemp1_, 9); // 0x8300 if not available
telegram->read_value(wwStorageTemp2_, 11); // 0x8000 if not available - this is boiler temp
changed_ |= telegram->read_value(wwStorageTemp1_, 9); // 0x8300 if not available
changed_ |= telegram->read_value(wwStorageTemp2_, 11); // 0x8000 if not available - this is boiler temp
telegram->read_value(retTemp_, 13);
telegram->read_value(flameCurr_, 15);
telegram->read_value(serviceCode_, 20);
changed_ |= telegram->read_value(retTemp_, 13);
changed_ |= telegram->read_value(flameCurr_, 15);
changed_ |= telegram->read_value(serviceCode_, 20);
// system pressure. FF means missing
telegram->read_value(sysPress_, 17); // is *10
changed_ |= telegram->read_value(sysPress_, 17); // is *10
// read the service code / installation status as appears on the display
if ((telegram->message_length > 18) && (telegram->offset == 0)) {
serviceCodeChar_[0] = char(telegram->message_data[18]); // ascii character 1
serviceCodeChar_[1] = char(telegram->message_data[19]); // ascii character 2
serviceCodeChar_[2] = '\0'; // null terminate string
changed_ |= telegram->read_value(serviceCodeChar_[0], 18);
changed_ |= telegram->read_value(serviceCodeChar_[1], 19);
serviceCodeChar_[2] = '\0'; // null terminate string
}
// at this point do a quick check to see if the hot water or heating is active
@@ -494,22 +535,22 @@ void Boiler::process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram) {
* received only after requested (not broadcasted)
*/
void Boiler::process_UBATotalUptime(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(UBAuptime_, 0, 3); // force to 3 bytes
changed_ |= telegram->read_value(UBAuptime_, 0, 3); // force to 3 bytes
}
/*
* UBAParameters - type 0x16
*/
void Boiler::process_UBAParameters(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(heating_temp_, 1);
telegram->read_value(burnPowermax_, 2);
telegram->read_value(burnPowermin_, 3);
telegram->read_value(boilTemp_off_, 4);
telegram->read_value(boilTemp_on_, 5);
telegram->read_value(burnPeriod_, 6);
telegram->read_value(pumpDelay_, 8);
telegram->read_value(pump_mod_max_, 9);
telegram->read_value(pump_mod_min_, 10);
changed_ |= telegram->read_value(heating_temp_, 1);
changed_ |= telegram->read_value(burnPowermax_, 2);
changed_ |= telegram->read_value(burnPowermin_, 3);
changed_ |= telegram->read_value(boilTemp_off_, 4);
changed_ |= telegram->read_value(boilTemp_on_, 5);
changed_ |= telegram->read_value(burnPeriod_, 6);
changed_ |= telegram->read_value(pumpDelay_, 8);
changed_ |= telegram->read_value(pump_mod_max_, 9);
changed_ |= telegram->read_value(pump_mod_min_, 10);
}
/*
@@ -517,19 +558,19 @@ void Boiler::process_UBAParameters(std::shared_ptr<const Telegram> telegram) {
* received every 10 seconds
*/
void Boiler::process_UBAMonitorWW(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(wWSetTmp_, 0);
telegram->read_value(wWCurTmp_, 1);
telegram->read_value(wWCurTmp2_, 3);
telegram->read_value(wWCurFlow_, 9);
changed_ |= telegram->read_value(wWSetTmp_, 0);
changed_ |= telegram->read_value(wWCurTmp_, 1);
changed_ |= telegram->read_value(wWCurTmp2_, 3);
changed_ |= telegram->read_value(wWCurFlow_, 9);
telegram->read_value(wWWorkM_, 10, 3); // force to 3 bytes
telegram->read_value(wWStarts_, 13, 3); // force to 3 bytes
changed_ |= telegram->read_value(wWWorkM_, 10, 3); // force to 3 bytes
changed_ |= telegram->read_value(wWStarts_, 13, 3); // force to 3 bytes
telegram->read_bitvalue(wWOneTime_, 5, 1);
telegram->read_bitvalue(wWDesinfecting_, 5, 2);
telegram->read_bitvalue(wWReadiness_, 5, 3);
telegram->read_bitvalue(wWRecharging_, 5, 4);
telegram->read_bitvalue(wWTemperatureOK_, 5, 5);
changed_ |= telegram->read_bitvalue(wWOneTime_, 5, 1);
changed_ |= telegram->read_bitvalue(wWDisinfecting_, 5, 2);
changed_ |= telegram->read_bitvalue(wWReadiness_, 5, 3);
changed_ |= telegram->read_bitvalue(wWRecharging_, 5, 4);
changed_ |= telegram->read_bitvalue(wWTemperatureOK_, 5, 5);
}
/*
@@ -537,18 +578,18 @@ void Boiler::process_UBAMonitorWW(std::shared_ptr<const Telegram> telegram) {
* Still to figure out are: serviceCode, retTemp, sysPress
*/
void Boiler::process_UBAMonitorFastPlus(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(selFlowTemp_, 6);
telegram->read_bitvalue(burnGas_, 11, 0);
telegram->read_bitvalue(wWHeat_, 11, 2);
telegram->read_value(curBurnPow_, 10);
telegram->read_value(selBurnPow_, 9);
telegram->read_value(curFlowTemp_, 7);
telegram->read_value(flameCurr_, 19);
changed_ |= telegram->read_value(selFlowTemp_, 6);
changed_ |= telegram->read_bitvalue(burnGas_, 11, 0);
changed_ |= telegram->read_bitvalue(wWHeat_, 11, 2);
changed_ |= telegram->read_value(curBurnPow_, 10);
changed_ |= telegram->read_value(selBurnPow_, 9);
changed_ |= telegram->read_value(curFlowTemp_, 7);
changed_ |= telegram->read_value(flameCurr_, 19);
// read the service code / installation status as appears on the display
if ((telegram->message_length > 4) && (telegram->offset == 0)) {
serviceCodeChar_[0] = char(telegram->message_data[4]); // ascii character 1
serviceCodeChar_[1] = char(telegram->message_data[5]); // ascii character 2
changed_ |= telegram->read_value(serviceCodeChar_[0], 4);
changed_ |= telegram->read_value(serviceCodeChar_[1], 5);
serviceCodeChar_[2] = '\0';
}
@@ -564,79 +605,79 @@ void Boiler::process_UBAMonitorFastPlus(std::shared_ptr<const Telegram> telegram
* 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 17 19 20 21 22 23 24
*/
void Boiler::process_UBAMonitorSlow(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(extTemp_, 0);
telegram->read_value(boilTemp_, 2);
telegram->read_value(exhaustTemp_, 4);
telegram->read_value(switchTemp_, 25); // only if there is a mixing module present
telegram->read_value(pumpMod_, 9);
telegram->read_value(burnStarts_, 10, 3); // force to 3 bytes
telegram->read_value(burnWorkMin_, 13, 3); // force to 3 bytes
telegram->read_value(heatWorkMin_, 19, 3); // force to 3 bytes
changed_ |= telegram->read_value(extTemp_, 0);
changed_ |= telegram->read_value(boilTemp_, 2);
changed_ |= telegram->read_value(exhaustTemp_, 4);
changed_ |= telegram->read_value(switchTemp_, 25); // only if there is a mixing module present
changed_ |= telegram->read_value(pumpMod_, 9);
changed_ |= telegram->read_value(burnStarts_, 10, 3); // force to 3 bytes
changed_ |= telegram->read_value(burnWorkMin_, 13, 3); // force to 3 bytes
changed_ |= telegram->read_value(heatWorkMin_, 19, 3); // force to 3 bytes
}
/*
* UBAMonitorSlowPlus2 - type 0xE3
*/
void Boiler::process_UBAMonitorSlowPlus2(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(pumpMod2_, 13);
changed_ |= telegram->read_value(pumpMod2_, 13); // Heat Pump Modulation
}
/*
* UBAMonitorSlowPlus - type 0xE5 - central heating monitor EMS+
*/
void Boiler::process_UBAMonitorSlowPlus(std::shared_ptr<const Telegram> telegram) {
telegram->read_bitvalue(fanWork_, 2, 2);
telegram->read_bitvalue(ignWork_, 2, 3);
telegram->read_bitvalue(heatPmp_, 2, 5);
telegram->read_bitvalue(wWCirc_, 2, 7);
telegram->read_value(burnStarts_, 10, 3); // force to 3 bytes
telegram->read_value(burnWorkMin_, 13, 3); // force to 3 bytes
telegram->read_value(heatWorkMin_, 19, 3); // force to 3 bytes
telegram->read_value(pumpMod_, 25);
changed_ |= telegram->read_bitvalue(fanWork_, 2, 2);
changed_ |= telegram->read_bitvalue(ignWork_, 2, 3);
changed_ |= telegram->read_bitvalue(heatPmp_, 2, 5);
changed_ |= telegram->read_bitvalue(wWCirc_, 2, 7);
changed_ |= telegram->read_value(burnStarts_, 10, 3); // force to 3 bytes
changed_ |= telegram->read_value(burnWorkMin_, 13, 3); // force to 3 bytes
changed_ |= telegram->read_value(heatWorkMin_, 19, 3); // force to 3 bytes
changed_ |= telegram->read_value(pumpMod_, 25);
}
// 0xE9 - DHW Status
// e.g. 08 00 E9 00 37 01 F6 01 ED 00 00 00 00 41 3C 00 00 00 00 00 00 00 00 00 00 00 00 37 00 00 00 (CRC=77) #data=27
void Boiler::process_UBADHWStatus(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(wWSetTmp_, 0);
telegram->read_value(wWCurTmp_, 1);
telegram->read_value(wWCurTmp2_, 3);
changed_ |= telegram->read_value(wWSetTmp_, 0);
changed_ |= telegram->read_value(wWCurTmp_, 1);
changed_ |= telegram->read_value(wWCurTmp2_, 3);
telegram->read_value(wWWorkM_, 17, 3); // force to 3 bytes
telegram->read_value(wWStarts_, 14, 3); // force to 3 bytes
changed_ |= telegram->read_value(wWWorkM_, 17, 3); // force to 3 bytes
changed_ |= telegram->read_value(wWStarts_, 14, 3); // force to 3 bytes
telegram->read_bitvalue(wWOneTime_, 12, 2);
telegram->read_bitvalue(wWDesinfecting_, 12, 3);
telegram->read_bitvalue(wWReadiness_, 12, 4);
telegram->read_bitvalue(wWRecharging_, 13, 4);
telegram->read_bitvalue(wWTemperatureOK_, 13, 5);
telegram->read_bitvalue(wWCircPump_, 13, 2);
changed_ |= telegram->read_bitvalue(wWOneTime_, 12, 2);
changed_ |= telegram->read_bitvalue(wWDisinfecting_, 12, 3);
changed_ |= telegram->read_bitvalue(wWReadiness_, 12, 4);
changed_ |= telegram->read_bitvalue(wWRecharging_, 13, 4);
changed_ |= telegram->read_bitvalue(wWTemperatureOK_, 13, 5);
changed_ |= telegram->read_bitvalue(wWCircPump_, 13, 2);
telegram->read_value(wWActivated_, 20);
telegram->read_value(wWSelTemp_, 10);
telegram->read_value(wWDisinfectTemp_, 9);
changed_ |= telegram->read_value(wWActivated_, 20);
changed_ |= telegram->read_value(wWSelTemp_, 10);
changed_ |= telegram->read_value(wWDisinfectTemp_, 9);
}
// 0x2A - MC10Status
// e.g. 88 00 2A 00 00 00 00 00 00 00 00 00 D2 00 00 80 00 00 01 08 80 00 02 47 00
// see https://github.com/proddy/EMS-ESP/issues/397
void Boiler::process_MC10Status(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(wwMixTemperature_, 14);
telegram->read_value(wwBufferBoilerTemperature_, 18);
changed_ |= telegram->read_value(wwMixTemperature_, 14);
changed_ |= telegram->read_value(wwBufferBoilerTemperature_, 18);
}
/*
* UBAOutdoorTemp - type 0xD1 - external temperature EMS+
*/
void Boiler::process_UBAOutdoorTemp(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(extTemp_, 0);
changed_ |= telegram->read_value(extTemp_, 0);
}
// UBASetPoint 0x1A
void Boiler::process_UBASetPoints(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(setFlowTemp_, 0); // boiler set temp from thermostat
telegram->read_value(setBurnPow_, 1); // max output power in %
telegram->read_value(setWWPumpPow_, 2); // ww pump speed/power?
changed_ |= telegram->read_value(setFlowTemp_, 0); // boiler set temp from thermostat
changed_ |= telegram->read_value(setBurnPow_, 1); // max output power in %
changed_ |= telegram->read_value(setWWPumpPow_, 2); // ww pump speed/power?
}
#pragma GCC diagnostic push
@@ -683,8 +724,8 @@ void Boiler::set_warmwater_temp(const char * value, const int8_t id) {
}
LOG_INFO(F("Setting boiler warm water temperature to %d C"), v);
write_command(EMS_TYPE_UBAParameterWW, 2, v);
write_command(EMS_TYPE_UBAFlags, 3, v); // for i9000, see #397
write_command(EMS_TYPE_UBAParameterWW, 2, v, EMS_TYPE_UBAParameterWW);
write_command(EMS_TYPE_UBAFlags, 3, v, EMS_TYPE_UBAParameterWW); // for i9000, see #397
}
// flow temp
@@ -695,7 +736,7 @@ void Boiler::set_flow_temp(const char * value, const int8_t id) {
}
LOG_INFO(F("Setting boiler flow temperature to %d C"), v);
write_command(EMS_TYPE_UBASetPoints, 0, v);
write_command(EMS_TYPE_UBASetPoints, 0, v, EMS_TYPE_UBASetPoints);
}
// set min boiler output
@@ -705,7 +746,7 @@ void Boiler::set_min_power(const char * value, const int8_t id) {
return;
}
LOG_INFO(F("Setting boiler min power to "), v);
write_command(EMS_TYPE_UBAParameters, 3, v);
write_command(EMS_TYPE_UBAParameters, 3, v, EMS_TYPE_UBAParameters);
}
// set max temp
@@ -716,10 +757,10 @@ void Boiler::set_max_power(const char * value, const int8_t id) {
}
LOG_INFO(F("Setting boiler max power to %d C"), v);
write_command(EMS_TYPE_UBAParameters, 2, v);
write_command(EMS_TYPE_UBAParameters, 2, v, EMS_TYPE_UBAParameters);
}
// set oiler on hysteresis
// set boiler on hysteresis
void Boiler::set_hyst_on(const char * value, const int8_t id) {
int v = 0;
if (!Helpers::value2number(value, v)) {
@@ -727,7 +768,7 @@ void Boiler::set_hyst_on(const char * value, const int8_t id) {
}
LOG_INFO(F("Setting boiler hysteresis on to %d C"), v);
write_command(EMS_TYPE_UBAParameters, 5, v);
write_command(EMS_TYPE_UBAParameters, 5, v, EMS_TYPE_UBAParameters);
}
// set boiler off hysteresis
@@ -738,7 +779,7 @@ void Boiler::set_hyst_off(const char * value, const int8_t id) {
}
LOG_INFO(F("Setting boiler hysteresis off to %d C"), v);
write_command(EMS_TYPE_UBAParameters, 4, v);
write_command(EMS_TYPE_UBAParameters, 4, v, EMS_TYPE_UBAParameters);
}
// set min burner period
@@ -749,7 +790,7 @@ void Boiler::set_burn_period(const char * value, const int8_t id) {
}
LOG_INFO(F("Setting burner min. period to %d min"), v);
write_command(EMS_TYPE_UBAParameters, 6, v);
write_command(EMS_TYPE_UBAParameters, 6, v, EMS_TYPE_UBAParameters);
}
// set pump delay
@@ -760,7 +801,7 @@ void Boiler::set_pump_delay(const char * value, const int8_t id) {
}
LOG_INFO(F("Setting boiler pump delay to %d min"), v);
write_command(EMS_TYPE_UBAParameters, 8, v);
write_command(EMS_TYPE_UBAParameters, 8, v, EMS_TYPE_UBAParameters);
}
// note some boilers do not have this setting, than it's done by thermostat
@@ -782,7 +823,7 @@ void Boiler::set_warmwater_mode(const char * value, const int8_t id) {
} else {
return; // do nothing
}
write_command(EMS_TYPE_UBAParameterWW, 9, set);
write_command(EMS_TYPE_UBAParameterWW, 9, set, EMS_TYPE_UBAParameterWW);
}
// turn on/off warm water
@@ -801,7 +842,7 @@ void Boiler::set_warmwater_activated(const char * value, const int8_t id) {
} else {
n = (v ? 0xFF : 0x00); // 0xFF is on, 0x00 is off
}
write_command(EMS_TYPE_UBAParameterWW, 1, n);
write_command(EMS_TYPE_UBAParameterWW, 1, n, EMS_TYPE_UBAParameterWW);
}
// Activate / De-activate the Warm Tap Water
@@ -846,7 +887,7 @@ void Boiler::set_warmwater_onetime(const char * value, const int8_t id) {
}
LOG_INFO(F("Setting boiler warm water OneTime loading %s"), v ? "on" : "off");
write_command(EMS_TYPE_UBAFlags, 0, (v ? 0x22 : 0x02));
write_command(EMS_TYPE_UBAFlags, 0, (v ? 0x22 : 0x02), 0x18);
}
// Activate / De-activate circulation of warm water 0x35
@@ -858,7 +899,7 @@ void Boiler::set_warmwater_circulation(const char * value, const int8_t id) {
}
LOG_INFO(F("Setting boiler warm water circulation %s"), v ? "on" : "off");
write_command(EMS_TYPE_UBAFlags, 1, (v ? 0x22 : 0x02));
write_command(EMS_TYPE_UBAFlags, 1, (v ? 0x22 : 0x02), 0x18);
}
// add console commands

View File

@@ -40,7 +40,7 @@ class Boiler : public EMSdevice {
virtual void show_values(uuid::console::Shell & shell);
virtual void publish_values();
virtual void device_info(JsonArray & root);
virtual void device_info_web(JsonArray & root);
virtual bool updated_values();
virtual void add_context_menu();
@@ -48,8 +48,12 @@ class Boiler : public EMSdevice {
static uuid::log::Logger logger_;
void console_commands(Shell & shell, unsigned int context);
void register_mqtt_ha_config();
void check_active();
uint8_t last_boilerState = 0xFF; // remember last state of heating and warm water on/off
uint8_t mqtt_format_; // single, nested or ha
bool changed_ = false;
static constexpr uint8_t EMS_TYPE_UBAParameterWW = 0x33;
static constexpr uint8_t EMS_TYPE_UBAFunctionTest = 0x1D;
@@ -108,7 +112,7 @@ class Boiler : public EMSdevice {
uint32_t wWStarts_ = EMS_VALUE_ULONG_NOTSET; // Warm Water # starts
uint32_t wWWorkM_ = EMS_VALUE_ULONG_NOTSET; // Warm Water # minutes
uint8_t wWOneTime_ = EMS_VALUE_BOOL_NOTSET; // Warm Water one time function on/off
uint8_t wWDesinfecting_ = EMS_VALUE_BOOL_NOTSET; // Warm Water disinfection on/off
uint8_t wWDisinfecting_ = EMS_VALUE_BOOL_NOTSET; // Warm Water disinfection on/off
uint8_t wWReadiness_ = EMS_VALUE_BOOL_NOTSET; // Warm Water readiness on/off
uint8_t wWRecharging_ = EMS_VALUE_BOOL_NOTSET; // Warm Water recharge on/off
uint8_t wWTemperatureOK_ = EMS_VALUE_BOOL_NOTSET; // Warm Water temperature ok on/off
@@ -147,7 +151,6 @@ class Boiler : public EMSdevice {
void process_UBAMonitorSlow(std::shared_ptr<const Telegram> telegram);
void process_UBAMonitorSlowPlus(std::shared_ptr<const Telegram> telegram);
void process_UBAMonitorSlowPlus2(std::shared_ptr<const Telegram> telegram);
void process_UBAOutdoorTemp(std::shared_ptr<const Telegram> telegram);
void process_UBASetPoints(std::shared_ptr<const Telegram> telegram);
void process_UBAFlags(std::shared_ptr<const Telegram> telegram);
@@ -155,11 +158,8 @@ class Boiler : public EMSdevice {
void process_UBAMaintenanceStatus(std::shared_ptr<const Telegram> telegram);
void process_UBAMaintenanceData(std::shared_ptr<const Telegram> telegram);
void process_UBAErrorMessage(std::shared_ptr<const Telegram> telegram);
void process_UBADHWStatus(std::shared_ptr<const Telegram> telegram);
void check_active();
// commands - none of these use the additional id parameter
void set_warmwater_mode(const char * value, const int8_t id);
void set_warmwater_activated(const char * value, const int8_t id);

View File

@@ -28,7 +28,7 @@ Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
}
void Connect::device_info(JsonArray & root) {
void Connect::device_info_web(JsonArray & root) {
}
void Connect::add_context_menu() {

View File

@@ -37,7 +37,7 @@ class Connect : public EMSdevice {
virtual void show_values(uuid::console::Shell & shell);
virtual void publish_values();
virtual void device_info(JsonArray & root);
virtual void device_info_web(JsonArray & root);
virtual bool updated_values();
virtual void add_context_menu();

View File

@@ -31,7 +31,7 @@ Controller::Controller(uint8_t device_type, uint8_t device_id, uint8_t product_i
void Controller::add_context_menu() {
}
void Controller::device_info(JsonArray & root) {
void Controller::device_info_web(JsonArray & root) {
}
// display all values into the shell console

View File

@@ -37,7 +37,7 @@ class Controller : public EMSdevice {
virtual void show_values(uuid::console::Shell & shell);
virtual void publish_values();
virtual void device_info(JsonArray & root);
virtual void device_info_web(JsonArray & root);
virtual bool updated_values();
virtual void add_context_menu();

View File

@@ -31,7 +31,7 @@ Gateway::Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
void Gateway::add_context_menu() {
}
void Gateway::device_info(JsonArray & root) {
void Gateway::device_info_web(JsonArray & root) {
}
// display all values into the shell console

View File

@@ -37,7 +37,7 @@ class Gateway : public EMSdevice {
virtual void show_values(uuid::console::Shell & shell);
virtual void publish_values();
virtual void device_info(JsonArray & root);
virtual void device_info_web(JsonArray & root);
virtual bool updated_values();
virtual void add_context_menu();

View File

@@ -37,7 +37,7 @@ Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, c
void Heatpump::add_context_menu() {
}
void Heatpump::device_info(JsonArray & root) {
void Heatpump::device_info_web(JsonArray & root) {
}
// display all values into the shell console

View File

@@ -37,7 +37,7 @@ class Heatpump : public EMSdevice {
virtual void show_values(uuid::console::Shell & shell);
virtual void publish_values();
virtual void device_info(JsonArray & root);
virtual void device_info_web(JsonArray & root);
virtual bool updated_values();
virtual void add_context_menu();

View File

@@ -60,25 +60,31 @@ void Mixing::add_context_menu() {
}
// output json to web UI
void Mixing::device_info(JsonArray & root) {
void Mixing::device_info_web(JsonArray & root) {
if (type_ == Type::NONE) {
return; // don't have any values yet
}
if (type_ == Type::WWC) {
render_value_json(root, "", F("Warm Water Circuit"), hc_, nullptr);
render_value_json(root, "", F("Current warm water temperature"), flowTemp_, F_(degrees), 10);
render_value_json(root, "", F("Current pump status"), pump_, nullptr);
render_value_json(root, "", F("Current temperature status"), status_, nullptr);
} else {
render_value_json(root, "", F("Heating Circuit"), hc_, nullptr);
render_value_json(root, "", F("Current flow temperature"), flowTemp_, F_(degrees), 10);
render_value_json(root, "", F("Setpoint flow temperature"), flowSetTemp_, F_(degrees));
render_value_json(root, "", F("Current pump status"), pump_, nullptr, EMS_VALUE_BOOL);
render_value_json(root, "", F("Current valve status"), status_, F_(percent));
}
render_value_json(root, "", F("Current flow temperature"), flowTemp_, F_(degrees), 10);
render_value_json(root, "", F("Setpoint flow temperature"), flowSetTemp_, F_(degrees));
render_value_json(root, "", F("Current pump modulation"), pumpMod_, F_(percent));
render_value_json(root, "", F("Current valve status"), status_, nullptr);
}
// check to see if values have been updated
bool Mixing::updated_values() {
if (changed_) {
changed_ = false;
return true;
}
return false;
}
@@ -96,14 +102,17 @@ void Mixing::show_values(uuid::console::Shell & shell) {
if (type_ == Type::WWC) {
print_value(shell, 2, F("Warm Water Circuit"), hc_, nullptr);
print_value(shell, 4, F("Current warm water temperature"), flowTemp_, F_(degrees), 10);
print_value(shell, 4, F("Current pump status"), pump_, nullptr);
print_value(shell, 4, F("Current temperature status"), status_, nullptr);
} else {
print_value(shell, 2, F("Heating Circuit"), hc_, nullptr);
print_value(shell, 4, F("Current flow temperature"), flowTemp_, F_(degrees), 10);
print_value(shell, 4, F("Setpoint flow temperature"), flowSetTemp_, F_(degrees));
print_value(shell, 4, F("Current pump status"), pump_, nullptr, EMS_VALUE_BOOL);
print_value(shell, 4, F("Current valve status"), status_, F_(percent));
}
print_value(shell, 4, F("Current flow temperature"), flowTemp_, F_(degrees), 10);
print_value(shell, 4, F("Setpoint flow temperature"), flowSetTemp_, F_(degrees));
print_value(shell, 4, F("Current pump modulation"), pumpMod_, F_(percent));
print_value(shell, 4, F("Current valve status"), status_, nullptr);
shell.println();
}
@@ -112,37 +121,42 @@ void Mixing::show_values(uuid::console::Shell & shell) {
// ideally we should group up all the mixing units together into a nested JSON but for now we'll send them individually
void Mixing::publish_values() {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
char s[5]; // for formatting strings
switch (type_) {
case Type::HC:
doc["type"] = "hc";
if (Helpers::hasValue(flowTemp_)) {
doc["flowTemp"] = (float)flowTemp_ / 10;
}
if (Helpers::hasValue(flowSetTemp_)) {
doc["flowSetTemp"] = flowSetTemp_;
}
if (Helpers::hasValue(pump_)) {
doc["pumpStatus"] = Helpers::render_value(s, pump_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(status_)) {
doc["valveStatus"] = status_;
}
break;
case Type::WWC:
doc["type"] = "wwc";
if (Helpers::hasValue(flowTemp_)) {
doc["wwTemp"] = (float)flowTemp_ / 10;
}
if (Helpers::hasValue(pump_)) {
doc["pumpStatus"] = pump_;
}
if (Helpers::hasValue(status_)) {
doc["tempStatus"] = status_;
}
break;
case Type::NONE:
default:
return;
}
if (Helpers::hasValue(flowTemp_)) {
doc["flowTemp"] = (float)flowTemp_ / 10;
}
if (Helpers::hasValue(pumpMod_)) {
doc["pumpMod"] = pumpMod_;
}
if (Helpers::hasValue(status_)) {
doc["status"] = status_;
}
if (Helpers::hasValue(flowSetTemp_)) {
doc["flowSetTemp"] = flowSetTemp_;
}
char topic[30];
char s[3]; // for formatting strings
strlcpy(topic, "mixing_data", 30);
strlcat(topic, Helpers::itoa(s, get_device_id() - 0x20 + 1), 30); // append hc to topic
Mqtt::publish(topic, doc);
@@ -153,11 +167,11 @@ void Mixing::publish_values() {
// A0 0B FF 00 01 D7 00 00 00 80 00 00 00 00 03 80
void Mixing::process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram) {
type_ = Type::HC;
hc_ = telegram->type_id - 0x02D7 + 1; // determine which circuit this is
telegram->read_value(flowTemp_, 3); // is * 10
telegram->read_value(flowSetTemp_, 5);
telegram->read_value(pumpMod_, 2);
telegram->read_value(status_, 1); // valve status
hc_ = telegram->type_id - 0x02D7 + 1; // determine which circuit this is
changed_ |= telegram->read_value(flowTemp_, 3); // is * 10
changed_ |= telegram->read_value(flowSetTemp_, 5);
changed_ |= telegram->read_value(pump_, 0);
changed_ |= telegram->read_value(status_, 2); // valve status
}
// Mixing module warm water loading/DHW - 0x0331, 0x0332
@@ -165,10 +179,10 @@ void Mixing::process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> tele
// A8 00 FF 00 02 31 02 35 00 3C 00 3C 3C 46 02 03 03 00 3C // in 0x29
void Mixing::process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> telegram) {
type_ = Type::WWC;
hc_ = telegram->type_id - 0x0331 + 1; // determine which circuit this is. There are max 2.
telegram->read_value(flowTemp_, 0); // is * 10
telegram->read_value(pumpMod_, 2);
telegram->read_value(status_, 11); // temp status
hc_ = telegram->type_id - 0x0331 + 1; // determine which circuit this is. There are max 2.
changed_ |= telegram->read_value(flowTemp_, 0); // is * 10
changed_ |= telegram->read_value(pump_, 2);
changed_ |= telegram->read_value(status_, 11); // temp status
}
// Mixing IMP - 0x010C
@@ -178,20 +192,16 @@ void Mixing::process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram)
type_ = Type::HC;
hc_ = get_device_id() - 0x20 + 1;
uint8_t ismixed = 0;
telegram->read_value(ismixed, 0); // check if circuit is active, 0-off, 1-unmixed, 2-mixed
changed_ |= telegram->read_value(ismixed, 0); // check if circuit is active, 0-off, 1-unmixed, 2-mixed
if (ismixed == 0) {
return;
}
if (ismixed == 2) { // we have a mixed circuit
telegram->read_value(flowTemp_, 3); // is * 10
telegram->read_value(flowSetTemp_, 5);
telegram->read_value(status_, 2); // valve status
}
uint8_t pump = 0xFF;
telegram->read_bitvalue(pump, 1, 0); // pump is also in unmixed circuits
if (pump != 0xFF) {
pumpMod_ = 100 * pump;
if (ismixed == 2) { // we have a mixed circuit
changed_ |= telegram->read_value(flowTemp_, 3); // is * 10
changed_ |= telegram->read_value(flowSetTemp_, 5);
changed_ |= telegram->read_value(status_, 2); // valve status
}
changed_ |= telegram->read_bitvalue(pump_, 1, 0); // pump is also in unmixed circuits
}
// Mixing on a MM10 - 0xAB
@@ -204,9 +214,10 @@ void Mixing::process_MMStatusMessage(std::shared_ptr<const Telegram> telegram) {
// 0x21 is position 2. 0x20 is typically reserved for the WM10 switch module
// see https://github.com/proddy/EMS-ESP/issues/270 and https://github.com/proddy/EMS-ESP/issues/386#issuecomment-629610918
hc_ = get_device_id() - 0x20 + 1;
telegram->read_value(flowTemp_, 1); // is * 10
telegram->read_value(pumpMod_, 3);
telegram->read_value(flowSetTemp_, 0);
changed_ |= telegram->read_value(flowTemp_, 1); // is * 10
changed_ |= telegram->read_value(pump_, 3);
changed_ |= telegram->read_value(flowSetTemp_, 0);
changed_ |= telegram->read_value(status_, 4); // valve status -100 to 100
}
#pragma GCC diagnostic push

View File

@@ -37,7 +37,7 @@ class Mixing : public EMSdevice {
virtual void show_values(uuid::console::Shell & shell);
virtual void publish_values();
virtual void device_info(JsonArray & root);
virtual void device_info_web(JsonArray & root);
virtual bool updated_values();
virtual void add_context_menu();
@@ -62,10 +62,11 @@ class Mixing : public EMSdevice {
private:
uint16_t hc_ = EMS_VALUE_USHORT_NOTSET;
uint16_t flowTemp_ = EMS_VALUE_USHORT_NOTSET;
uint8_t pumpMod_ = EMS_VALUE_UINT_NOTSET;
uint8_t status_ = EMS_VALUE_UINT_NOTSET;
uint8_t pump_ = EMS_VALUE_UINT_NOTSET;
int8_t status_ = EMS_VALUE_UINT_NOTSET;
uint8_t flowSetTemp_ = EMS_VALUE_UINT_NOTSET;
Type type_ = Type::NONE;
bool changed_ = false;
};
} // namespace emsesp

View File

@@ -54,7 +54,7 @@ void Solar::add_context_menu() {
}
// print to web
void Solar::device_info(JsonArray & root) {
void Solar::device_info_web(JsonArray & root) {
render_value_json(root, "", F("Collector temperature (TS1)"), collectorTemp_, F_(degrees), 10);
render_value_json(root, "", F("Tank bottom temperature (TS2)"), tankBottomTemp_, F_(degrees), 10);
render_value_json(root, "", F("Tank bottom temperature (TS5)"), tankBottomTemp2_, F_(degrees), 10);
@@ -145,7 +145,7 @@ void Solar::publish_values() {
}
if (Helpers::hasValue(pumpWorkMin_)) {
doc["pumpWorkMin"] = (float)pumpWorkMin_;
doc["pumpWorkMin"] = pumpWorkMin_;
}
if (Helpers::hasValue(tankHeated_, EMS_VALUE_BOOL)) {
@@ -168,11 +168,18 @@ void Solar::publish_values() {
doc["energyTotal"] = (float)energyTotal_ / 10;
}
Mqtt::publish("sm_data", doc);
// if we have data, publish it
if (!doc.isNull()) {
Mqtt::publish(F("sm_data"), doc);
}
}
// check to see if values have been updated
bool Solar::updated_values() {
if (changed_) {
changed_ = false;
return true;
}
return false;
}
@@ -182,11 +189,11 @@ void Solar::console_commands() {
// SM10Monitor - type 0x97
void Solar::process_SM10Monitor(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(collectorTemp_, 2); // collector temp from SM10, is *10
telegram->read_value(tankBottomTemp_, 5); // bottom temp from SM10, is *10
telegram->read_value(solarPumpModulation_, 4); // modulation solar pump
telegram->read_bitvalue(solarPump_, 7, 1);
telegram->read_value(pumpWorkMin_, 8);
changed_ |= telegram->read_value(collectorTemp_, 2); // collector temp from SM10, is *10
changed_ |= telegram->read_value(tankBottomTemp_, 5); // bottom temp from SM10, is *10
changed_ |= telegram->read_value(solarPumpModulation_, 4); // modulation solar pump
changed_ |= telegram->read_bitvalue(solarPump_, 7, 1);
changed_ |= telegram->read_value(pumpWorkMin_, 8);
}
/*
@@ -201,10 +208,10 @@ void Solar::process_SM10Monitor(std::shared_ptr<const Telegram> telegram) {
* bytes 20+21 = TS6 Temperature sensor external heat exchanger
*/
void Solar::process_SM100Monitor(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(collectorTemp_, 0); // is *10 - TS1: Temperature sensor for collector array 1
telegram->read_value(tankBottomTemp_, 2); // is *10 - TS2: Temperature sensor 1 cylinder, bottom
telegram->read_value(tankBottomTemp2_, 16); // is *10 - TS5: Temperature sensor 2 cylinder, bottom, or swimming pool
telegram->read_value(heatExchangerTemp_, 20); // is *10 - TS6: Heat exchanger temperature sensor
changed_ |= telegram->read_value(collectorTemp_, 0); // is *10 - TS1: Temperature sensor for collector array 1
changed_ |= telegram->read_value(tankBottomTemp_, 2); // is *10 - TS2: Temperature sensor 1 cylinder, bottom
changed_ |= telegram->read_value(tankBottomTemp2_, 16); // is *10 - TS5: Temperature sensor 2 cylinder, bottom, or swimming pool
changed_ |= telegram->read_value(heatExchangerTemp_, 20); // is *10 - TS6: Heat exchanger temperature sensor
}
#pragma GCC diagnostic push
@@ -221,9 +228,9 @@ void Solar::process_SM100Monitor2(std::shared_ptr<const Telegram> telegram) {
// SM100Config - 0x0366
// e.g. B0 00 FF 00 02 66 01 62 00 13 40 14
void Solar::process_SM100Config(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(availabilityFlag_, 0);
telegram->read_value(configFlag_, 1);
telegram->read_value(userFlag_, 2);
changed_ |= telegram->read_value(availabilityFlag_, 0);
changed_ |= telegram->read_value(configFlag_, 1);
changed_ |= telegram->read_value(userFlag_, 2);
}
/*
@@ -235,8 +242,8 @@ void Solar::process_SM100Config(std::shared_ptr<const Telegram> telegram) {
void Solar::process_SM100Status(std::shared_ptr<const Telegram> telegram) {
uint8_t solarpumpmod = solarPumpModulation_;
uint8_t cylinderpumpmod = cylinderPumpModulation_;
telegram->read_value(cylinderPumpModulation_, 8);
telegram->read_value(solarPumpModulation_, 9);
changed_ |= telegram->read_value(cylinderPumpModulation_, 8);
changed_ |= telegram->read_value(solarPumpModulation_, 9);
if (solarpumpmod == 0 && solarPumpModulation_ == 100) { // mask out boosts
solarPumpModulation_ = 15; // set to minimum
@@ -245,8 +252,8 @@ void Solar::process_SM100Status(std::shared_ptr<const Telegram> telegram) {
if (cylinderpumpmod == 0 && cylinderPumpModulation_ == 100) { // mask out boosts
cylinderPumpModulation_ = 15; // set to minimum
}
telegram->read_bitvalue(tankHeated_, 3, 1); // issue #422
telegram->read_bitvalue(collectorShutdown_, 3, 0); // collector shutdown
changed_ |= telegram->read_bitvalue(tankHeated_, 3, 1); // issue #422
changed_ |= telegram->read_bitvalue(collectorShutdown_, 3, 0); // collector shutdown
}
/*
@@ -256,8 +263,8 @@ void Solar::process_SM100Status(std::shared_ptr<const Telegram> telegram) {
* byte 10 = PS1 Solar circuit pump for collector array 1: test=b0001(1), on=b0100(4) and off=b0011(3)
*/
void Solar::process_SM100Status2(std::shared_ptr<const Telegram> telegram) {
telegram->read_bitvalue(valveStatus_, 4, 2); // on if bit 2 set
telegram->read_bitvalue(solarPump_, 10, 2); // on if bit 2 set
changed_ |= telegram->read_bitvalue(valveStatus_, 4, 2); // on if bit 2 set
changed_ |= telegram->read_bitvalue(solarPump_, 10, 2); // on if bit 2 set
}
/*
@@ -265,9 +272,9 @@ void Solar::process_SM100Status2(std::shared_ptr<const Telegram> telegram) {
* e.g. 30 00 FF 00 02 8E 00 00 00 00 00 00 06 C5 00 00 76 35
*/
void Solar::process_SM100Energy(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(energyLastHour_, 0); // last hour / 10 in Wh
telegram->read_value(energyToday_, 4); // todays in Wh
telegram->read_value(energyTotal_, 8); // total / 10 in kWh
changed_ |= telegram->read_value(energyLastHour_, 0); // last hour / 10 in Wh
changed_ |= telegram->read_value(energyToday_, 4); // todays in Wh
changed_ |= telegram->read_value(energyTotal_, 8); // total / 10 in kWh
}
/*
@@ -275,26 +282,26 @@ void Solar::process_SM100Energy(std::shared_ptr<const Telegram> telegram) {
* e.g. B0 00 FF 00 00 03 32 00 00 00 00 13 00 D6 00 00 00 FB D0 F0
*/
void Solar::process_ISM1StatusMessage(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(collectorTemp_, 4); // Collector Temperature
telegram->read_value(tankBottomTemp_, 6); // Temperature Bottom of Solar Boiler
changed_ |= telegram->read_value(collectorTemp_, 4); // Collector Temperature
changed_ |= telegram->read_value(tankBottomTemp_, 6); // Temperature Bottom of Solar Boiler
uint16_t Wh = 0xFFFF;
telegram->read_value(Wh, 2); // Solar Energy produced in last hour only ushort, is not * 10
changed_ |= telegram->read_value(Wh, 2); // Solar Energy produced in last hour only ushort, is not * 10
if (Wh != 0xFFFF) {
energyLastHour_ = Wh * 10; // set to *10
}
telegram->read_bitvalue(solarPump_, 8, 0); // PS1 Solar pump on (1) or off (0)
telegram->read_value(pumpWorkMin_, 10, 3); // force to 3 bytes
telegram->read_bitvalue(collectorShutdown_, 9, 0); // collector shutdown on/off
telegram->read_bitvalue(tankHeated_, 9, 2); // tank full
changed_ |= telegram->read_bitvalue(solarPump_, 8, 0); // PS1 Solar pump on (1) or off (0)
changed_ |= telegram->read_value(pumpWorkMin_, 10, 3); // force to 3 bytes
changed_ |= telegram->read_bitvalue(collectorShutdown_, 9, 0); // collector shutdown on/off
changed_ |= telegram->read_bitvalue(tankHeated_, 9, 2); // tank full
}
/*
* Junkers ISM1 Solar Module - type 0x0101 EMS+ for setting values
*/
void Solar::process_ISM1Set(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(setpoint_maxBottomTemp_, 6);
changed_ |= telegram->read_value(setpoint_maxBottomTemp_, 6);
}
} // namespace emsesp

View File

@@ -37,7 +37,7 @@ class Solar : public EMSdevice {
virtual void show_values(uuid::console::Shell & shell);
virtual void publish_values();
virtual void device_info(JsonArray & root);
virtual void device_info_web(JsonArray & root);
virtual bool updated_values();
virtual void add_context_menu();
@@ -65,6 +65,7 @@ class Solar : public EMSdevice {
uint8_t availabilityFlag_ = EMS_VALUE_BOOL_NOTSET;
uint8_t configFlag_ = EMS_VALUE_BOOL_NOTSET;
uint8_t userFlag_ = EMS_VALUE_BOOL_NOTSET;
bool changed_ = false;
void process_SM10Monitor(std::shared_ptr<const Telegram> telegram);
void process_SM100Monitor(std::shared_ptr<const Telegram> telegram);

View File

@@ -31,7 +31,7 @@ Switch::Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const
void Switch::add_context_menu() {
}
void Switch::device_info(JsonArray & root) {
void Switch::device_info_web(JsonArray & root) {
}
// display all values into the shell console

View File

@@ -37,7 +37,7 @@ class Switch : public EMSdevice {
virtual void show_values(uuid::console::Shell & shell);
virtual void publish_values();
virtual void device_info(JsonArray & root);
virtual void device_info_web(JsonArray & root);
virtual bool updated_values();
virtual void add_context_menu();

View File

@@ -26,7 +26,22 @@ uuid::log::Logger Thermostat::logger_{F_(thermostat), uuid::log::Facility::CONSO
Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
if (EMSESP::actual_master_thermostat() == 0) {
uint8_t actual_master_thermostat = EMSESP::actual_master_thermostat(); // what we're actually using
uint8_t master_thermostat = EMSESP_DEFAULT_MASTER_THERMOSTAT;
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
master_thermostat = settings.master_thermostat; // what the user has defined
});
uint8_t model = this->model();
// if we're on auto mode, register this thermostat if it has a device id of 0x10, 0x17 or 0x18
// or if its the master thermostat we defined
// see https://github.com/proddy/EMS-ESP/issues/362#issuecomment-629628161
if ((master_thermostat == device_id)
|| ((master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT) && (device_id < 0x19)
&& ((actual_master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT) || (device_id < actual_master_thermostat)))) {
EMSESP::actual_master_thermostat(device_id);
actual_master_thermostat = device_id;
this->reserve_mem(25); // reserve some space for the telegram registries, to avoid memory fragmentation
// common telegram handlers
@@ -34,7 +49,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
register_telegram_type(EMS_TYPE_RCTime, F("RCTime"), false, [&](std::shared_ptr<const Telegram> t) { process_RCTime(t); });
}
// RC10
if (flags == EMSdevice::EMS_DEVICE_FLAG_RC10) {
if (model == EMSdevice::EMS_DEVICE_FLAG_RC10) {
monitor_typeids = {0xB1};
set_typeids = {0xB0};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
@@ -43,7 +58,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
}
// RC35
} else if ((flags == EMSdevice::EMS_DEVICE_FLAG_RC35) || (flags == EMSdevice::EMS_DEVICE_FLAG_RC30_1)) {
} else if ((model == EMSdevice::EMS_DEVICE_FLAG_RC35) || (model == EMSdevice::EMS_DEVICE_FLAG_RC30_1)) {
monitor_typeids = {0x3E, 0x48, 0x52, 0x5C};
set_typeids = {0x3D, 0x47, 0x51, 0x5B};
timer_typeids = {0x3F, 0x49, 0x53, 0x5D};
@@ -55,10 +70,10 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
register_telegram_type(EMS_TYPE_wwSettings, F("WWSettings"), true, [&](std::shared_ptr<const Telegram> t) { process_RC35wwSettings(t); });
// RC20
} else if (flags == EMSdevice::EMS_DEVICE_FLAG_RC20) {
} else if (model == EMSdevice::EMS_DEVICE_FLAG_RC20) {
monitor_typeids = {0x91};
set_typeids = {0xA8};
if (EMSESP::actual_master_thermostat() == 0) {
if (actual_master_thermostat == device_id) {
for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(monitor_typeids[i], F("RC20Monitor"), false, [&](std::shared_ptr<const Telegram> t) { process_RC20Monitor(t); });
register_telegram_type(set_typeids[i], F("RC20Set"), false, [&](std::shared_ptr<const Telegram> t) { process_RC20Set(t); });
@@ -67,10 +82,10 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
register_telegram_type(0xAF, F("RC20Remote"), false, [&](std::shared_ptr<const Telegram> t) { process_RC20Remote(t); });
}
// RC20 newer
} else if (flags == EMSdevice::EMS_DEVICE_FLAG_RC20_2) {
} else if (model == EMSdevice::EMS_DEVICE_FLAG_RC20_2) {
monitor_typeids = {0xAE};
set_typeids = {0xAD};
if (EMSESP::actual_master_thermostat() == 0) {
if (actual_master_thermostat == device_id) {
for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(monitor_typeids[i], F("RC20Monitor"), false, [&](std::shared_ptr<const Telegram> t) { process_RC20Monitor_2(t); });
register_telegram_type(set_typeids[i], F("RC20Set"), false, [&](std::shared_ptr<const Telegram> t) { process_RC20Set_2(t); });
@@ -79,7 +94,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
register_telegram_type(0xAF, F("RC20Remote"), false, [&](std::shared_ptr<const Telegram> t) { process_RC20Remote(t); });
}
// RC30
} else if (flags == EMSdevice::EMS_DEVICE_FLAG_RC30) {
} else if (model == EMSdevice::EMS_DEVICE_FLAG_RC30) {
monitor_typeids = {0x41};
set_typeids = {0xA7};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
@@ -88,13 +103,13 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
}
// EASY
} else if (flags == EMSdevice::EMS_DEVICE_FLAG_EASY) {
} else if (model == EMSdevice::EMS_DEVICE_FLAG_EASY) {
monitor_typeids = {0x0A};
set_typeids = {};
register_telegram_type(monitor_typeids[0], F("EasyMonitor"), false, [&](std::shared_ptr<const Telegram> t) { process_EasyMonitor(t); });
register_telegram_type(monitor_typeids[0], F("EasyMonitor"), true, [&](std::shared_ptr<const Telegram> t) { process_EasyMonitor(t); });
// RC300/RC100
} else if ((flags == EMSdevice::EMS_DEVICE_FLAG_RC300) || (flags == EMSdevice::EMS_DEVICE_FLAG_RC100)) {
} else if ((model == EMSdevice::EMS_DEVICE_FLAG_RC300) || (model == EMSdevice::EMS_DEVICE_FLAG_RC100)) {
monitor_typeids = {0x02A5, 0x02A6, 0x02A7, 0x02A8};
set_typeids = {0x02B9, 0x02BA, 0x02BB, 0x02BC};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
@@ -105,7 +120,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
register_telegram_type(0x31E, F("RC300WWmode"), false, [&](std::shared_ptr<const Telegram> t) { process_RC300WWmode(t); });
// JUNKERS/HT3
} else if (flags == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
} else if (model == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
monitor_typeids = {0x016F, 0x0170, 0x0171, 0x0172};
set_typeids = {0x0165, 0x0166, 0x0167, 0x0168};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
@@ -113,7 +128,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
register_telegram_type(set_typeids[i], F("JunkersSet"), false, [&](std::shared_ptr<const Telegram> t) { process_JunkersSet(t); });
}
} else if (flags == (EMSdevice::EMS_DEVICE_FLAG_JUNKERS | EMSdevice::EMS_DEVICE_FLAG_JUNKERS_2)) {
} else if (model == (EMSdevice::EMS_DEVICE_FLAG_JUNKERS | EMSdevice::EMS_DEVICE_FLAG_JUNKERS_2)) {
monitor_typeids = {0x016F, 0x0170, 0x0171, 0x0172};
set_typeids = {0x0179, 0x017A, 0x017B, 0x017C};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
@@ -122,29 +137,16 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
}
}
uint8_t master_thermostat = 0;
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
master_thermostat = settings.master_thermostat; // what the user has defined
});
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
mqtt_format_ = settings.mqtt_format; // single, nested or ha
});
uint8_t actual_master_thermostat = EMSESP::actual_master_thermostat(); // what we're actually using
uint8_t num_devices = EMSESP::count_devices(EMSdevice::DeviceType::THERMOSTAT) + 1; // including this thermostat
// if we're on auto mode, register this thermostat if it has a device id of 0x10, 0x17 or 0x18
// or if its the master thermostat we defined
// see https://github.com/proddy/EMS-ESP/issues/362#issuecomment-629628161
if (((num_devices == 1) && (actual_master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT)) || (master_thermostat == device_id)) {
EMSESP::actual_master_thermostat(device_id);
LOG_DEBUG(F("Adding new thermostat with device ID 0x%02X (as master)"), device_id);
add_commands();
} else {
if (actual_master_thermostat != device_id) {
LOG_DEBUG(F("Adding new thermostat with device ID 0x%02X"), device_id);
return; // don't fetch data if more than 1 thermostat
}
LOG_DEBUG(F("Adding new thermostat with device ID 0x%02X (as master)"), device_id);
add_commands();
// reserve some memory for the heating circuits (max 4 to start with)
heating_circuits_.reserve(4);
@@ -161,8 +163,8 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
}
// prepare data for Web UI
void Thermostat::device_info(JsonArray & root) {
uint8_t flags = (this->flags() & 0x0F); // specific thermostat characteristics, strip the option bits
void Thermostat::device_info_web(JsonArray & root) {
uint8_t flags = this->model();
for (const auto & hc : heating_circuits_) {
if (!Helpers::hasValue(hc->setpoint_roomTemp)) {
@@ -240,23 +242,10 @@ bool Thermostat::updated_values() {
if (EMSESP::actual_master_thermostat() != this->get_device_id()) {
return false;
}
// quick hack to see if it changed. We simply just add up all the raw values
uint16_t new_value = 0;
static uint16_t current_value_ = 0;
for (const auto & hc : heating_circuits_) {
// don't publish if we haven't yet received some data
if (!Helpers::hasValue(hc->setpoint_roomTemp)) {
return false;
}
new_value += hc->setpoint_roomTemp + hc->curr_roomTemp + hc->mode;
}
if (new_value != current_value_) {
current_value_ = new_value;
if (changed_) {
changed_ = false;
return true;
}
return false;
}
@@ -267,15 +256,15 @@ void Thermostat::publish_values() {
return;
}
uint8_t flags = (this->flags() & 0x0F); // specific thermostat characteristics, stripping the option bits
bool has_data = false;
uint8_t flags = this->model();
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
JsonObject rootThermostat = doc.to<JsonObject>();
JsonObject dataThermostat;
// add external temp and other stuff specific to the RC30 and RC35
if ((flags == EMS_DEVICE_FLAG_RC35 || flags == EMS_DEVICE_FLAG_RC30_1) && (mqtt_format_ == MQTT_format::SINGLE || mqtt_format_ == MQTT_format::CUSTOM)) {
// if ((flags == EMS_DEVICE_FLAG_RC35 || flags == EMS_DEVICE_FLAG_RC30_1) && (mqtt_format_ == MQTT_format::SINGLE || mqtt_format_ == MQTT_format::CUSTOM)) {
if (flags == EMS_DEVICE_FLAG_RC35 || flags == EMS_DEVICE_FLAG_RC30_1) {
if (datetime_.size()) {
rootThermostat["time"] = datetime_.c_str();
}
@@ -314,135 +303,140 @@ void Thermostat::publish_values() {
}
// send this specific data using the thermostat_data topic
// if ((mqtt_format_ == MQTT_format::SINGLE) || (mqtt_format_ == MQTT_format::HA)) {
if (mqtt_format_ != MQTT_format::NESTED) {
Mqtt::publish("thermostat_data", doc);
Mqtt::publish(F("thermostat_data"), doc);
rootThermostat = doc.to<JsonObject>(); // clear object
}
}
// go through all the heating circuits
bool has_data = false;
for (const auto & hc : heating_circuits_) {
if (!Helpers::hasValue(hc->setpoint_roomTemp)) {
break; // skip this HC
}
if (hc->is_active()) {
has_data = true;
has_data = true;
// if the MQTT format is 'nested' or 'ha' then create the parent object hc<n>
// if (mqtt_format_ != MQTT_format::SINGLE) {
if ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::HA)) {
char hc_name[10]; // hc{1-4}
strlcpy(hc_name, "hc", 10);
char s[3];
strlcat(hc_name, Helpers::itoa(s, hc->hc_num()), 10);
dataThermostat = rootThermostat.createNestedObject(hc_name);
} else {
dataThermostat = rootThermostat;
}
// different logic on how temperature values are stored, depending on model
uint8_t setpoint_temp_divider;
uint8_t curr_temp_divider;
if (flags == EMS_DEVICE_FLAG_EASY) {
setpoint_temp_divider = 100;
curr_temp_divider = 100;
} else if (flags == EMS_DEVICE_FLAG_JUNKERS) {
setpoint_temp_divider = 10;
curr_temp_divider = 10;
} else {
setpoint_temp_divider = 2;
curr_temp_divider = 10;
}
if (Helpers::hasValue(hc->setpoint_roomTemp)) {
dataThermostat["seltemp"] = Helpers::round2((float)hc->setpoint_roomTemp / setpoint_temp_divider);
}
if (Helpers::hasValue(hc->curr_roomTemp)) {
dataThermostat["currtemp"] = Helpers::round2((float)hc->curr_roomTemp / curr_temp_divider);
}
if (Helpers::hasValue(hc->daytemp)) {
if (flags == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
dataThermostat["heattemp"] = (float)hc->daytemp / 2;
// if the MQTT format is 'nested' or 'ha' then create the parent object hc<n>
// if (mqtt_format_ != MQTT_format::SINGLE) {
if ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::HA)) {
char hc_name[10]; // hc{1-4}
strlcpy(hc_name, "hc", 10);
char s[3];
strlcat(hc_name, Helpers::itoa(s, hc->hc_num()), 10);
dataThermostat = rootThermostat.createNestedObject(hc_name);
} else {
dataThermostat["daytemp"] = (float)hc->daytemp / 2;
dataThermostat = rootThermostat;
}
}
if (Helpers::hasValue(hc->nighttemp)) {
if (flags == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
dataThermostat["ecotemp"] = (float)hc->nighttemp / 2;
// different logic on how temperature values are stored, depending on model
uint8_t setpoint_temp_divider;
uint8_t curr_temp_divider;
if (flags == EMS_DEVICE_FLAG_EASY) {
setpoint_temp_divider = 100;
curr_temp_divider = 100;
} else if (flags == EMS_DEVICE_FLAG_JUNKERS) {
setpoint_temp_divider = 10;
curr_temp_divider = 10;
} else {
dataThermostat["nighttemp"] = (float)hc->nighttemp / 2;
setpoint_temp_divider = 2;
curr_temp_divider = 10;
}
}
if (Helpers::hasValue(hc->holidaytemp)) {
dataThermostat["holidaytemp"] = (float)hc->holidaytemp / 2;
}
if (Helpers::hasValue(hc->nofrosttemp)) {
dataThermostat["nofrosttemp"] = (float)hc->nofrosttemp / 2;
}
if (Helpers::hasValue(hc->setpoint_roomTemp)) {
dataThermostat["seltemp"] = Helpers::round2((float)hc->setpoint_roomTemp / setpoint_temp_divider);
}
if (Helpers::hasValue(hc->heatingtype)) {
dataThermostat["heatingtype"] = hc->heatingtype;
}
if (Helpers::hasValue(hc->curr_roomTemp)) {
dataThermostat["currtemp"] = Helpers::round2((float)hc->curr_roomTemp / curr_temp_divider);
}
if (Helpers::hasValue(hc->targetflowtemp)) {
dataThermostat["targetflowtemp"] = hc->targetflowtemp;
}
if (Helpers::hasValue(hc->offsettemp)) {
dataThermostat["offsettemp"] = hc->offsettemp / 2;
}
if (Helpers::hasValue(hc->designtemp)) {
dataThermostat["designtemp"] = hc->designtemp;
}
if (Helpers::hasValue(hc->summertemp)) {
dataThermostat["summertemp"] = hc->summertemp;
}
// when using HA always send the mode otherwise it'll may break the component/widget and report an error
if ((Helpers::hasValue(hc->mode)) || (mqtt_format_ == MQTT_format::HA)) {
uint8_t hc_mode = hc->get_mode(flags);
// if we're sending to HA the only valid mode types are heat, auto and off
if (mqtt_format_ == MQTT_format::HA) {
if ((hc_mode == HeatingCircuit::Mode::MANUAL) || (hc_mode == HeatingCircuit::Mode::DAY)) {
hc_mode = HeatingCircuit::Mode::HEAT;
} else if ((hc_mode == HeatingCircuit::Mode::NIGHT) || (hc_mode == HeatingCircuit::Mode::OFF)) {
hc_mode = HeatingCircuit::Mode::OFF;
if (Helpers::hasValue(hc->daytemp)) {
if (flags == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
dataThermostat["heattemp"] = (float)hc->daytemp / 2;
} else {
hc_mode = HeatingCircuit::Mode::AUTO;
dataThermostat["daytemp"] = (float)hc->daytemp / 2;
}
}
dataThermostat["mode"] = mode_tostring(hc_mode);
}
if (Helpers::hasValue(hc->nighttemp)) {
if (flags == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
dataThermostat["ecotemp"] = (float)hc->nighttemp / 2;
} else {
dataThermostat["nighttemp"] = (float)hc->nighttemp / 2;
}
}
if (Helpers::hasValue(hc->holidaytemp)) {
dataThermostat["holidaytemp"] = (float)hc->holidaytemp / 2;
}
// special handling of mode type, for the RC35 replace with summer/holiday if set
// https://github.com/proddy/EMS-ESP/issues/373#issuecomment-619810209
if (Helpers::hasValue(hc->summer_mode) && hc->summer_mode) {
dataThermostat["modetype"] = F("summer");
} else if (Helpers::hasValue(hc->holiday_mode) && hc->holiday_mode) {
dataThermostat["modetype"] = F("holiday");
} else if (Helpers::hasValue(hc->mode_type)) {
dataThermostat["modetype"] = mode_tostring(hc->get_mode_type(flags));
}
if (Helpers::hasValue(hc->nofrosttemp)) {
dataThermostat["nofrosttemp"] = (float)hc->nofrosttemp / 2;
}
// if format is single, send immediately and clear object for next hc
// the topic will have the hc number appended
// if (mqtt_format_ == MQTT_format::SINGLE) {
if ((mqtt_format_ == MQTT_format::SINGLE) || (mqtt_format_ == MQTT_format::CUSTOM)) {
char topic[30];
char s[3];
strlcpy(topic, "thermostat_data", 30);
strlcat(topic, Helpers::itoa(s, hc->hc_num()), 30); // append hc to topic
Mqtt::publish(topic, doc);
rootThermostat = doc.to<JsonObject>(); // clear object
} else if (mqtt_format_ == MQTT_format::HA) {
std::string topic(100, '\0');
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/state"), hc->hc_num());
Mqtt::publish(topic, doc);
if (Helpers::hasValue(hc->heatingtype)) {
dataThermostat["heatingtype"] = hc->heatingtype;
}
if (Helpers::hasValue(hc->targetflowtemp)) {
dataThermostat["targetflowtemp"] = hc->targetflowtemp;
}
if (Helpers::hasValue(hc->offsettemp)) {
dataThermostat["offsettemp"] = hc->offsettemp / 2;
}
if (Helpers::hasValue(hc->designtemp)) {
dataThermostat["designtemp"] = hc->designtemp;
}
if (Helpers::hasValue(hc->summertemp)) {
dataThermostat["summertemp"] = hc->summertemp;
}
// when using HA always send the mode otherwise it'll may break the component/widget and report an error
if ((Helpers::hasValue(hc->mode)) || (mqtt_format_ == MQTT_format::HA)) {
uint8_t hc_mode = hc->get_mode(flags);
// if we're sending to HA the only valid mode types are heat, auto and off
if (mqtt_format_ == MQTT_format::HA) {
if ((hc_mode == HeatingCircuit::Mode::MANUAL) || (hc_mode == HeatingCircuit::Mode::DAY)) {
hc_mode = HeatingCircuit::Mode::HEAT;
} else if ((hc_mode == HeatingCircuit::Mode::NIGHT) || (hc_mode == HeatingCircuit::Mode::OFF)) {
hc_mode = HeatingCircuit::Mode::OFF;
} else {
hc_mode = HeatingCircuit::Mode::AUTO;
}
}
dataThermostat["mode"] = mode_tostring(hc_mode);
}
// special handling of mode type, for the RC35 replace with summer/holiday if set
// https://github.com/proddy/EMS-ESP/issues/373#issuecomment-619810209
if (Helpers::hasValue(hc->summer_mode) && hc->summer_mode) {
dataThermostat["modetype"] = F("summer");
} else if (Helpers::hasValue(hc->holiday_mode) && hc->holiday_mode) {
dataThermostat["modetype"] = F("holiday");
} else if (Helpers::hasValue(hc->mode_type)) {
dataThermostat["modetype"] = mode_tostring(hc->get_mode_type(flags));
}
// if format is single, send immediately and clear object for next hc
// the topic will have the hc number appended
// if (mqtt_format_ == MQTT_format::SINGLE) {
if ((mqtt_format_ == MQTT_format::SINGLE) || (mqtt_format_ == MQTT_format::CUSTOM)) {
char topic[30];
char s[3];
strlcpy(topic, "thermostat_data", 30);
strlcat(topic, Helpers::itoa(s, hc->hc_num()), 30); // append hc to topic
Mqtt::publish(topic, doc);
rootThermostat = doc.to<JsonObject>(); // clear object
} else if (mqtt_format_ == MQTT_format::HA) {
// see if we have already registered this with HA MQTT Discovery, if not send the config
if (!hc->ha_registered()) {
register_mqtt_ha_config(hc->hc_num());
hc->ha_registered(true);
}
// send the thermostat topic and payload data
std::string topic(100, '\0');
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/state"), hc->hc_num());
Mqtt::publish(topic, doc);
}
}
}
@@ -451,9 +445,8 @@ void Thermostat::publish_values() {
}
// if we're using nested json, send all in one go under one topic called thermostat_data
// if ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::CUSTOM)) {
if (mqtt_format_ == MQTT_format::NESTED) {
Mqtt::publish("thermostat_data", doc);
Mqtt::publish(F("thermostat_data"), doc);
}
}
@@ -461,7 +454,7 @@ void Thermostat::publish_values() {
// of nullptr if it doesn't exist yet
std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(const uint8_t hc_num) {
// if hc_num is 0 then return the first existing hc in the list
if (hc_num == 0) {
if (hc_num == AUTO_HEATING_CIRCUIT) {
for (const auto & heating_circuit : heating_circuits_) {
return heating_circuit;
}
@@ -522,19 +515,17 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
}
// create a new heating circuit object
auto new_hc = std::make_shared<Thermostat::HeatingCircuit>(hc_num, monitor_typeids[hc_num - 1], set_typeids[hc_num - 1]);
auto new_hc = std::make_shared<Thermostat::HeatingCircuit>(hc_num);
heating_circuits_.push_back(new_hc);
std::sort(heating_circuits_.begin(), heating_circuits_.end()); // sort based on hc number
// if we're using Home Assistant and HA discovery, register the new config
if (mqtt_format_ == MQTT_format::HA) {
register_mqtt_ha_config(hc_num);
}
// set the flag saying we want its data during the next auto fetch
toggle_fetch(monitor_typeids[hc_num - 1], toggle_);
toggle_fetch(set_typeids[hc_num - 1], toggle_);
if (set_typeids.size()) {
toggle_fetch(set_typeids[hc_num - 1], toggle_);
}
return heating_circuits_.back(); // even after sorting, this should still point back to the newly created HC
}
@@ -579,9 +570,24 @@ void Thermostat::register_mqtt_ha_config(uint8_t hc_num) {
doc["temp_step"] = "0.5";
JsonArray modes = doc.createNestedArray("modes");
modes.add("off");
modes.add("heat");
modes.add("auto");
uint8_t flags = this->model();
if (flags == EMSdevice::EMS_DEVICE_FLAG_RC20_2) {
modes.add("night");
modes.add("day");
} else if ((flags == EMSdevice::EMS_DEVICE_FLAG_RC300) || (flags == EMSdevice::EMS_DEVICE_FLAG_RC100)) {
modes.add("eco");
modes.add("comfort");
modes.add("auto");
} else if (flags == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
modes.add("nofrost");
modes.add("eco");
modes.add("heat");
modes.add("auto");
} else { // default for all other thermostats
modes.add("night");
modes.add("day");
modes.add("auto");
}
std::string topic(100, '\0'); // e.g homeassistant/climate/hc1/thermostat/config
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/config"), hc_num);
@@ -725,7 +731,7 @@ std::string Thermostat::mode_tostring(uint8_t mode) {
void Thermostat::show_values(uuid::console::Shell & shell) {
EMSdevice::show_values(shell); // always call this to show header
uint8_t flags = (this->flags() & 0x0F); // specific thermostat characteristics, strip the option bits
uint8_t flags = this->model();
if (datetime_.size()) {
shell.printfln(F(" Clock: %s"), datetime_.c_str());
@@ -804,9 +810,10 @@ void Thermostat::show_values(uuid::console::Shell & shell) {
}
for (const auto & hc : heating_circuits_) {
if (!Helpers::hasValue(hc->setpoint_roomTemp)) {
if (!hc->is_active()) {
break; // skip this HC
}
shell.printfln(F(" Heating Circuit %d:"), hc->hc_num());
// different thermostat types store their temperature values differently
@@ -882,16 +889,16 @@ void Thermostat::show_values(uuid::console::Shell & shell) {
void Thermostat::process_RC20Set(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->mode, 23);
changed_ |= telegram->read_value(hc->mode, 23);
}
// type 0xAE - data from the RC20 thermostat (0x17)
void Thermostat::process_RC20Monitor_2(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_bitvalue(hc->mode_type, 0, 7); // day/night MSB 7th bit is day
telegram->read_value(hc->setpoint_roomTemp, 2, 1); // is * 2, force as single byte
telegram->read_value(hc->curr_roomTemp, 3); // is * 10
changed_ |= telegram->read_bitvalue(hc->mode_type, 0, 7); // day/night MSB 7th bit is day
changed_ |= telegram->read_value(hc->setpoint_roomTemp, 2, 1); // is * 2, force as single byte
changed_ |= telegram->read_value(hc->curr_roomTemp, 3); // is * 10
}
// 0xAD - for reading the mode from the RC20/ES72 thermostat (0x17)
@@ -899,21 +906,21 @@ void Thermostat::process_RC20Monitor_2(std::shared_ptr<const Telegram> telegram)
void Thermostat::process_RC20Set_2(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->mode, 3);
changed_ |= telegram->read_value(hc->mode, 3);
}
// 0xAF - for reading the roomtemperature from the RC20/ES72 thermostat (0x18, 0x19, ..)
void Thermostat::process_RC20Remote(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->curr_roomTemp, 0);
changed_ |= telegram->read_value(hc->curr_roomTemp, 0);
}
// type 0xB1 - data from the RC10 thermostat (0x17)
void Thermostat::process_RC10Monitor(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->setpoint_roomTemp, 1, 1); // is * 2, force as single byte
telegram->read_value(hc->curr_roomTemp, 2); // is * 10
changed_ |= telegram->read_value(hc->setpoint_roomTemp, 1, 1); // is * 2, force as single byte
changed_ |= telegram->read_value(hc->curr_roomTemp, 2); // is * 10
}
#pragma GCC diagnostic push
@@ -928,56 +935,57 @@ void Thermostat::process_RC10Set(std::shared_ptr<const Telegram> telegram) {
// type 0x0165, ff
void Thermostat::process_JunkersSet(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->daytemp, 17); // is * 2
telegram->read_value(hc->nighttemp, 16); // is * 2
telegram->read_value(hc->nofrosttemp, 15); // is * 2
changed_ |= telegram->read_value(hc->daytemp, 17); // is * 2
changed_ |= telegram->read_value(hc->nighttemp, 16); // is * 2
changed_ |= telegram->read_value(hc->nofrosttemp, 15); // is * 2
}
// type 0x0179, ff
void Thermostat::process_JunkersSet2(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->daytemp, 7); // is * 2
telegram->read_value(hc->nighttemp, 6); // is * 2
telegram->read_value(hc->nofrosttemp, 5); // is * 2
changed_ |= telegram->read_value(hc->daytemp, 7); // is * 2
changed_ |= telegram->read_value(hc->nighttemp, 6); // is * 2
changed_ |= telegram->read_value(hc->nofrosttemp, 5); // is * 2
}
// type 0xA3 - for external temp settings from the the RC* thermostats (e.g. RC35)
void Thermostat::process_RCOutdoorTemp(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(dampedoutdoortemp_, 0);
telegram->read_value(tempsensor1_, 3); // sensor 1 - is * 10
telegram->read_value(tempsensor2_, 5); // sensor 2 - is * 10
changed_ |= telegram->read_value(dampedoutdoortemp_, 0);
changed_ |= telegram->read_value(tempsensor1_, 3); // sensor 1 - is * 10
changed_ |= telegram->read_value(tempsensor2_, 5); // sensor 2 - is * 10
}
// 0x91 - data from the RC20 thermostat (0x17) - 15 bytes long
void Thermostat::process_RC20Monitor(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->setpoint_roomTemp, 1, 1); // is * 2, force as single byte
telegram->read_value(hc->curr_roomTemp, 2); // is * 10
changed_ |= telegram->read_value(hc->setpoint_roomTemp, 1, 1); // is * 2, force as single byte
changed_ |= telegram->read_value(hc->curr_roomTemp, 2); // is * 10
}
// type 0x0A - data from the Nefit Easy/TC100 thermostat (0x18) - 31 bytes long
void Thermostat::process_EasyMonitor(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->curr_roomTemp, 8); // is * 100
telegram->read_value(hc->setpoint_roomTemp, 10); // is * 100
changed_ |= telegram->read_value(hc->curr_roomTemp, 8); // is * 100
changed_ |= telegram->read_value(hc->setpoint_roomTemp, 10); // is * 100
}
// Settings Parameters - 0xA5 - RC30_1
void Thermostat::process_IBASettings(std::shared_ptr<const Telegram> telegram) {
// 22 - display line on RC35
telegram->read_value(ibaMainDisplay_,
0); // display on Thermostat: 0 int. temp, 1 int. setpoint, 2 ext. temp., 3 burner temp., 4 ww temp, 5 functioning mode, 6 time, 7 data, 8 smoke temp
telegram->read_value(ibaLanguage_, 1); // language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian
telegram->read_value(ibaCalIntTemperature_, 2); // offset int. temperature sensor, by * 0.1 Kelvin
telegram->read_value(ibaBuildingType_, 6); // building type: 0 = light, 1 = medium, 2 = heavy
telegram->read_value(ibaMinExtTemperature_, 5); // min ext temp for heating curve, in deg., 0xF6=-10, 0x0 = 0, 0xFF=-1
telegram->read_value(ibaClockOffset_, 12); // offset (in sec) to clock, 0xff = -1 s, 0x02 = 2 s
changed_ |=
telegram->read_value(ibaMainDisplay_,
0); // display on Thermostat: 0 int. temp, 1 int. setpoint, 2 ext. temp., 3 burner temp., 4 ww temp, 5 functioning mode, 6 time, 7 data, 8 smoke temp
changed_ |= telegram->read_value(ibaLanguage_, 1); // language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian
changed_ |= telegram->read_value(ibaCalIntTemperature_, 2); // offset int. temperature sensor, by * 0.1 Kelvin
changed_ |= telegram->read_value(ibaBuildingType_, 6); // building type: 0 = light, 1 = medium, 2 = heavy
changed_ |= telegram->read_value(ibaMinExtTemperature_, 5); // min ext temp for heating curve, in deg., 0xF6=-10, 0x0 = 0, 0xFF=-1
changed_ |= telegram->read_value(ibaClockOffset_, 12); // offset (in sec) to clock, 0xff = -1 s, 0x02 = 2 s
}
// Settings WW 0x37 - RC35
void Thermostat::process_RC35wwSettings(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(wwMode_, 2); // 0 off, 1-on, 2-auto
changed_ |= telegram->read_value(wwMode_, 2); // 0 off, 1-on, 2-auto
}
// type 0x6F - FR10/FR50/FR100 Junkers
@@ -989,21 +997,21 @@ void Thermostat::process_JunkersMonitor(std::shared_ptr<const Telegram> telegram
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->curr_roomTemp, 4); // value is * 10
telegram->read_value(hc->setpoint_roomTemp, 2); // value is * 10
changed_ |= telegram->read_value(hc->curr_roomTemp, 4); // value is * 10
changed_ |= telegram->read_value(hc->setpoint_roomTemp, 2); // value is * 10
telegram->read_value(hc->mode_type, 0); // 1 = nofrost, 2 = eco, 3 = heat
telegram->read_value(hc->mode, 1); // 1 = manual, 2 = auto
changed_ |= telegram->read_value(hc->mode_type, 0); // 1 = nofrost, 2 = eco, 3 = heat
changed_ |= telegram->read_value(hc->mode, 1); // 1 = manual, 2 = auto
}
// type 0x02A5 - data from the Nefit RC1010/3000 thermostat (0x18) and RC300/310s on 0x10
void Thermostat::process_RC300Monitor(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->curr_roomTemp, 0); // is * 10
changed_ |= telegram->read_value(hc->curr_roomTemp, 0); // is * 10
telegram->read_bitvalue(hc->mode_type, 10, 1);
telegram->read_bitvalue(hc->mode, 10, 0); // bit 1, mode (auto=1 or manual=0)
changed_ |= telegram->read_bitvalue(hc->mode_type, 10, 1);
changed_ |= telegram->read_bitvalue(hc->mode, 10, 0); // bit 1, mode (auto=1 or manual=0)
// if manual, take the current setpoint temp at pos 6
// if auto, take the next setpoint temp at pos 7
@@ -1012,9 +1020,9 @@ void Thermostat::process_RC300Monitor(std::shared_ptr<const Telegram> telegram)
// pos 3 actual setpoint (optimized), i.e. changes with temporary change, summer/holiday-modes
// pos 6 actual setpoint according to programmed changes eco/comfort
// pos 7 next setpoint in the future, time to next setpoint in pos 8/9
telegram->read_value(hc->setpoint_roomTemp, 3, 1); // is * 2, force as single byte
telegram->read_bitvalue(hc->summer_mode, 2, 4);
telegram->read_value(hc->targetflowtemp, 4);
changed_ |= telegram->read_value(hc->setpoint_roomTemp, 3, 1); // is * 2, force as single byte
changed_ |= telegram->read_bitvalue(hc->summer_mode, 2, 4);
changed_ |= telegram->read_value(hc->targetflowtemp, 4);
}
// type 0x02B9 EMS+ for reading from RC300/RC310 thermostat
@@ -1026,21 +1034,21 @@ void Thermostat::process_RC300Set(std::shared_ptr<const Telegram> telegram) {
// comfort is position 2
// I think auto is position 8?
// actual setpoint taken from RC300Monitor (Michael 12.06.2020)
// telegram->read_value(hc->setpoint_roomTemp, 8, 1); // single byte conversion, value is * 2 - auto?
// telegram->read_value(hc->setpoint_roomTemp, 10, 1); // single byte conversion, value is * 2 - manual
// changed_ |= telegram->read_value(hc->setpoint_roomTemp, 8, 1); // single byte conversion, value is * 2 - auto?
// changed_ |= telegram->read_value(hc->setpoint_roomTemp, 10, 1); // single byte conversion, value is * 2 - manual
// check why mode is both in the Monitor and Set for the RC300. It'll be read twice!
// telegram->read_value(hc->mode, 0); // Auto = xFF, Manual = x00 eg. 10 00 FF 08 01 B9 FF
// changed_ |= telegram->read_value(hc->mode, 0); // Auto = xFF, Manual = x00 eg. 10 00 FF 08 01 B9 FF
telegram->read_value(hc->daytemp, 2); // is * 2
telegram->read_value(hc->nighttemp, 4); // is * 2
changed_ |= telegram->read_value(hc->daytemp, 2); // is * 2
changed_ |= telegram->read_value(hc->nighttemp, 4); // is * 2
}
// types 0x31D and 0x31E
void Thermostat::process_RC300WWmode(std::shared_ptr<const Telegram> telegram) {
// 0x31D for WW system 1, 0x31E for WW system 2
wwSystem_ = telegram->type_id - 0x31D + 1;
telegram->read_value(wwExtra_, 0); // 0=no, 1=yes
changed_ |= telegram->read_value(wwExtra_, 0); // 0=no, 1=yes
// pos 1 = holiday mode
// pos 2 = current status of DHW setpoint
// pos 3 = current status of DHW circulation pump
@@ -1050,15 +1058,15 @@ void Thermostat::process_RC300WWmode(std::shared_ptr<const Telegram> telegram) {
void Thermostat::process_RC30Monitor(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->setpoint_roomTemp, 1, 1); // is * 2, force as single byte
telegram->read_value(hc->curr_roomTemp, 2);
changed_ |= telegram->read_value(hc->setpoint_roomTemp, 1, 1); // is * 2, force as single byte
changed_ |= telegram->read_value(hc->curr_roomTemp, 2);
}
// type 0xA7 - for reading the mode from the RC30 thermostat (0x10)
void Thermostat::process_RC30Set(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->mode, 23);
changed_ |= telegram->read_value(hc->mode, 23);
}
// type 0x3E (HC1), 0x48 (HC2), 0x52 (HC3), 0x5C (HC4) - data from the RC35 thermostat (0x10) - 16 bytes
@@ -1072,14 +1080,14 @@ void Thermostat::process_RC35Monitor(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->setpoint_roomTemp, 2, 1); // is * 2, force to single byte, is 0 in summermode
telegram->read_value(hc->curr_roomTemp, 3); // is * 10 - or 0x7D00 if thermostat is mounted on boiler
changed_ |= telegram->read_value(hc->setpoint_roomTemp, 2, 1); // is * 2, force to single byte, is 0 in summermode
changed_ |= telegram->read_value(hc->curr_roomTemp, 3); // is * 10 - or 0x7D00 if thermostat is mounted on boiler
telegram->read_bitvalue(hc->mode_type, 1, 1);
telegram->read_bitvalue(hc->summer_mode, 1, 0);
telegram->read_bitvalue(hc->holiday_mode, 0, 5);
changed_ |= telegram->read_bitvalue(hc->mode_type, 1, 1);
changed_ |= telegram->read_bitvalue(hc->summer_mode, 1, 0);
changed_ |= telegram->read_bitvalue(hc->holiday_mode, 0, 5);
telegram->read_value(hc->targetflowtemp, 14);
changed_ |= telegram->read_value(hc->targetflowtemp, 14);
}
// type 0x3D (HC1), 0x47 (HC2), 0x51 (HC3), 0x5B (HC4) - Working Mode Heating - for reading the mode from the RC35 thermostat (0x10)
@@ -1091,16 +1099,16 @@ void Thermostat::process_RC35Set(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
telegram->read_value(hc->mode, 7); // night, day, auto
telegram->read_value(hc->daytemp, 2); // is * 2
telegram->read_value(hc->nighttemp, 1); // is * 2
telegram->read_value(hc->holidaytemp, 3); // is * 2
telegram->read_value(hc->heatingtype, 0); // 0- off, 1-radiator, 2-convector, 3-floor
changed_ |= telegram->read_value(hc->mode, 7); // night, day, auto
changed_ |= telegram->read_value(hc->daytemp, 2); // is * 2
changed_ |= telegram->read_value(hc->nighttemp, 1); // is * 2
changed_ |= telegram->read_value(hc->holidaytemp, 3); // is * 2
changed_ |= telegram->read_value(hc->heatingtype, 0); // 0- off, 1-radiator, 2-convector, 3-floor
telegram->read_value(hc->summertemp, 22); // is * 1
telegram->read_value(hc->nofrosttemp, 23); // is * 1
telegram->read_value(hc->designtemp, 17); // is * 1
telegram->read_value(hc->offsettemp, 6); // is * 2
changed_ |= telegram->read_value(hc->summertemp, 22); // is * 1
changed_ |= telegram->read_value(hc->nofrosttemp, 23); // is * 1
changed_ |= telegram->read_value(hc->designtemp, 17); // is * 1
changed_ |= telegram->read_value(hc->offsettemp, 6); // is * 2
}
// process_RCTime - type 0x06 - date and time from a thermostat - 14 bytes long
@@ -1118,6 +1126,7 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
if (datetime_.empty()) {
datetime_.resize(25, '\0');
}
auto timeold = datetime_;
// render time to HH:MM:SS DD/MM/YYYY
// had to create separate buffers because of how printf works
char buf1[6];
@@ -1136,6 +1145,9 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
Helpers::smallitoa(buf5, telegram->message_data[1]), // month
Helpers::itoa(buf6, telegram->message_data[0] + 2000, 10) // year
);
if (timeold != datetime_) {
changed_ = true;
}
}
// add console commands
@@ -1143,15 +1155,9 @@ void Thermostat::console_commands(Shell & shell, unsigned int context) {
EMSESPShell::commands->add_command(ShellContext::THERMOSTAT,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(master)},
flash_string_vector{F_(deviceid_optional)},
flash_string_vector{F_(deviceid_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
uint8_t value;
if (arguments.empty()) {
value = EMSESP_DEFAULT_MASTER_THERMOSTAT;
} else {
value = Helpers::hextoint(arguments.front().c_str());
}
uint8_t value = Helpers::hextoint(arguments.front().c_str());
EMSESP::emsespSettingsService.update(
[&](EMSESPSettings & settings) {
settings.master_thermostat = value;
@@ -1203,7 +1209,7 @@ void Thermostat::set_minexttemp(const char * value, const int8_t id) {
return;
}
LOG_INFO(F("Setting min external temperature to %d"), mt);
write_command(EMS_TYPE_IBASettings, 5, mt);
write_command(EMS_TYPE_IBASettings, 5, mt, EMS_TYPE_IBASettings);
}
// 0xA5 - Clock offset
@@ -1213,7 +1219,7 @@ void Thermostat::set_clockoffset(const char * value, const int8_t id) {
return;
}
LOG_INFO(F("Setting clock offset to %d"), co);
write_command(EMS_TYPE_IBASettings, 12, co);
write_command(EMS_TYPE_IBASettings, 12, co, EMS_TYPE_IBASettings);
}
// 0xA5 - Calibrate internal temperature
@@ -1224,7 +1230,7 @@ void Thermostat::set_calinttemp(const char * value, const int8_t id) {
}
// does this value need to be multiple by 10?
LOG_INFO(F("Calibrating internal temperature to %d.%d"), ct / 10, ct < 0 ? -ct % 10 : ct % 10);
write_command(EMS_TYPE_IBASettings, 2, ct);
write_command(EMS_TYPE_IBASettings, 2, ct, EMS_TYPE_IBASettings);
}
// 0xA5 - Set the display settings
@@ -1234,7 +1240,7 @@ void Thermostat::set_display(const char * value, const int8_t id) {
return;
}
LOG_INFO(F("Setting display to %d"), ds);
write_command(EMS_TYPE_IBASettings, 0, ds);
write_command(EMS_TYPE_IBASettings, 0, ds, EMS_TYPE_IBASettings);
}
void Thermostat::set_remotetemp(const char * value, const int8_t id) {
@@ -1271,7 +1277,7 @@ void Thermostat::set_building(const char * value, const int8_t id) {
}
LOG_INFO(F("Setting building to %d"), bg);
write_command(EMS_TYPE_wwSettings, 6, bg);
write_command(EMS_TYPE_IBASettings, 6, bg, EMS_TYPE_IBASettings);
}
// 0xA5 Set the language settings
@@ -1281,7 +1287,7 @@ void Thermostat::set_language(const char * value, const int8_t id) {
return;
}
LOG_INFO(F("Setting language to %d"), lg);
write_command(EMS_TYPE_wwSettings, 1, lg);
write_command(EMS_TYPE_IBASettings, 1, lg, EMS_TYPE_IBASettings);
}
// Set the control-mode for hc 0-off, 1-RC20, 2-RC3x
@@ -1326,7 +1332,7 @@ void Thermostat::set_wwmode(const char * value, const int8_t id) {
if (set != 0xFF) {
LOG_INFO(F("Setting thermostat warm water mode to %s"), v.c_str());
write_command(EMS_TYPE_wwSettings, 2, set);
write_command(EMS_TYPE_wwSettings, 2, set, EMS_TYPE_wwSettings);
} else {
LOG_WARNING(F("Set thermostat warm water mode: Invalid mode: %s"), v.c_str());
}
@@ -1430,7 +1436,7 @@ void Thermostat::set_datetime(const char * value, const int8_t id) {
data[7] = (dt[22] - '0') + 2; // DST and flag
}
LOG_INFO(F("Setting date and time"));
write_command(EMS_TYPE_time, 0, data, 8, 0);
write_command(EMS_TYPE_time, 0, data, 8, EMS_TYPE_time);
}
// sets the thermostat working mode, where mode is a string
@@ -1503,7 +1509,7 @@ void Thermostat::set_mode_n(const uint8_t mode, const uint8_t hc_num) {
break;
}
switch (this->flags() & 0x0F) {
switch (this->model()) {
case EMSdevice::EMS_DEVICE_FLAG_RC20:
offset = EMS_OFFSET_RC20Set_mode;
validate_typeid = set_typeids[hc_p];
@@ -1557,7 +1563,6 @@ void Thermostat::set_mode_n(const uint8_t mode, const uint8_t hc_num) {
// add the write command to the Tx queue
// post validate is the corresponding monitor or set type IDs as they can differ per model
// write_command(set_typeids[hc->hc_num() - 1], offset, set_mode_value, validate_typeid);
write_command(set_typeids[hc->hc_num() - 1], offset, set_mode_value, validate_typeid);
}
@@ -1600,7 +1605,7 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co
return;
}
uint8_t model = this->flags() & 0x0F;
uint8_t model = this->model();
int8_t offset = -1; // we use -1 to check if there is a value
uint8_t factor = 2; // some temperatures only use 1
uint16_t validate_typeid = monitor_typeids[hc->hc_num() - 1];
@@ -1616,12 +1621,18 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co
} else if ((model == EMS_DEVICE_FLAG_RC300) || (model == EMS_DEVICE_FLAG_RC100)) {
validate_typeid = set_typeids[hc->hc_num() - 1];
if (mode == HeatingCircuit::Mode::AUTO) {
offset = 0x08; // auto offset
} else if (mode == HeatingCircuit::Mode::MANUAL) {
switch (mode) {
case HeatingCircuit::Mode::MANUAL:
offset = 0x0A; // manual offset
} else if (mode == HeatingCircuit::Mode::COMFORT) {
break;
case HeatingCircuit::Mode::COMFORT:
offset = 0x02; // comfort offset
break;
default:
case HeatingCircuit::Mode::AUTO:
offset = 0x08; // auto offset
validate_typeid = monitor_typeids[hc->hc_num() - 1]; // get setpoint roomtemp back
break;
}
} else if (model == EMS_DEVICE_FLAG_RC20_2) {
@@ -1632,6 +1643,11 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co
case HeatingCircuit::Mode::DAY: // change the day temp
offset = EMS_OFFSET_RC20_2_Set_temp_day;
break;
default:
case HeatingCircuit::Mode::AUTO: // automatic selection, if no type is defined, we use the standard code
uint8_t mode_type = hc->get_mode_type(this->flags());
offset = (mode_type == HeatingCircuit::Mode::NIGHT) ? EMS_OFFSET_RC20_2_Set_temp_night : EMS_OFFSET_RC20_2_Set_temp_day;
break;
}
} else if ((model == EMS_DEVICE_FLAG_RC35) || (model == EMS_DEVICE_FLAG_RC30_1)) {
@@ -1662,7 +1678,8 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co
factor = 1;
break;
default:
case HeatingCircuit::Mode::AUTO: // automatic selection, if no type is defined, we use the standard code
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
if (model == EMS_DEVICE_FLAG_RC35) {
uint8_t mode_ = hc->get_mode(this->flags());
if (mode_ == HeatingCircuit::Mode::NIGHT) {
@@ -1699,8 +1716,13 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co
default:
case HeatingCircuit::Mode::AUTO: // automatic selection, if no type is defined, we use the standard code
uint8_t mode_type = hc->get_mode_type(this->flags());
offset = (mode_type == HeatingCircuit::Mode::NIGHT || mode_type == HeatingCircuit::Mode::ECO) ? EMS_OFFSET_JunkersSetMessage_night_temp
: EMS_OFFSET_JunkersSetMessage_day_temp;
if (mode_type == HeatingCircuit::Mode::NIGHT || mode_type == HeatingCircuit::Mode::ECO) {
offset = EMS_OFFSET_JunkersSetMessage_night_temp;
} else if (mode_type == HeatingCircuit::Mode::DAY || mode_type == HeatingCircuit::Mode::HEAT) {
offset = EMS_OFFSET_JunkersSetMessage_day_temp;
} else {
offset = EMS_OFFSET_JunkersSetMessage_no_frost_temp;
}
break;
}
@@ -1714,10 +1736,20 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co
case HeatingCircuit::Mode::NIGHT:
offset = EMS_OFFSET_JunkersSetMessage2_eco_temp;
break;
default:
case HeatingCircuit::Mode::HEAT:
case HeatingCircuit::Mode::DAY:
offset = EMS_OFFSET_JunkersSetMessage3_heat;
offset = EMS_OFFSET_JunkersSetMessage2_heat_temp;
break;
default:
case HeatingCircuit::Mode::AUTO: // automatic selection, if no type is defined, we use the standard code
uint8_t mode_type = hc->get_mode_type(this->flags());
if (mode_type == HeatingCircuit::Mode::NIGHT || mode_type == HeatingCircuit::Mode::ECO) {
offset = EMS_OFFSET_JunkersSetMessage2_eco_temp;
} else if (mode_type == HeatingCircuit::Mode::DAY || mode_type == HeatingCircuit::Mode::HEAT) {
offset = EMS_OFFSET_JunkersSetMessage2_heat_temp;
} else {
offset = EMS_OFFSET_JunkersSetMessage2_no_frost_temp;
}
break;
}
}
@@ -1809,7 +1841,7 @@ void Thermostat::add_commands() {
register_mqtt_cmd(F("temp"), [&](const char * value, const int8_t id) { set_temp(value, id); });
register_mqtt_cmd(F("mode"), [&](const char * value, const int8_t id) { set_mode(value, id); });
uint8_t model = this->flags() & 0x0F;
uint8_t model = this->model();
switch (model) {
case EMS_DEVICE_FLAG_RC20_2:
register_mqtt_cmd(F("nighttemp"), [&](const char * value, const int8_t id) { set_nighttemp(value, id); });

View File

@@ -40,10 +40,9 @@ class Thermostat : public EMSdevice {
Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
class HeatingCircuit {
public:
HeatingCircuit(const uint8_t hc_num, const uint16_t monitor_typeid, const uint16_t set_typeid)
HeatingCircuit(const uint8_t hc_num)
: hc_num_(hc_num)
, monitor_typeid_(monitor_typeid)
, set_typeid_(set_typeid) {
, ha_registered_(false) {
}
~HeatingCircuit() = default;
@@ -60,42 +59,46 @@ class Thermostat : public EMSdevice {
uint8_t targetflowtemp = EMS_VALUE_UINT_NOTSET;
uint8_t summertemp = EMS_VALUE_UINT_NOTSET;
uint8_t nofrosttemp = EMS_VALUE_UINT_NOTSET;
uint8_t designtemp = EMS_VALUE_UINT_NOTSET; // heatingcurve design temp at MinExtTemp
int8_t offsettemp = EMS_VALUE_INT_NOTSET; // heatingcurve offest temp at roomtemp signed!
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 hc_num() const {
return hc_num_; // 1..10
return hc_num_;
}
bool ha_registered() const {
return ha_registered_;
}
void ha_registered(bool b) {
ha_registered_ = b;
}
// determines if the heating circuit is actually present and has data
bool is_active() {
return Helpers::hasValue(setpoint_roomTemp);
}
uint8_t get_mode(uint8_t flags) const;
uint8_t get_mode_type(uint8_t flags) const;
uint16_t monitor_typeid() const {
return monitor_typeid_;
}
uint16_t set_typeid() const {
return set_typeid_;
}
enum Mode : uint8_t { UNKNOWN, OFF, MANUAL, AUTO, DAY, NIGHT, HEAT, NOFROST, ECO, HOLIDAY, COMFORT, OFFSET, DESIGN, SUMMER };
// for sorting
// for sorting based on hc number
friend inline bool operator<(const std::shared_ptr<HeatingCircuit> & lhs, const std::shared_ptr<HeatingCircuit> & rhs) {
return (lhs->hc_num_ < rhs->hc_num_);
}
private:
uint8_t hc_num_; // 1..10
uint16_t monitor_typeid_;
uint16_t set_typeid_;
uint8_t hc_num_; // heating circuit number 1..10
bool ha_registered_; // whether it has been registered for HA MQTT Discovery
};
static std::string mode_tostring(uint8_t mode);
virtual void show_values(uuid::console::Shell & shell);
virtual void publish_values();
virtual void device_info(JsonArray & root);
virtual void device_info_web(JsonArray & root);
virtual bool updated_values();
virtual void add_context_menu();
@@ -105,6 +108,11 @@ class Thermostat : public EMSdevice {
void console_commands(Shell & shell, unsigned int context);
void add_commands();
// specific thermostat characteristics, stripping the option bits at pos 6 and 7
inline uint8_t model() const {
return (this->flags() & 0x0F);
}
// each thermostat has a list of heating controller type IDs for reading and writing
std::vector<uint16_t> monitor_typeids;
std::vector<uint16_t> set_typeids;
@@ -113,10 +121,11 @@ class Thermostat : public EMSdevice {
std::string datetime_; // date and time stamp
uint8_t mqtt_format_; // single, nested or ha
bool changed_ = false;
// Installation parameters
uint8_t ibaMainDisplay_ =
EMS_VALUE_UINT_NOTSET; // display on Thermostat: 0 int. temp, 1 int. setpoint, 2 ext. temp., 3 burner temp., 4 ww temp, 5 functioning mode, 6 time, 7 data, 9 smoke temp
EMS_VALUE_UINT_NOTSET; // display on Thermostat: 0 int temp, 1 int setpoint, 2 ext temp, 3 burner temp, 4 ww temp, 5 functioning mode, 6 time, 7 data, 9 smoke temp
uint8_t ibaLanguage_ = EMS_VALUE_UINT_NOTSET; // language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian
int8_t ibaCalIntTemperature_ = EMS_VALUE_INT_NOTSET; // offset int. temperature sensor, by * 0.1 Kelvin (-5.0 to 5.0K)
int8_t ibaMinExtTemperature_ = EMS_VALUE_INT_NOTSET; // min ext temp for heating curve, in deg., 0xF6=-10, 0x0 = 0, 0xFF=-1
@@ -198,10 +207,9 @@ class Thermostat : public EMSdevice {
static constexpr uint8_t EMS_OFFSET_JunkersSetMessage2_set_mode = 4; // EMS offset to set mode on thermostat
static constexpr uint8_t EMS_OFFSET_JunkersSetMessage2_no_frost_temp = 5;
static constexpr uint8_t EMS_OFFSET_JunkersSetMessage2_eco_temp = 6;
static constexpr uint8_t EMS_OFFSET_JunkersSetMessage3_heat = 7;
static constexpr uint8_t EMS_OFFSET_JunkersSetMessage2_heat_temp = 7;
#define AUTO_HEATING_CIRCUIT 0
#define DEFAULT_HEATING_CIRCUIT 1
static constexpr uint8_t AUTO_HEATING_CIRCUIT = 0;
// Installation settings
static constexpr uint8_t EMS_TYPE_IBASettings = 0xA5; // installation settings
@@ -276,7 +284,7 @@ class Thermostat : public EMSdevice {
void set_display(const char * value, const int8_t id);
void set_building(const char * value, const int8_t id);
void set_language(const char * value, const int8_t id);
};
}; // namespace emsesp
} // namespace emsesp

View File

@@ -46,7 +46,7 @@ std::string EMSdevice::brand_to_string() const {
break;
case EMSdevice::Brand::NO_BRAND:
default:
return read_flash_string(F(""));
return read_flash_string(F("---"));
break;
}

View File

@@ -140,7 +140,7 @@ class EMSdevice {
virtual void publish_values() = 0;
virtual bool updated_values() = 0;
virtual void add_context_menu() = 0;
virtual void device_info(JsonArray & root) = 0;
virtual void device_info_web(JsonArray & root) = 0;
std::string telegram_type_name(std::shared_ptr<const Telegram> telegram);

View File

@@ -58,6 +58,7 @@ uint8_t EMSESP::actual_master_thermostat_ = EMSESP_DEFAULT_MASTER_THERMOSTAT; /
uint16_t EMSESP::watch_id_ = WATCH_ID_NONE; // for when log is TRACE. 0 means no trace set
uint8_t EMSESP::watch_ = 0; // trace off
uint16_t EMSESP::read_id_ = WATCH_ID_NONE;
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::unique_id_count_ = 0;
@@ -130,12 +131,7 @@ uint8_t EMSESP::actual_master_thermostat() {
// to watch both type IDs and device IDs
void EMSESP::watch_id(uint16_t watch_id) {
// if it's a device ID, which is a single byte, remove the MSB so to support both Buderus and HT3 protocols
if (watch_id <= 0xFF) {
watch_id_ = (watch_id & 0x7F);
} else {
watch_id_ = watch_id;
}
watch_id_ = watch_id;
}
// change the tx_mode
@@ -286,27 +282,63 @@ void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
char valuestr[8] = {0}; // for formatting temp
shell.printfln(F("Dallas temperature sensors:"));
for (const auto & device : sensor_devices()) {
shell.printfln(F(" ID: %s, Temperature: %s°C"), device.to_string().c_str(), Helpers::render_value(valuestr, device.temperature_c, 2));
shell.printfln(F(" ID: %s, Temperature: %s°C"), device.to_string().c_str(), Helpers::render_value(valuestr, device.temperature_c, 1));
}
shell.println();
}
// publish all values from each EMS device to MQTT
// plus the heartbeat and sensor if activated
void EMSESP::publish_all_values() {
void EMSESP::publish_device_values(uint8_t device_type) {
if (Mqtt::connected()) {
// Dallas sensors first
sensors_.publish_values();
// all the connected EMS devices we known about
for (const auto & emsdevice : emsdevices) {
if (emsdevice) {
if (emsdevice && (emsdevice->device_type() == device_type)) {
emsdevice->publish_values();
}
}
}
}
void EMSESP::publish_other_values() {
if (Mqtt::connected()) {
for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() != EMSdevice::DeviceType::BOILER) && (emsdevice->device_type() != EMSdevice::DeviceType::THERMOSTAT)
&& (emsdevice->device_type() != EMSdevice::DeviceType::SOLAR) && (emsdevice->device_type() != EMSdevice::DeviceType::MIXING)) {
emsdevice->publish_values();
}
}
}
}
void EMSESP::publish_sensor_values(const bool force) {
if (Mqtt::connected()) {
if (sensors_.updated_values() || force) {
sensors_.publish_values();
}
}
}
// MQTT publish a telegram as raw data
void EMSESP::publish_response(std::shared_ptr<const Telegram> telegram) {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
char buffer[100];
doc["src"] = Helpers::hextoa(buffer, telegram->src);
doc["dest"] = Helpers::hextoa(buffer, telegram->dest);
doc["type"] = Helpers::hextoa(buffer, telegram->type_id);
doc["offset"] = Helpers::hextoa(buffer, telegram->offset);
strcpy(buffer, Helpers::data_to_hex(telegram->message_data, telegram->message_length).c_str());
doc["data"] = buffer;
if (telegram->message_length <= 4) {
uint32_t value = 0;
for (uint8_t i = 0; i < telegram->message_length; i++) {
value = (value << 8) + telegram->message_data[i];
}
doc["value"] = value;
}
Mqtt::publish(F("response"), doc);
}
// search for recognized device_ids : Me, All, otherwise print hex value
std::string EMSESP::device_tostring(const uint8_t device_id) {
if ((device_id & 0x7F) == rxservice_.ems_bus_id()) {
@@ -475,9 +507,11 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
// if watching...
if (telegram->type_id == read_id_) {
LOG_NOTICE(pretty_telegram(telegram).c_str());
publish_response(telegram);
read_id_ = WATCH_ID_NONE;
} else if (watch() == WATCH_ON) {
if ((watch_id_ == WATCH_ID_NONE) || (telegram->src == watch_id_) || (telegram->dest == watch_id_) || (telegram->type_id == watch_id_)) {
if ((watch_id_ == WATCH_ID_NONE) || (telegram->type_id == watch_id_)
|| ((watch_id_ < 0x80) && ((telegram->src == watch_id_) || (telegram->dest == watch_id_)))) {
LOG_NOTICE(pretty_telegram(telegram).c_str());
}
}
@@ -507,7 +541,10 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
found = emsdevice->handle_telegram(telegram);
// check to see if we need to follow up after the telegram has been processed
if (found) {
if (emsdevice->updated_values()) {
if ((mqtt_.get_publish_onchange(emsdevice->device_type()) && emsdevice->updated_values()) || telegram->type_id == publish_id_) {
if (telegram->type_id == publish_id_) {
publish_id_ = 0;
}
emsdevice->publish_values(); // publish to MQTT if we explicitly have too
}
}
@@ -524,13 +561,14 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
}
// calls the device handler's function to populate a json doc with device info
void EMSESP::device_info(const uint8_t unique_id, JsonObject & root) {
// to be used in the Web UI
void EMSESP::device_info_web(const uint8_t unique_id, JsonObject & root) {
for (const auto & emsdevice : emsdevices) {
if (emsdevice) {
if (emsdevice->unique_id() == unique_id) {
root["deviceName"] = emsdevice->to_string_short(); // can;t use c_str() because of scope
JsonArray data = root.createNestedArray("deviceData");
emsdevice->device_info(data);
emsdevice->device_info_web(data);
return;
}
}
@@ -709,7 +747,6 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
if (tx_state != Telegram::Operation::NONE) {
bool tx_successful = false;
EMSbus::tx_state(Telegram::Operation::NONE); // reset Tx wait state
// txservice_.print_last_tx();
// if we're waiting on a Write operation, we want a single byte 1 or 4
if ((tx_state == Telegram::Operation::TX_WRITE) && (length == 1)) {
@@ -717,7 +754,7 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
LOG_DEBUG(F("Last Tx write successful"));
txservice_.increment_telegram_write_count(); // last tx/write was confirmed ok
txservice_.send_poll(); // close the bus
txservice_.post_send_query(); // follow up with any post-read
publish_id_ = txservice_.post_send_query(); // follow up with any post-read if set
txservice_.reset_retry_count();
tx_successful = true;
} else if (first_value == TxService::TX_WRITE_FAIL) {
@@ -771,11 +808,9 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
#ifdef EMSESP_DEBUG
LOG_TRACE(F("[DEBUG] Reply after %d ms: %s"), ::millis() - rx_time_, Helpers::data_to_hex(data, length).c_str());
#endif
// check if there is a message for the roomcontroller
Roomctrl::check((data[1] ^ 0x80 ^ rxservice_.ems_mask()), data);
// add to RxQueue, what ever it is.
// in add() the CRC will be checked
rxservice_.add(data, length);
Roomctrl::check((data[1] ^ 0x80 ^ rxservice_.ems_mask()), data); // check if there is a message for the roomcontroller
rxservice_.add(data, length); // add to RxQueue
}
}

View File

@@ -59,7 +59,9 @@ class EMSESP {
static void start();
static void loop();
static void publish_all_values();
static void publish_device_values(uint8_t device_type);
static void publish_other_values();
static void publish_sensor_values(const bool force = false);
#ifdef EMSESP_STANDALONE
static void run_test(uuid::console::Shell & shell, const std::string & command); // only for testing
@@ -84,7 +86,7 @@ class EMSESP {
static void send_raw_telegram(const char * data);
static bool device_exists(const uint8_t device_id);
static void device_info(const uint8_t unique_id, JsonObject & root);
static void device_info_web(const uint8_t unique_id, JsonObject & root);
static uint8_t count_devices(const uint8_t device_type);
@@ -174,6 +176,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 constexpr uint32_t EMS_FETCH_FREQUENCY = 60000; // check every minute
static uint32_t last_fetch_;
@@ -191,6 +194,7 @@ class EMSESP {
static uint16_t watch_id_;
static uint8_t watch_;
static uint16_t read_id_;
static uint16_t publish_id_;
static bool tap_water_active_;
static uint8_t unique_id_count_;

View File

@@ -329,27 +329,27 @@ bool Helpers::check_abs(const int32_t i) {
}
// for booleans, use isBool true (EMS_VALUE_BOOL)
bool Helpers::hasValue(const uint8_t v, const uint8_t isBool) {
bool Helpers::hasValue(const uint8_t &v, const uint8_t isBool) {
if (isBool == EMS_VALUE_BOOL) {
return (v != EMS_VALUE_BOOL_NOTSET);
}
return (v != EMS_VALUE_UINT_NOTSET);
}
bool Helpers::hasValue(const int8_t v) {
bool Helpers::hasValue(const int8_t &v) {
return (v != EMS_VALUE_INT_NOTSET);
}
// for short these are typically 0x8300, 0x7D00 and sometimes 0x8000
bool Helpers::hasValue(const int16_t v) {
bool Helpers::hasValue(const int16_t &v) {
return (abs(v) < EMS_VALUE_USHORT_NOTSET);
}
bool Helpers::hasValue(const uint16_t v) {
bool Helpers::hasValue(const uint16_t &v) {
return (v < EMS_VALUE_USHORT_NOTSET);
}
bool Helpers::hasValue(const uint32_t v) {
bool Helpers::hasValue(const uint32_t &v) {
return (v != EMS_VALUE_ULONG_NOTSET);
}

View File

@@ -50,11 +50,11 @@ class Helpers {
static char * ultostr(char * ptr, uint32_t value, const uint8_t base);
#endif
static bool hasValue(const uint8_t v, const uint8_t isBool = 0);
static bool hasValue(const int8_t v);
static bool hasValue(const int16_t v);
static bool hasValue(const uint16_t v);
static bool hasValue(const uint32_t v);
static bool hasValue(const uint8_t &v, const uint8_t isBool = 0);
static bool hasValue(const int8_t &v);
static bool hasValue(const int16_t &v);
static bool hasValue(const uint16_t &v);
static bool hasValue(const uint32_t &v);
static std::string toLower(std::string const & s);

View File

@@ -108,7 +108,6 @@ MAKE_PSTR(gpio_mandatory, "<gpio>")
MAKE_PSTR(data_optional, "[data]")
MAKE_PSTR(typeid_mandatory, "<type ID>")
MAKE_PSTR(deviceid_mandatory, "<device ID>")
MAKE_PSTR(deviceid_optional, "[device ID]")
MAKE_PSTR(invalid_log_level, "Invalid log level")
MAKE_PSTR(log_level_fmt, "Log level = %s")
MAKE_PSTR(log_level_optional, "[level]")

View File

@@ -27,8 +27,13 @@ AsyncMqttClient * Mqtt::mqttClient_;
// static parameters we make global
std::string Mqtt::hostname_;
uint8_t Mqtt::mqtt_qos_;
uint16_t Mqtt::publish_time_;
uint8_t Mqtt::bus_id_;
uint32_t Mqtt::publish_time_boiler_;
uint32_t Mqtt::publish_time_thermostat_;
uint32_t Mqtt::publish_time_solar_;
uint32_t Mqtt::publish_time_mixing_;
uint32_t Mqtt::publish_time_other_;
uint32_t Mqtt::publish_time_sensor_;
std::vector<Mqtt::MQTTSubFunction> Mqtt::mqtt_subfunctions_;
std::vector<Mqtt::MQTTCmdFunction> Mqtt::mqtt_cmdfunctions_;
@@ -111,11 +116,30 @@ void Mqtt::loop() {
uint32_t currentMillis = uuid::get_uptime();
// create publish messages for each of the EMS device values, adding to queue
if (publish_time_ && (currentMillis - last_publish_ > publish_time_)) {
last_publish_ = currentMillis;
EMSESP::publish_all_values();
if (publish_time_boiler_ && (currentMillis - last_publish_boiler_ > publish_time_boiler_)) {
last_publish_boiler_ = currentMillis;
EMSESP::publish_device_values(EMSdevice::DeviceType::BOILER);
}
if (publish_time_thermostat_ && (currentMillis - last_publish_thermostat_ > publish_time_thermostat_)) {
last_publish_thermostat_ = currentMillis;
EMSESP::publish_device_values(EMSdevice::DeviceType::THERMOSTAT);
}
if (publish_time_solar_ && (currentMillis - last_publish_solar_ > publish_time_solar_)) {
last_publish_solar_ = currentMillis;
EMSESP::publish_device_values(EMSdevice::DeviceType::SOLAR);
}
if (publish_time_mixing_ && (currentMillis - last_publish_mixing_ > publish_time_mixing_)) {
last_publish_mixing_ = currentMillis;
EMSESP::publish_device_values(EMSdevice::DeviceType::MIXING);
}
if (publish_time_other_ && (currentMillis - last_publish_other_ > publish_time_other_)) {
last_publish_other_ = currentMillis;
EMSESP::publish_other_values();
}
if (currentMillis - last_publish_sensor_ > publish_time_sensor_) {
last_publish_sensor_ = currentMillis;
EMSESP::publish_sensor_values(publish_time_sensor_ != 0);
}
// publish top item from MQTT queue to stop flooding
if ((uint32_t)(currentMillis - last_mqtt_poll_) > MQTT_PUBLISH_WAIT) {
last_mqtt_poll_ = currentMillis;
@@ -280,7 +304,7 @@ void Mqtt::on_message(const char * topic, const char * payload, size_t len) {
}
if (!cmd_known) {
LOG_ERROR(F("MQTT: no matching cmd or invalid data: %s"), command);
LOG_ERROR(F("MQTT: no matching cmd or invalid data: %s"), message);
}
return;
@@ -342,8 +366,13 @@ void Mqtt::start() {
// fetch MQTT settings
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & mqttSettings) {
publish_time_ = mqttSettings.publish_time * 1000; // convert to milliseconds
mqtt_qos_ = mqttSettings.mqtt_qos;
publish_time_boiler_ = mqttSettings.publish_time_boiler * 1000; // convert to milliseconds
publish_time_thermostat_ = mqttSettings.publish_time_thermostat * 1000;
publish_time_solar_ = mqttSettings.publish_time_solar * 1000;
publish_time_mixing_ = mqttSettings.publish_time_mixing * 1000;
publish_time_other_ = mqttSettings.publish_time_other * 1000;
publish_time_sensor_ = mqttSettings.publish_time_sensor * 1000;
mqtt_qos_ = mqttSettings.mqtt_qos;
});
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { bus_id_ = settings.ems_bus_id; });
@@ -390,8 +419,51 @@ void Mqtt::start() {
mqtt_subfunctions_.reserve(10);
}
void Mqtt::set_publish_time(uint16_t publish_time) {
publish_time_ = publish_time * 1000; // convert to milliseconds
void Mqtt::set_publish_time_boiler(uint16_t publish_time) {
publish_time_boiler_ = publish_time * 1000; // convert to milliseconds
}
void Mqtt::set_publish_time_thermostat(uint16_t publish_time) {
publish_time_thermostat_ = publish_time * 1000; // convert to milliseconds
}
void Mqtt::set_publish_time_solar(uint16_t publish_time) {
publish_time_solar_ = publish_time * 1000; // convert to milliseconds
}
void Mqtt::set_publish_time_mixing(uint16_t publish_time) {
publish_time_mixing_ = publish_time * 1000; // convert to milliseconds
}
void Mqtt::set_publish_time_other(uint16_t publish_time) {
publish_time_other_ = publish_time * 1000; // convert to milliseconds
}
void Mqtt::set_publish_time_sensor(uint16_t publish_time) {
publish_time_sensor_ = publish_time * 1000; // convert to milliseconds
}
bool Mqtt::get_publish_onchange(uint8_t device_type) {
if (device_type == EMSdevice::DeviceType::BOILER) {
if (!publish_time_boiler_) {
return true;
}
} else if (device_type == EMSdevice::DeviceType::THERMOSTAT) {
if (!publish_time_thermostat_) {
return true;
}
} else if (device_type == EMSdevice::DeviceType::SOLAR) {
if (!publish_time_solar_) {
return true;
}
} else if (device_type == EMSdevice::DeviceType::MIXING) {
if (!publish_time_mixing_) {
return true;
}
} else if (!publish_time_other_) {
return true;
}
return false;
}
void Mqtt::set_qos(uint8_t mqtt_qos) {
@@ -407,9 +479,9 @@ void Mqtt::on_connect() {
#ifndef EMSESP_STANDALONE
doc["ip"] = WiFi.localIP().toString();
#endif
publish("info", doc, false); // send with retain off
publish(F("info"), doc, false); // send with retain off
publish("status", "online", true); // say we're alive to the Last Will topic, with retain on
publish(F("status"), "online", true); // say we're alive to the Last Will topic, with retain on
reset_publish_fails(); // reset fail count to 0
@@ -466,6 +538,15 @@ void Mqtt::publish(const std::string & topic, const std::string & payload, bool
queue_publish_message(topic, payload, retain);
}
// MQTT Publish, using a specific retain flag, topic is a flash string
void Mqtt::publish(const __FlashStringHelper * topic, const std::string & payload, bool retain) {
queue_publish_message(uuid::read_flash_string(topic), payload, retain);
}
void Mqtt::publish(const __FlashStringHelper * topic, const JsonDocument & payload, bool retain) {
publish(uuid::read_flash_string(topic), payload, retain);
}
void Mqtt::publish(const std::string & topic, const JsonDocument & payload, bool retain) {
std::string payload_text;
serializeJson(payload, payload_text); // convert json to string
@@ -476,19 +557,15 @@ void Mqtt::publish(const std::string & topic, const JsonDocument & payload, bool
void Mqtt::publish(const std::string & topic, const bool value) {
queue_publish_message(topic, value ? "1" : "0", false);
}
void Mqtt::publish(const __FlashStringHelper * topic, const bool value) {
queue_publish_message(uuid::read_flash_string(topic), value ? "1" : "0", false);
}
// no payload
void Mqtt::publish(const std::string & topic) {
queue_publish_message(topic, "", false);
}
// publish all queued messages to MQTT
void Mqtt::process_all_queue() {
while (!mqtt_messages_.empty()) {
process_queue();
}
}
// take top from queue and perform the publish or subscribe action
// assumes there is an MQTT connection
void Mqtt::process_queue() {

View File

@@ -67,8 +67,14 @@ class Mqtt {
void loop();
void start();
void set_publish_time(uint16_t publish_time);
void set_publish_time_boiler(uint16_t publish_time);
void set_publish_time_thermostat(uint16_t publish_time);
void set_publish_time_solar(uint16_t publish_time);
void set_publish_time_mixing(uint16_t publish_time);
void set_publish_time_other(uint16_t publish_time);
void set_publish_time_sensor(uint16_t publish_time);
void set_qos(uint8_t mqtt_qos);
bool get_publish_onchange(uint8_t device_type);
enum Operation { PUBLISH, SUBSCRIBE };
@@ -82,7 +88,10 @@ class Mqtt {
static void publish(const std::string & topic, const std::string & payload, bool retain = false);
static void publish(const std::string & topic, const JsonDocument & payload, bool retain = false);
static void publish(const __FlashStringHelper * topic, const JsonDocument & payload, bool retain = false);
static void publish(const __FlashStringHelper * topic, const std::string & payload, bool retain = false);
static void publish(const std::string & topic, const bool value);
static void publish(const __FlashStringHelper * topi, const bool value);
static void publish(const std::string & topic);
static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type);
@@ -167,7 +176,6 @@ class Mqtt {
void on_publish(uint16_t packetId);
void on_message(const char * topic, const char * payload, size_t len);
void process_queue();
void process_all_queue();
static uint16_t mqtt_publish_fails_;
@@ -189,14 +197,25 @@ class Mqtt {
static std::vector<MQTTSubFunction> mqtt_subfunctions_; // list of mqtt subscribe callbacks for all devices
static std::vector<MQTTCmdFunction> mqtt_cmdfunctions_; // list of commands
uint32_t last_mqtt_poll_ = 0;
uint32_t last_publish_ = 0;
uint32_t last_mqtt_poll_ = 0;
uint32_t last_publish_boiler_ = 0;
uint32_t last_publish_thermostat_ = 0;
uint32_t last_publish_solar_ = 0;
uint32_t last_publish_mixing_ = 0;
uint32_t last_publish_other_ = 0;
uint32_t last_publish_sensor_ = 0;
// settings, copied over
static std::string hostname_;
static uint8_t mqtt_qos_;
static uint16_t publish_time_;
static uint32_t publish_time_;
static uint8_t bus_id_;
static uint32_t publish_time_boiler_;
static uint32_t publish_time_thermostat_;
static uint32_t publish_time_solar_;
static uint32_t publish_time_mixing_;
static uint32_t publish_time_other_;
static uint32_t publish_time_sensor_;
};
} // namespace emsesp

View File

@@ -49,7 +49,10 @@ void Sensors::reload() {
mqtt_format_ = settings.mqtt_format; // single, nested or ha
});
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { dallas_gpio_ = settings.dallas_gpio; });
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
dallas_gpio_ = settings.dallas_gpio;
parasite_ = settings.dallas_parasite;
});
if (mqtt_format_ == MQTT_format::HA) {
for (uint8_t i = 0; i < MAX_SENSORS; registered_ha_[i++] = false)
@@ -64,10 +67,10 @@ void Sensors::loop() {
if (state_ == State::IDLE) {
if (time_now - last_activity_ >= READ_INTERVAL_MS) {
// LOG_DEBUG(F("Read sensor temperature")); // uncomment for debug
if (bus_.reset()) {
if (bus_.reset() || parasite_) {
YIELD;
bus_.skip();
bus_.write(CMD_CONVERT_TEMP);
bus_.write(CMD_CONVERT_TEMP, parasite_ ? 1 : 0);
state_ = State::READING;
} else {
// no sensors found
@@ -94,8 +97,9 @@ void Sensors::loop() {
uint8_t addr[ADDR_LEN] = {0};
if (bus_.search(addr)) {
bus_.depower();
if (!parasite_) {
bus_.depower();
}
if (bus_.crc8(addr, ADDR_LEN - 1) == addr[ADDR_LEN - 1]) {
switch (addr[0]) {
case TYPE_DS18B20:
@@ -122,8 +126,19 @@ void Sensors::loop() {
LOG_ERROR(F("Invalid sensor %s"), Device(addr).to_string().c_str());
}
} else {
bus_.depower();
if (!parasite_) {
bus_.depower();
}
if ((found_.size() >= devices_.size()) || (retrycnt_ > 5)) {
if (found_.size() == devices_.size()) {
for (uint8_t i = 0; i < devices_.size(); i++) {
if (found_[i].temperature_c != devices_[i].temperature_c) {
changed_ = true;
}
}
} else {
changed_ = true;
}
devices_ = std::move(found_);
retrycnt_ = 0;
} else {
@@ -140,9 +155,12 @@ void Sensors::loop() {
bool Sensors::temperature_convert_complete() {
#ifndef EMSESP_STANDALONE
if (parasite_) {
return true; // don't care, use the minimum time in loop
}
return bus_.read_bit() == 1;
#else
return 1;
return true;
#endif
}
@@ -183,27 +201,27 @@ float Sensors::get_temperature_c(const uint8_t addr[]) {
int16_t raw_value = ((int16_t)scratchpad[SCRATCHPAD_TEMP_MSB] << 8) | scratchpad[SCRATCHPAD_TEMP_LSB];
// Adjust based on device resolution
int resolution = 9 + ((scratchpad[SCRATCHPAD_CONFIG] >> 5) & 0x3);
switch (resolution) {
case 9:
raw_value &= ~0x1;
break;
case 10:
raw_value &= ~0x3;
break;
case 11:
raw_value &= ~0x7;
break;
case 12:
break;
if (addr[0] == TYPE_DS18S20) {
raw_value = (raw_value << 3) + 12 - scratchpad[SCRATCHPAD_CNT_REM];
} else {
// Adjust based on device resolution
int resolution = 9 + ((scratchpad[SCRATCHPAD_CONFIG] >> 5) & 0x3);
switch (resolution) {
case 9:
raw_value &= ~0x7;
break;
case 10:
raw_value &= ~0x3;
break;
case 11:
raw_value &= ~0x1;
break;
case 12:
break;
}
}
uint32_t raw = (raw_value * 625) / 100; // round to 0.01
return (float)raw / 100;
uint32_t raw = (raw_value * 625 + 500) / 1000; // round to 0.1
return (float)raw / 10;
#else
return NAN;
#endif
@@ -237,6 +255,14 @@ std::string Sensors::Device::to_string() const {
return str;
}
// check to see if values have been updated
bool Sensors::updated_values() {
if (changed_) {
changed_ = false;
return true;
}
return false;
}
// send all dallas sensor values as a JSON package to MQTT
// assumes there are devices
@@ -254,7 +280,7 @@ void Sensors::publish_values() {
StaticJsonDocument<100> doc;
for (const auto & device : devices_) {
char s[7]; // sensorrange -55.00 to 125.00
doc["temp"] = Helpers::render_value(s, device.temperature_c, 2);
doc["temp"] = Helpers::render_value(s, device.temperature_c, 1);
char topic[60]; // sensors{1-n}
strlcpy(topic, "sensor_", 50); // create topic, e.g. home/ems-esp/sensor_28-EA41-9497-0E03-5F
strlcat(topic, device.to_string().c_str(), 60);
@@ -264,28 +290,22 @@ void Sensors::publish_values() {
return;
}
// const size_t capacity = num_devices * JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(num_devices);
DynamicJsonDocument doc(100 * num_devices);
uint8_t i = 1; // sensor count
for (const auto & device : devices_) {
char s[7];
if (mqtt_format_ == MQTT_format::CUSTOM) {
doc[device.to_string()] = Helpers::render_value(s, device.temperature_c, 2);
} else if (mqtt_format_ == MQTT_format::SINGLE) {
doc["id"] = device.to_string();
doc["temp"] = Helpers::render_value(s, device.temperature_c, 2);
std::string topic(100, '\0');
snprintf_P(&topic[0], 50, PSTR("sensor%d"), i);
Mqtt::publish(topic, doc);
// e.g. sensors = {28-EA41-9497-0E03-5F":23.30,"28-233D-9497-0C03-8B":24.0}
doc[device.to_string()] = Helpers::render_value(s, device.temperature_c, 1);
} else if ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::HA)) {
// e.g. {"sensor1":{"id":"28-EA41-9497-0E03-5F","temp":"23.30"},"sensor2":{"id":"28-233D-9497-0C03-8B","temp":"24.0"}}
// e.g. sensors = {"sensor1":{"id":"28-EA41-9497-0E03-5F","temp":"23.30"},"sensor2":{"id":"28-233D-9497-0C03-8B","temp":"24.0"}}
char sensorID[10]; // sensor{1-n}
strlcpy(sensorID, "sensor", 10);
strlcat(sensorID, Helpers::itoa(s, i), 10);
JsonObject dataSensor = doc.createNestedObject(sensorID);
dataSensor["id"] = device.to_string();
dataSensor["temp"] = Helpers::render_value(s, device.temperature_c, 2);
dataSensor["temp"] = Helpers::render_value(s, device.temperature_c, 1);
}
// special for HA
@@ -327,9 +347,9 @@ void Sensors::publish_values() {
}
if ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::CUSTOM)) {
Mqtt::publish("sensors", doc);
Mqtt::publish(F("sensors"), doc);
} else if (mqtt_format_ == MQTT_format::HA) {
Mqtt::publish("homeassistant/sensor/ems-esp/state", doc);
Mqtt::publish(F("homeassistant/sensor/ems-esp/state"), doc);
}
}
} // namespace emsesp

View File

@@ -60,6 +60,7 @@ class Sensors {
void loop();
void publish_values();
void reload();
bool updated_values();
const std::vector<Device> devices() const;
@@ -74,12 +75,13 @@ class Sensors {
static constexpr size_t SCRATCHPAD_TEMP_MSB = 1;
static constexpr size_t SCRATCHPAD_TEMP_LSB = 0;
static constexpr size_t SCRATCHPAD_CONFIG = 4;
static constexpr size_t SCRATCHPAD_CNT_REM = 6;
// dallas chips
static constexpr uint8_t TYPE_DS18B20 = 0x28;
static constexpr uint8_t TYPE_DS18S20 = 0x10;
static constexpr uint8_t TYPE_DS1822 = 0x22;
static constexpr uint8_t TYPE_DS1825 = 0x3B;
static constexpr uint8_t TYPE_DS1825 = 0x3B; // also DS1826
static constexpr uint32_t READ_INTERVAL_MS = 5000; // 5 seconds
static constexpr uint32_t CONVERSION_MS = 1000; // 1 seconds
@@ -109,6 +111,8 @@ class Sensors {
uint8_t mqtt_format_;
uint8_t retrycnt_ = 0;
uint8_t dallas_gpio_ = 0;
bool parasite_ = false;
bool changed_ = false;
};
} // namespace emsesp

View File

@@ -53,7 +53,7 @@ void Shower::loop() {
// first check to see if hot water has been on long enough to be recognized as a Shower/Bath
if (!shower_on_ && (time_now - timer_start_) > SHOWER_MIN_DURATION) {
shower_on_ = true;
Mqtt::publish("shower_active", (bool)true);
Mqtt::publish(F("shower_active"), (bool)true);
LOG_DEBUG(F("[Shower] hot water still running, starting shower timer"));
}
// check if the shower has been on too long
@@ -74,7 +74,7 @@ void Shower::loop() {
if ((timer_pause_ - timer_start_) > SHOWER_OFFSET_TIME) {
duration_ = (timer_pause_ - timer_start_ - SHOWER_OFFSET_TIME);
if (duration_ > SHOWER_MIN_DURATION) {
Mqtt::publish("shower_active", (bool)false);
Mqtt::publish(F("shower_active"), (bool)false);
LOG_DEBUG(F("[Shower] finished with duration %d"), duration_);
publish_values();
}
@@ -129,7 +129,7 @@ void Shower::publish_values() {
doc["duration"] = s;
}
Mqtt::publish("shower_data", doc);
Mqtt::publish(F("shower_data"), doc);
}
} // namespace emsesp

View File

@@ -35,6 +35,7 @@ int System::reset_counter_ = 0;
bool System::upload_status_ = false;
bool System::hide_led_ = false;
uint8_t System::led_gpio_ = 0;
uint16_t System::analog_ = 0;
// send on/off to a gpio pin
// value: true = HIGH, false = LOW
@@ -188,6 +189,7 @@ void System::loop() {
#endif
led_monitor(); // check status and report back using the LED
system_check(); // check system health
measure_analog();
// send out heartbeat
uint32_t currentMillis = uuid::get_uptime();
@@ -233,8 +235,34 @@ void System::send_heartbeat() {
doc["mqttpublishfails"] = Mqtt::publish_fails();
doc["txfails"] = EMSESP::txservice_.telegram_fail_count();
doc["rxfails"] = EMSESP::rxservice_.telegram_error_count();
doc["adc"] = analog_; //analogRead(A0);
Mqtt::publish("heartbeat", doc, false); // send to MQTT with retain off. This will add to MQTT queue.
Mqtt::publish(F("heartbeat"), doc, false); // send to MQTT with retain off. This will add to MQTT queue.
}
// measure and moving average adc
void System::measure_analog() {
static uint32_t measure_last_ = 0;
if (!measure_last_ || (uint32_t)(uuid::get_uptime() - measure_last_) >= SYSTEM_MEASURE_ANALOG_INTERVAL) {
measure_last_ = uuid::get_uptime();
#if defined(ESP8266)
uint16_t a = analogRead(A0);
#elif defined(ESP32)
uint16_t a = analogRead(36);
#else
uint16_t a = 0; // standalone
#endif
static uint32_t sum_ = 0;
if (!analog_) { // init first time
analog_ = a;
sum_ = a * 256;
} else { // simple moving average filter
sum_ = sum_ * 255 / 256 + a;
analog_ = sum_ / 256;
}
}
}
// sets rate of led flash
@@ -594,9 +622,23 @@ void System::console_commands(Shell & shell, unsigned int context) {
// upgrade from previous versions of EMS-ESP, based on SPIFFS on an ESP8266
// returns true if an upgrade was done
// the logic is bit abnormal (loading both filesystems and testing) but this was the only way I could get it to work reliably
bool System::check_upgrade() {
#if defined(ESP8266)
LittleFSConfig l_cfg;
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)
Serial.begin(115200);
Serial.println(F("FS is Littlefs"));
Serial.flush();
Serial.end();
#endif
return false;
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
@@ -604,120 +646,172 @@ bool System::check_upgrade() {
cfg.setAutoFormat(false); // prevent formatting when opening SPIFFS filesystem
SPIFFS.setConfig(cfg);
if (!SPIFFS.begin()) {
return false; // is not SPIFFS
#if defined(EMSESP_DEBUG)
Serial.begin(115200);
Serial.println(F("No old SPIFFS found!"));
Serial.flush();
Serial.end();
#endif
return false;
}
Serial.begin(115200);
// open the two files
File file1 = SPIFFS.open("/myesp.json", "r");
File file2 = SPIFFS.open("/customconfig.json", "r");
if (!file1 || !file2) {
Serial.println(F("Unable to read the config files"));
file1.close();
file2.close();
SPIFFS.end();
return false; // can't open files
}
// read the content of the files
DeserializationError error;
StaticJsonDocument<1024> doc1; // for myESP settings
StaticJsonDocument<1024> doc2; // for custom EMS-ESP settings
bool failed = false;
File file;
JsonObject network, general, mqtt, custom_settings;
StaticJsonDocument<1024> doc;
error = deserializeJson(doc1, file1);
if (error) {
Serial.printf("Error. Failed to deserialize json, doc1, error %s", error.c_str());
// open the system settings:
// {
// "command":"configfile",
// "network":{"ssid":"xxxx","password":"yyyy","wmode":1,"staticip":null,"gatewayip":null,"nmask":null,"dnsip":null},
// "general":{"password":"admin","serial":false,"hostname":"ems-esp","log_events":false,"log_ip":null,"version":"1.9.5"},
// "mqtt":{"enabled":false,"heartbeat":false,"ip":null,"user":null,"port":1883,"qos":0,"keepalive":60,"retain":false,"password":null,"base":null,"nestedjson":false},
// "ntp":{"server":"pool.ntp.org","interval":720,"enabled":false,"timezone":2}
// }
file = SPIFFS.open("/myesp.json", "r");
if (!file) {
Serial.println(F("Unable to read the system config file"));
failed = true;
} else {
DeserializationError error = deserializeJson(doc, file);
if (error) {
Serial.printf(PSTR("Error. Failed to deserialize system json, error %s\n"), error.c_str());
failed = true;
} else {
Serial.println(F("Migrating settings from EMS-ESP v1.9..."));
#if defined(EMSESP_DEBUG)
serializeJson(doc, Serial);
Serial.println();
#endif
network = doc["network"];
general = doc["general"];
mqtt = doc["mqtt"];
// start up LittleFS. If it doesn't exist it will format it
l_cfg.setAutoFormat(true);
LittleFS.setConfig(l_cfg);
LittleFS.begin();
EMSESP::esp8266React.begin();
EMSESP::emsespSettingsService.begin();
EMSESP::esp8266React.getWiFiSettingsService()->update(
[&](WiFiSettings & wifiSettings) {
wifiSettings.hostname = general["hostname"] | FACTORY_WIFI_HOSTNAME;
wifiSettings.ssid = network["ssid"] | FACTORY_WIFI_SSID;
wifiSettings.password = network["password"] | FACTORY_WIFI_PASSWORD;
wifiSettings.staticIPConfig = false;
JsonUtils::readIP(network, "staticip", wifiSettings.localIP);
JsonUtils::readIP(network, "dnsip", wifiSettings.dnsIP1);
JsonUtils::readIP(network, "gatewayip", wifiSettings.gatewayIP);
JsonUtils::readIP(network, "nmask", wifiSettings.subnetMask);
return StateUpdateResult::CHANGED;
},
"local");
EMSESP::esp8266React.getSecuritySettingsService()->update(
[&](SecuritySettings & securitySettings) {
securitySettings.jwtSecret = general["password"] | FACTORY_JWT_SECRET;
return StateUpdateResult::CHANGED;
},
"local");
EMSESP::esp8266React.getMqttSettingsService()->update(
[&](MqttSettings & mqttSettings) {
mqttSettings.host = mqtt["ip"] | FACTORY_MQTT_HOST;
mqttSettings.mqtt_format = (mqtt["nestedjson"] ? MQTT_format::NESTED : MQTT_format::SINGLE);
mqttSettings.mqtt_qos = mqtt["qos"] | 0;
mqttSettings.username = mqtt["user"] | "";
mqttSettings.password = mqtt["password"] | "";
mqttSettings.port = mqtt["port"] | FACTORY_MQTT_PORT;
mqttSettings.clientId = FACTORY_MQTT_CLIENT_ID;
mqttSettings.enabled = mqtt["enabled"];
mqttSettings.system_heartbeat = mqtt["heartbeat"];
mqttSettings.keepAlive = FACTORY_MQTT_KEEP_ALIVE;
mqttSettings.cleanSession = FACTORY_MQTT_CLEAN_SESSION;
mqttSettings.maxTopicLength = FACTORY_MQTT_MAX_TOPIC_LENGTH;
return StateUpdateResult::CHANGED;
},
"local");
}
}
error = deserializeJson(doc2, file2);
if (error) {
Serial.printf("Error. Failed to deserialize json, doc2, error %s", error.c_str());
failed = true;
file.close();
if (failed) {
#if defined(EMSESP_DEBUG)
Serial.println(F("Failed to read system config. Quitting."));
#endif
SPIFFS.end();
Serial.end();
return false;
}
file1.close();
file2.close();
// open the custom settings file next:
// {
// "command":"custom_configfile",
// "settings":{"led":true,"led_gpio":2,"dallas_gpio":14,"dallas_parasite":false,"listen_mode":false,"shower_timer":false,"shower_alert":false,"publish_time":0,"tx_mode":1,"bus_id":11,"master_thermostat":0,"known_devices":""}
// }
doc.clear();
failed = false;
file = SPIFFS.open("/customconfig.json", "r");
if (!file) {
Serial.println(F("Unable to read custom config file"));
failed = true;
} else {
DeserializationError error = deserializeJson(doc, file);
if (error) {
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::emsespSettingsService.update(
[&](EMSESPSettings & settings) {
settings.tx_mode = custom_settings["tx_mode"] | EMSESP_DEFAULT_TX_MODE;
settings.shower_alert = custom_settings["shower_alert"] | EMSESP_DEFAULT_SHOWER_ALERT;
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_host = EMSESP_DEFAULT_SYSLOG_HOST;
settings.syslog_level = EMSESP_DEFAULT_SYSLOG_LEVEL;
settings.syslog_mark_interval = EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL;
settings.dallas_gpio = custom_settings["dallas_gpio"] | EMSESP_DEFAULT_DALLAS_GPIO;
settings.dallas_parasite = custom_settings["dallas_parasite"] | EMSESP_DEFAULT_DALLAS_PARASITE;
settings.led_gpio = custom_settings["led_gpio"] | EMSESP_DEFAULT_LED_GPIO;
return StateUpdateResult::CHANGED;
},
"local");
}
}
file.close();
SPIFFS.end();
if (failed) {
return false; // parse error
#if defined(EMSESP_DEBUG)
Serial.println(F("Failed to read custom config. Quitting."));
#endif
Serial.end();
return false;
}
#pragma GCC diagnostic pop
LittleFS.begin();
EMSESP::esp8266React.begin(); // loads system settings (wifi, mqtt, etc)
EMSESP::emsespSettingsService.begin(); // load EMS-ESP specific settings
Serial.println(F("Migrating settings from EMS-ESP 1.9.x..."));
// get the json objects
JsonObject network = doc1["network"];
JsonObject general = doc1["general"];
JsonObject mqtt = doc1["mqtt"];
JsonObject custom_settings = doc2["settings"]; // from 2nd file
EMSESP::esp8266React.getWiFiSettingsService()->update(
[&](WiFiSettings & wifiSettings) {
wifiSettings.hostname = general["hostname"] | FACTORY_WIFI_HOSTNAME;
wifiSettings.ssid = network["ssid"] | FACTORY_WIFI_SSID;
wifiSettings.password = network["password"] | FACTORY_WIFI_PASSWORD;
wifiSettings.staticIPConfig = false;
JsonUtils::readIP(network, "staticip", wifiSettings.localIP);
JsonUtils::readIP(network, "dnsip", wifiSettings.dnsIP1);
JsonUtils::readIP(network, "gatewayip", wifiSettings.gatewayIP);
JsonUtils::readIP(network, "nmask", wifiSettings.subnetMask);
return StateUpdateResult::CHANGED;
},
"local");
EMSESP::esp8266React.getMqttSettingsService()->update(
[&](MqttSettings & mqttSettings) {
mqttSettings.host = mqtt["ip"] | FACTORY_MQTT_HOST;
mqttSettings.mqtt_format = (mqtt["nestedjson"] ? 2 : 1);
mqttSettings.mqtt_qos = mqtt["qos"] | 0;
mqttSettings.username = mqtt["user"] | "";
mqttSettings.password = mqtt["password"] | "";
mqttSettings.port = mqtt["port"] | FACTORY_MQTT_PORT;
mqttSettings.clientId = FACTORY_MQTT_CLIENT_ID;
mqttSettings.enabled = mqtt["enabled"];
mqttSettings.system_heartbeat = mqtt["heartbeat"];
mqttSettings.keepAlive = FACTORY_MQTT_KEEP_ALIVE;
mqttSettings.cleanSession = FACTORY_MQTT_CLEAN_SESSION;
mqttSettings.maxTopicLength = FACTORY_MQTT_MAX_TOPIC_LENGTH;
return StateUpdateResult::CHANGED;
},
"local");
EMSESP::esp8266React.getSecuritySettingsService()->update(
[&](SecuritySettings & securitySettings) {
securitySettings.jwtSecret = general["password"] | FACTORY_JWT_SECRET;
return StateUpdateResult::CHANGED;
},
"local");
EMSESP::emsespSettingsService.update(
[&](EMSESPSettings & settings) {
settings.tx_mode = custom_settings["tx_mode"] | EMSESP_DEFAULT_TX_MODE;
settings.shower_alert = custom_settings["shower_alert"] | EMSESP_DEFAULT_SHOWER_ALERT;
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_host = EMSESP_DEFAULT_SYSLOG_HOST;
settings.syslog_level = EMSESP_DEFAULT_SYSLOG_LEVEL;
settings.syslog_mark_interval = EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL;
return StateUpdateResult::CHANGED;
},
"local");
Serial.println(F("Restarting..."));
Serial.flush();
delay(1000);
Serial.end();
delay(1000);
restart();
return true;
#else
return false;

View File

@@ -68,29 +68,19 @@ class System {
static uuid::syslog::SyslogService syslog_;
#endif
static constexpr uint32_t SYSTEM_CHECK_FREQUENCY = 5000; // check every 5 seconds
static constexpr uint32_t LED_WARNING_BLINK = 1000; // pulse to show no connection, 1 sec
static constexpr uint32_t LED_WARNING_BLINK_FAST = 100; // flash quickly for boot up sequence
static constexpr uint32_t SYSTEM_HEARTBEAT_INTERVAL = 60000; // in milliseconds, how often the MQTT heartbeat is sent (1 min)
static constexpr uint32_t SYSTEM_CHECK_FREQUENCY = 5000; // check every 5 seconds
static constexpr uint32_t LED_WARNING_BLINK = 1000; // pulse to show no connection, 1 sec
static constexpr uint32_t LED_WARNING_BLINK_FAST = 100; // flash quickly for boot up sequence
static constexpr uint32_t SYSTEM_HEARTBEAT_INTERVAL = 60000; // in milliseconds, how often the MQTT heartbeat is sent (1 min)
static constexpr uint32_t SYSTEM_MEASURE_ANALOG_INTERVAL = 1100;
// internal LED
#ifndef EMSESP_NO_LED
#if defined(ESP8266)
// internal LED
static constexpr uint8_t LED_ON = LOW;
#elif defined(ESP32)
#ifdef WEMOS_D1_32
static constexpr uint8_t LED_ON = HIGH;
#else
static constexpr uint8_t LED_ON = LOW;
#endif
#endif
#else
static constexpr uint8_t LED_ON = 0;
#endif
void led_monitor();
void set_led_speed(uint32_t speed);
void system_check();
void measure_analog();
static void show_system(uuid::console::Shell & shell);
static void show_users(uuid::console::Shell & shell);
@@ -103,6 +93,7 @@ class System {
static int reset_counter_;
uint32_t last_heartbeat_ = 0;
static bool upload_status_; // true if we're in the middle of a OTA firmware upload
static uint16_t analog_;
// settings
bool system_heartbeat_;

View File

@@ -73,7 +73,7 @@ Telegram::Telegram(const uint8_t operation,
, offset(offset)
, message_length(message_length) {
// copy complete telegram data over, preventing buffer overflow
for (uint8_t i = 0; ((i < message_length) && (i != EMS_MAX_TELEGRAM_MESSAGE_LENGTH - 1)); i++) {
for (uint8_t i = 0; ((i < message_length) && (i < EMS_MAX_TELEGRAM_MESSAGE_LENGTH)); i++) {
message_data[i] = data[i];
}
}
@@ -95,7 +95,7 @@ std::string Telegram::to_string() const {
data[2] = this->type_id;
length = 5;
}
} else if (this->operation == Telegram::Operation::TX_WRITE) {
} else {
data[1] = this->dest;
if (this->type_id > 0xFF) {
data[2] = 0xFF;
@@ -109,10 +109,6 @@ std::string Telegram::to_string() const {
for (uint8_t i = 0; i < this->message_length; i++) {
data[length++] = this->message_data[i];
}
} else {
for (uint8_t i = 0; i < this->message_length; i++) {
data[length++] = this->message_data[i];
}
}
return Helpers::data_to_hex(data, length);
@@ -189,13 +185,14 @@ void RxService::add(uint8_t * data, uint8_t length) {
}
type_id = (data[4 + shift] << 8) + data[5 + shift] + 256;
message_data = data + 6 + shift;
message_length = length - 6 - shift;
message_length = length - 7 - shift;
}
// if we're watching and "raw" print out actual telegram as bytes to the console
if (EMSESP::watch() == EMSESP::Watch::WATCH_RAW) {
uint16_t trace_watch_id = EMSESP::watch_id();
if ((trace_watch_id == WATCH_ID_NONE) || (src == trace_watch_id) || (dest == trace_watch_id) || (type_id == trace_watch_id)) {
if ((trace_watch_id == WATCH_ID_NONE) || (type_id == trace_watch_id)
|| ((trace_watch_id < 0x80) && ((src == trace_watch_id) || (dest == trace_watch_id)))) {
LOG_NOTICE(F("Rx: %s"), Helpers::data_to_hex(data, length).c_str());
}
}
@@ -213,16 +210,16 @@ void RxService::add(uint8_t * data, uint8_t length) {
// if we receive a hc2.. telegram from 0x19.. match it to master_thermostat if master is 0x18
src = EMSESP::check_master_device(src, type_id, true);
// create the telegram
// create the telegram
auto telegram = std::make_shared<Telegram>(Telegram::Operation::RX, src, dest, type_id, offset, message_data, message_length);
// check if queue is full, if so remove top item to make space
if (rx_telegrams_.size() >= MAX_RX_TELEGRAMS) {
rx_telegrams_.pop_front();
increment_telegram_error_count();
}
rx_telegrams_.emplace_back(rx_telegram_id_++, std::move(telegram)); // add to queue
}
//
@@ -319,7 +316,7 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
telegram_raw[2] = 0xFF; // fixed value indicating an extended message
telegram_raw[3] = telegram->offset;
// EMS+ has different format for read and write. See https://github.com/proddy/EMS-ESP/wiki/RC3xx-Thermostats
// EMS+ has different format for read and write
if (telegram->operation == Telegram::Operation::TX_WRITE) {
// WRITE
telegram_raw[4] = (telegram->type_id >> 8) - 1; // type, 1st byte, high-byte, subtract 0x100
@@ -472,7 +469,9 @@ void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t lengt
operation = Telegram::Operation::TX_READ;
} else {
operation = Telegram::Operation::TX_WRITE;
set_post_send_query(type_id);
}
EMSESP::set_read_id(type_id);
}
auto telegram = std::make_shared<Telegram>(operation, src, dest, type_id, offset, message_data, message_length); // operation is TX_WRITE or TX_READ
@@ -535,7 +534,7 @@ void TxService::send_raw(const char * telegram_data) {
return; // nothing to send
}
add(Telegram::Operation::TX_RAW, data, count + 1); // add to Tx queue
add(Telegram::Operation::TX_RAW, data, count + 1, true); // add to front of Tx queue
}
// add last Tx to tx queue and increment count
@@ -578,21 +577,22 @@ bool TxService::is_last_tx(const uint8_t src, const uint8_t dest) const {
}
// sends a type_id read request to fetch values after a successful Tx write operation
void TxService::post_send_query() {
if (telegram_last_post_send_query_) {
uint8_t dest = (telegram_last_->dest & 0x7F);
uint8_t message_data[1] = {EMS_MAX_TELEGRAM_LENGTH}; // request all data, 32 bytes
add(Telegram::Operation::TX_READ, dest, telegram_last_post_send_query_, 0, message_data, 1, true);
// read_request(telegram_last_post_send_query_, dest, 0); // no offset
LOG_DEBUG(F("Sending post validate read, type ID 0x%02X to dest 0x%02X"), telegram_last_post_send_query_, dest);
}
}
// unless the post_send_query has a type_id of 0
uint16_t TxService::post_send_query() {
uint16_t post_typeid = this->get_post_send_query();
// print out the last Tx that was sent
void TxService::print_last_tx() {
LOG_DEBUG(F("Last Tx %s operation: %s"),
(telegram_last_->operation == Telegram::Operation::TX_WRITE) ? F("Write") : F("Read"),
telegram_last_->to_string().c_str());
if (post_typeid) {
uint8_t dest = (this->telegram_last_->dest & 0x7F);
// when set a value with large offset before and validate on same type, we have to add offset 0, 26, 52, ...
uint8_t offset = (this->telegram_last_->type_id == post_typeid) ? ((this->telegram_last_->offset / 26) * 26) : 0;
uint8_t message_data[1] = {EMS_MAX_TELEGRAM_LENGTH}; // request all data, 32 bytes
this->add(Telegram::Operation::TX_READ, dest, post_typeid, offset, message_data, 1, true);
// read_request(telegram_last_post_send_query_, dest, 0); // no offset
LOG_DEBUG(F("Sending post validate read, type ID 0x%02X to dest 0x%02X"), post_typeid, dest);
set_post_send_query(0); // reset
}
return post_typeid;
}
} // namespace emsesp

View File

@@ -83,36 +83,43 @@ class Telegram {
std::string to_string() const;
// reads a bit value from a given telegram position
void read_bitvalue(uint8_t & value, const uint8_t index, const uint8_t bit) const {
uint8_t abs_index = (index - offset);
if (abs_index >= message_length - 1) {
return; // out of bounds
bool read_bitvalue(uint8_t & value, const uint8_t index, const uint8_t bit) const {
uint8_t abs_index = (index - this->offset);
if (abs_index >= this->message_length) {
return false; // out of bounds
}
value = (uint8_t)(((message_data[abs_index]) >> (bit)) & 0x01);
uint8_t val = value;
value = (uint8_t)(((this->message_data[abs_index]) >> (bit)) & 0x01);
if (val != value) {
return true;
}
return false;
}
// read values from a telegram. We always store the value, regardless if its garbage
// read a value from a telegram. We always store the value, regardless if its garbage
template <typename Value>
// assuming negative numbers are stored as 2's-complement
// https://medium.com/@LeeJulija/how-integers-are-stored-in-memory-using-twos-complement-5ba04d61a56c
// 2-compliment : https://www.rapidtables.com/convert/number/decimal-to-hex.html
// https://en.wikipedia.org/wiki/Two%27s_complement
// s is to override number of bytes read (e.g. use 3 to simulat a uint24_t)
void read_value(Value & value, const uint8_t index, uint8_t s = 0) const {
uint8_t size = (!s) ? sizeof(Value) : s;
int8_t abs_index = ((index - offset + size - 1) >= message_length - 1) ? -1 : (index - offset);
if (abs_index < 0) {
return; // out of bounds, we don't change the value
// s is to override number of bytes read (e.g. use 3 to simulate a uint24_t)
bool read_value(Value & value, const uint8_t index, uint8_t s = 0) const {
uint8_t num_bytes = (!s) ? sizeof(Value) : s;
// check for out of bounds, if so don't modify the value
if ((index < this->offset) || ((index - this->offset + num_bytes - 1) >= this->message_length)) {
return false;
}
value = 0;
for (uint8_t i = 0; i < size; i++) {
value = (value << 8) + message_data[abs_index + i]; // shift
auto val = value;
value = 0;
for (uint8_t i = 0; i < num_bytes; i++) {
value = (value << 8) + this->message_data[index - this->offset + i]; // shift by byte
}
if (val != value) {
return true;
}
return false;
}
private:
int8_t _getDataPosition(const uint8_t index, const uint8_t size) const;
};
@@ -182,13 +189,12 @@ class EMSbus {
private:
static constexpr uint32_t EMS_BUS_TIMEOUT = 30000; // timeout in ms before recognizing the ems bus is offline (30 seconds)
static uint32_t last_bus_activity_; // timestamp of last time a valid Rx came in
static bool bus_connected_; // start assuming the bus hasn't been connected
static uint8_t ems_mask_; // unset=0xFF, buderus=0x00, junkers/ht3=0x80
static uint8_t ems_bus_id_; // the bus id, which configurable and stored in settings
static uint8_t tx_mode_; // local copy of the tx mode
static uint8_t tx_state_; // state of the Tx line (NONE or waiting on a TX_READ or TX_WRITE)
static uint32_t last_bus_activity_; // timestamp of last time a valid Rx came in
static bool bus_connected_; // start assuming the bus hasn't been connected
static uint8_t ems_mask_; // unset=0xFF, buderus=0x00, junkers/ht3=0x80
static uint8_t ems_bus_id_; // the bus id, which configurable and stored in settings
static uint8_t tx_mode_; // local copy of the tx mode
static uint8_t tx_state_; // state of the Tx line (NONE or waiting on a TX_READ or TX_WRITE)
};
class RxService : public EMSbus {
@@ -201,7 +207,7 @@ class RxService : public EMSbus {
void loop();
void add(uint8_t * data, uint8_t length);
uint16_t telegram_count() const {
uint32_t telegram_count() const {
return telegram_count_;
}
@@ -209,7 +215,7 @@ class RxService : public EMSbus {
telegram_count_++;
}
uint16_t telegram_error_count() const {
uint32_t telegram_error_count() const {
return telegram_error_count_;
}
@@ -235,44 +241,38 @@ class RxService : public EMSbus {
private:
uint8_t rx_telegram_id_ = 0; // queue counter
uint16_t telegram_count_ = 0; // # Rx received
uint16_t telegram_error_count_ = 0; // # Rx CRC errors
uint32_t telegram_count_ = 0; // # Rx received
uint32_t telegram_error_count_ = 0; // # Rx CRC errors
std::shared_ptr<const Telegram> rx_telegram; // the incoming Rx telegram
std::list<QueuedRxTelegram> rx_telegrams_; // the Rx Queue
std::list<QueuedRxTelegram> rx_telegrams_; // the Rx Queue
};
class TxService : public EMSbus {
public:
static constexpr size_t MAX_TX_TELEGRAMS = 20; // size of Tx queue
static constexpr uint8_t TX_WRITE_FAIL = 4; // EMS return code for fail
static constexpr uint8_t TX_WRITE_SUCCESS = 1; // EMS return code for success
static constexpr size_t MAX_TX_TELEGRAMS = 20; // size of Tx queue
static constexpr uint8_t TX_WRITE_FAIL = 4; // EMS return code for fail
static constexpr uint8_t TX_WRITE_SUCCESS = 1; // EMS return code for success
TxService() = default;
~TxService() = default;
void start();
void send();
void add(const uint8_t operation,
const uint8_t dest,
const uint16_t type_id,
const uint8_t offset,
uint8_t * message_data,
const uint8_t message_length,
const bool front = false);
void add(const uint8_t operation, const uint8_t * data, const uint8_t length, const bool front = false);
void read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset = 0);
void send_raw(const char * telegram_data);
void send_poll();
void flush_tx_queue();
void retry_tx(const uint8_t operation, const uint8_t * data, const uint8_t length);
void start();
void send();
void add(const uint8_t operation,
const uint8_t dest,
const uint16_t type_id,
const uint8_t offset,
uint8_t * message_data,
const uint8_t message_length,
const bool front = false);
void add(const uint8_t operation, const uint8_t * data, const uint8_t length, const bool front = false);
void read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset = 0);
void send_raw(const char * telegram_data);
void send_poll();
void flush_tx_queue();
void retry_tx(const uint8_t operation, const uint8_t * data, const uint8_t length);
bool is_last_tx(const uint8_t src, const uint8_t dest) const;
uint16_t post_send_query();
uint8_t retry_count() const {
return retry_count_;
@@ -282,13 +282,15 @@ class TxService : public EMSbus {
retry_count_ = 0;
}
bool is_last_tx(const uint8_t src, const uint8_t dest) const;
void set_post_send_query(uint16_t type_id) {
telegram_last_post_send_query_ = type_id;
}
uint16_t telegram_read_count() const {
uint16_t get_post_send_query() {
return telegram_last_post_send_query_;
}
uint32_t telegram_read_count() const {
return telegram_read_count_;
}
@@ -300,7 +302,7 @@ class TxService : public EMSbus {
telegram_read_count_++;
}
uint16_t telegram_fail_count() const {
uint32_t telegram_fail_count() const {
return telegram_fail_count_;
}
@@ -312,7 +314,7 @@ class TxService : public EMSbus {
telegram_fail_count_++;
}
uint16_t telegram_write_count() const {
uint32_t telegram_write_count() const {
return telegram_write_count_;
}
@@ -324,10 +326,6 @@ class TxService : public EMSbus {
telegram_write_count_++;
}
void post_send_query();
void print_last_tx();
class QueuedTxTelegram {
public:
const uint16_t id_;
@@ -355,9 +353,9 @@ class TxService : public EMSbus {
private:
std::list<QueuedTxTelegram> tx_telegrams_; // the Tx queue
uint16_t telegram_read_count_ = 0; // # Tx successful reads
uint16_t telegram_write_count_ = 0; // # Tx successful writes
uint16_t telegram_fail_count_ = 0; // # Tx unsuccessful transmits
uint32_t telegram_read_count_ = 0; // # Tx successful reads
uint32_t telegram_write_count_ = 0; // # Tx successful writes
uint32_t telegram_fail_count_ = 0; // # Tx unsuccessful transmits
std::shared_ptr<Telegram> telegram_last_;
uint16_t telegram_last_post_send_query_; // which type ID to query after a successful send, to read back the values just written

View File

@@ -123,10 +123,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
uint8bitb = EMS_VALUE_UINT_NOTSET;
telegram->read_bitvalue(uint8bitb, 0, 0); // value is 0x01 = 0000 0001
shell.printfln("uint8 bit read: expecting 1, got:%d", uint8bitb);
shell.loop_all();
return;
}
if (command == "devices") {
@@ -142,6 +138,9 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
// 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});
}
// unknown device -
@@ -159,7 +158,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
// note there is no brand (byte 9)
rx_telegram({0x09, 0x0B, 0x02, 0x00, 0x59, 0x09, 0x0a});
shell.loop_all();
EMSESP::show_device_values(shell);
}
@@ -210,8 +208,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
shell.invoke_command("show");
// shell.invoke_command("system");
// shell.invoke_command("show mqtt");
// shell.loop_all();
}
if (command == "thermostat") {
@@ -230,13 +226,34 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
// EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline
// add a thermostat
EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
EMSESP::add_device(0x10, 192, version, EMSdevice::Brand::JUNKERS); // FW120
// RCPLUSStatusMessage_HC1(0x01A5)
uart_telegram({0x98, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24,
// 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});
shell.loop_all();
// 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});
}
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
// 0x0A
uart_telegram({0x98, 0x0B, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
}
if (command == "solar") {
@@ -272,6 +289,39 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
EMSESP::show_device_values(shell);
}
if (command == "solar200") {
shell.printfln(F("Testing Solar SM200"));
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
std::string version("1.2.3");
EMSESP::add_device(0x30, 164, version, EMSdevice::Brand::BUDERUS); // 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);
}
if (command == "km") {
shell.printfln(F("Testing KM200 Gateway"));
@@ -353,7 +403,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
uart_telegram("98 00 FF 00 01 A5 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03"); // without CRC
uart_telegram_withCRC("98 00 FF 00 01 A5 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03 13"); // with CRC
uart_telegram_withCRC("98 00 FF 00 01 A6 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03 6B"); // with CRC
EMSESP::txservice_.flush_tx_queue();
shell.loop_all();
@@ -524,8 +574,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
shell.invoke_command("help");
shell.invoke_command("pin");
shell.invoke_command("pin 1 true");
shell.loop_all();
}
if (command == "mqtt") {
@@ -539,7 +587,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
// add a thermostat
EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
// RCPLUSStatusMessage_HC1(0x01A5)
// RCPLUSStatusMessage_HC1(0x01A5) - HC1
uart_telegram({0x98, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24,
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
uart_telegram("98 00 FF 00 01 A5 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03"); // without CRC
@@ -576,6 +624,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"control\",\"data\":1}");
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"mode\",\"data\":\"auto\",\"id\":2}");
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"mode\",\"data\":\"auto\",\"hc\":2}"); // hc as number
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"temp\",\"data\":19.5,\"hc\":1}"); // data as number
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"mode\",\"data\":\"auto\",\"hc\":\"2\"}"); // hc as string
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"temp\",\"data\":22.56}");
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"temp\",\"data\":22}");
@@ -596,8 +645,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
Mqtt::resubscribe();
Mqtt::show_mqtt(shell); // show queue
shell.loop_all();
}
if (command == "poll2") {
@@ -656,7 +703,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
}
// finally dump to console
shell.loop_all();
EMSESP::loop();
}
// simulates a telegram in the Rx queue, but without the CRC which is added automatically
@@ -683,6 +730,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();
}
// takes raw string, assuming it contains the CRC. This is what is output from 'watch raw'
@@ -720,6 +768,7 @@ void Test::uart_telegram_withCRC(const char * rx_data) {
}
EMSESP::incoming_telegram(data, count + 1);
EMSESP::rxservice_.loop();
}
// takes raw string, adds CRC to end
@@ -759,6 +808,7 @@ 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();
}
#pragma GCC diagnostic push

View File

@@ -46,21 +46,25 @@ void ICACHE_RAM_ATTR EMSuart::emsuart_rx_intr_handler(void * para) {
if (USIS(EMSUART_UART) & ((1 << UIBD))) { // BREAK detection = End of EMS data block
USC0(EMSUART_UART) &= ~(1 << UCBRK); // reset tx-brk
if (emsTxBufIdx < emsTxBufLen) { // irq tx_mode is interrupted by <brk>
emsTxBufIdx = emsTxBufLen + 1; // stop tx
// drop_next_rx = true; // we have trash in buffer
if (sending_) { // irq tx_mode is interrupted by <brk>, should never happen
drop_next_rx = true; // we have trash in buffer
}
USIC(EMSUART_UART) = (1 << UIBD); // INT clear the BREAK detect interrupt
length = 0;
while ((USS(EMSUART_UART) >> USRXC) & 0x0FF) { // read fifo into buffer
uint8_t rx = USF(EMSUART_UART);
if (length < EMS_MAXBUFFERSIZE) {
uart_buffer[length++] = rx;
if (length || rx) { // skip a leading zero
uart_buffer[length++] = rx;
}
} else {
drop_next_rx = true;
}
}
if (!drop_next_rx) {
if (uart_buffer[length - 1]) { // check if last byte is break
length++;
}
pEMSRxBuf->length = length;
os_memcpy((void *)pEMSRxBuf->buffer, (void *)&uart_buffer, pEMSRxBuf->length); // copy data into transfer buffer, including the BRK 0x00 at the end
system_os_post(EMSUART_recvTaskPrio, 0, 0); // call emsuart_recvTask() at next opportunity
@@ -89,6 +93,9 @@ void ICACHE_FLASH_ATTR EMSuart::emsuart_recvTask(os_event_t * events) {
// ISR to Fire when Timer is triggered
void ICACHE_RAM_ATTR EMSuart::emsuart_tx_timer_intr_handler() {
if (!sending_) {
return;
}
emsTxBufIdx++;
if (emsTxBufIdx < emsTxBufLen) {
USF(EMSUART_UART) = emsTxBuf[emsTxBufIdx];

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "2.0.0"
#define EMSESP_APP_VERSION "2.0.1"