mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 15:59:52 +03:00
a7
This commit is contained in:
@@ -41,7 +41,7 @@ Note: Version 2.0 is not backward compatible with v1.0. The File system structur
|
||||
common commands available in all contexts:
|
||||
exit
|
||||
help
|
||||
log [level] [trace ID]
|
||||
log [level] [trace ID] [raw]
|
||||
su
|
||||
|
||||
(top level)
|
||||
@@ -124,7 +124,6 @@ thermostat
|
||||
### **Known issues, bugs and improvements currently working on**
|
||||
|
||||
```
|
||||
TODO when doing show, should we sort the ems devices?
|
||||
TODO figure out why sometimes telnet on ESP32 (and sometimes ESP8266) has slow response times. After a manual reset it seems to fix itself. Perhaps the telnet service needs to start after the wifi is up & running.
|
||||
TODO Get the ESP32 UART code working.
|
||||
TODO sometimes with tx_mode 0 there are a few CRC errors due to collision when waiting for a BRK signal.
|
||||
@@ -152,4 +151,6 @@ TODO See if it's easier to use timers instead of millis() timers, using https://
|
||||
```
|
||||
TODO merge in the web code which has the Captive AP and better wifi reconnect logic. Use IPV6 and NTP from lwip2
|
||||
TODO decide if I want to port over the shower one-shot cold water logic. Don't think its used.
|
||||
TODO when doing show in telnet, should we sort the ems devices?
|
||||
|
||||
```
|
||||
|
||||
@@ -21,7 +21,7 @@ extra_configs = pio_local.ini
|
||||
|
||||
;debug_flags = -DDEBUG_ESP_PORT=Serial -DDEBUG_ESP_CORE -DDEBUG_ESP_SSL -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_UPDATE -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_UPDATER -DDEBUG_ESP_OTA -DDEBUG_TLS_MEM
|
||||
debug_flags =
|
||||
; -D EMSESP_DEBUG
|
||||
-D EMSESP_DEBUG
|
||||
; -D EMSESP_SAFE_MODE
|
||||
; -D ENABLE_CORS -D CORS_ORIGIN=\"http://localhost:3000\"
|
||||
|
||||
@@ -33,7 +33,7 @@ build_flags = -std=c++11 -Os -fno-exceptions
|
||||
-D ARDUINOJSON_USE_LONG_LONG=0
|
||||
-D BEARSSL_SSL_BASIC
|
||||
-D PROGMEM_WWW
|
||||
-D UUID_TELNET_HAVE_WIFICLIENT_NODELAY=0
|
||||
-D UUID_TELNET_HAVE_WIFICLIENT_NODELAY=1
|
||||
|
||||
libs_core =
|
||||
ArduinoJson
|
||||
@@ -58,18 +58,18 @@ check_flags =
|
||||
clangtidy: --checks=-*,clang-analyzer-*,performance-*
|
||||
|
||||
; USB upload
|
||||
upload_protocol = esptool
|
||||
; upload_protocol = esptool
|
||||
; example ports for OSX
|
||||
;upload_port = /dev/cu.wchusbserial14403
|
||||
;upload_port = /dev/cu.usbserial-1440
|
||||
;upload_port = /dev/cu.SLAB_USBtoUART
|
||||
|
||||
; OTA upload
|
||||
; upload_protocol = espota
|
||||
; upload_flags =
|
||||
; --port=8266
|
||||
; --auth=neo
|
||||
; upload_port = ems-esp.local
|
||||
upload_protocol = espota
|
||||
upload_flags =
|
||||
--port=8266
|
||||
--auth=neo
|
||||
upload_port = ems-esp32.local
|
||||
|
||||
[env:esp8266]
|
||||
build_type = release
|
||||
|
||||
@@ -46,7 +46,7 @@ uuid::log::Logger Boiler::logger_{F_(logger_name), uuid::log::Facility::CONSOLE}
|
||||
|
||||
Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
|
||||
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
|
||||
DEBUG_LOG(F("Registering new Boiler with device ID 0x%02X"), device_id);
|
||||
LOG_DEBUG(F("Registering new Boiler with device ID 0x%02X"), device_id);
|
||||
|
||||
// the telegram handlers...
|
||||
register_telegram_type(0x18, F("UBAMonitorFast"), true, std::bind(&Boiler::process_UBAMonitorFast, this, _1));
|
||||
@@ -91,7 +91,7 @@ void Boiler::boiler_cmd(const char * message) {
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
|
||||
DeserializationError error = deserializeJson(doc, message);
|
||||
if (error) {
|
||||
DEBUG_LOG(F("MQTT error: payload %s, error %s"), message, error.c_str());
|
||||
LOG_DEBUG(F("MQTT error: payload %s, error %s"), message, error.c_str());
|
||||
return;
|
||||
}
|
||||
const char * command = doc["cmd"];
|
||||
@@ -312,7 +312,7 @@ void Boiler::publish_values() {
|
||||
}
|
||||
|
||||
#ifdef EMSESP_DEBUG
|
||||
DEBUG_LOG(F("[DEBUG] Performing a boiler publish"));
|
||||
LOG_DEBUG(F("[DEBUG] Performing a boiler publish"));
|
||||
#endif
|
||||
|
||||
// if we have data, publish it
|
||||
@@ -675,31 +675,31 @@ void Boiler::process_UBAMaintenanceSettings(std::shared_ptr<const Telegram> tele
|
||||
|
||||
// Set the warm water temperature 0x33
|
||||
void Boiler::set_warmwater_temp(const uint8_t temperature) {
|
||||
logger_.info(F("Setting boiler warm water temperature to %d C"), temperature);
|
||||
LOG_INFO(F("Setting boiler warm water temperature to %d C"), temperature);
|
||||
write_command(EMS_TYPE_UBAParameterWW, 2, temperature);
|
||||
}
|
||||
|
||||
// flow temp
|
||||
void Boiler::set_flow_temp(const uint8_t temperature) {
|
||||
logger_.info(F("Setting boiler flow temperature to %d C"), temperature);
|
||||
LOG_INFO(F("Setting boiler flow temperature to %d C"), temperature);
|
||||
write_command(EMS_TYPE_UBASetPoints, 0, temperature);
|
||||
}
|
||||
|
||||
// 1=hot, 2=eco, 3=intelligent
|
||||
void Boiler::set_warmwater_mode(const uint8_t comfort) {
|
||||
if (comfort == 1) {
|
||||
logger_.info(F("Setting boiler warm water to hot"));
|
||||
LOG_INFO(F("Setting boiler warm water to hot"));
|
||||
} else if (comfort == 2) {
|
||||
logger_.info(F("Setting boiler warm water to eco"));
|
||||
LOG_INFO(F("Setting boiler warm water to eco"));
|
||||
} else if (comfort == 3) {
|
||||
logger_.info(F("Setting boiler warm water to intelligent"));
|
||||
LOG_INFO(F("Setting boiler warm water to intelligent"));
|
||||
}
|
||||
write_command(EMS_TYPE_UBAParameterWW, 9, comfort);
|
||||
}
|
||||
|
||||
// turn on/off warm water
|
||||
void Boiler::set_warmwater_activated(const bool activated) {
|
||||
logger_.info(F("Setting boiler warm water %s"), activated ? "on" : "off");
|
||||
LOG_INFO(F("Setting boiler warm water %s"), activated ? "on" : "off");
|
||||
uint8_t value;
|
||||
|
||||
// https://github.com/proddy/EMS-ESP/issues/268
|
||||
@@ -715,7 +715,7 @@ void Boiler::set_warmwater_activated(const bool activated) {
|
||||
// true = on, false = off
|
||||
// Note: Using the type 0x1D to put the boiler into Test mode. This may be shown on the boiler with a flashing 'T'
|
||||
void Boiler::set_tapwarmwater_activated(const bool activated) {
|
||||
logger_.info(F("Setting boiler warm tap water %s"), activated ? "on" : "off");
|
||||
LOG_INFO(F("Setting boiler warm tap water %s"), activated ? "on" : "off");
|
||||
uint8_t message_data[EMS_MAX_TELEGRAM_MESSAGE_LENGTH];
|
||||
for (uint8_t i = 0; i < sizeof(message_data); i++) {
|
||||
message_data[i] = 0x00;
|
||||
@@ -743,14 +743,14 @@ void Boiler::set_tapwarmwater_activated(const bool activated) {
|
||||
// true = on, false = off
|
||||
// See also https://github.com/proddy/EMS-ESP/issues/341#issuecomment-596245458 for Junkers
|
||||
void Boiler::set_warmwater_onetime(const bool activated) {
|
||||
logger_.info(F("Setting boiler warm water OneTime loading %s"), activated ? "on" : "off");
|
||||
LOG_INFO(F("Setting boiler warm water OneTime loading %s"), activated ? "on" : "off");
|
||||
write_command(EMS_TYPE_UBAFlags, 0, (activated ? 0x22 : 0x02));
|
||||
}
|
||||
|
||||
// Activate / De-activate circulation of warm water 0x35
|
||||
// true = on, false = off
|
||||
void Boiler::set_warmwater_circulation(const bool activated) {
|
||||
logger_.info(F("Setting boiler warm water circulation %s"), activated ? "on" : "off");
|
||||
LOG_INFO(F("Setting boiler warm water circulation %s"), activated ? "on" : "off");
|
||||
write_command(EMS_TYPE_UBAFlags, 1, (activated ? 0x22 : 0x02));
|
||||
}
|
||||
|
||||
|
||||
@@ -221,8 +221,9 @@ void Console::load_standard_commands(unsigned int context) {
|
||||
context,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(log)},
|
||||
flash_string_vector{F_(log_level_optional), F_(traceid_optional)},
|
||||
flash_string_vector{F_(log_level_optional), F_(traceid_optional), F_(raw_optional)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
uint16_t watch_id;
|
||||
if (!arguments.empty()) {
|
||||
uuid::log::Level level;
|
||||
|
||||
@@ -233,15 +234,33 @@ void Console::load_standard_commands(unsigned int context) {
|
||||
return;
|
||||
}
|
||||
|
||||
// see if we have extra argument, for trace
|
||||
uint16_t watch_id = 0; // no watch ID set
|
||||
if ((arguments.size() == 2) && (level == uuid::log::Level::TRACE)) {
|
||||
watch_id = Helpers::hextoint(arguments[1].c_str());
|
||||
shell.printfln(("Tracing only telegrams that match a device ID or telegram type of 0x%02X"), watch_id);
|
||||
// trace logic
|
||||
if (level == uuid::log::Level::TRACE) {
|
||||
watch_id = LOG_TRACE_WATCH_NONE; // no watch ID set
|
||||
if (arguments.size() > 1) {
|
||||
watch_id = Helpers::hextoint(arguments[1].c_str()); // get the watch ID
|
||||
}
|
||||
|
||||
emsesp::EMSESP::trace_watch_id(watch_id);
|
||||
|
||||
// see if we have a "raw"
|
||||
if ((arguments.size() == 3) && (arguments[2] == read_flash_string(F_(raw)))) {
|
||||
emsesp::EMSESP::trace_raw(true);
|
||||
} else {
|
||||
emsesp::EMSESP::trace_raw(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// print out logging settings
|
||||
shell.printfln(F_(log_level_fmt), uuid::log::format_level_uppercase(shell.log_level()));
|
||||
watch_id = emsesp::EMSESP::trace_watch_id();
|
||||
if (watch_id == LOG_TRACE_WATCH_NONE) {
|
||||
shell.printfln(F("Tracing all telegrams"));
|
||||
} else {
|
||||
shell.printfln(F("Tracing only telegrams that match a device ID or telegram type of 0x%02X"), watch_id);
|
||||
}
|
||||
shell.printfln(F_(trace_raw_fmt), emsesp::EMSESP::trace_raw() ? uuid::read_flash_string(F_(on)).c_str() : uuid::read_flash_string(F_(off)).c_str());
|
||||
},
|
||||
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> std::vector<std::string> {
|
||||
return uuid::log::levels_lowercase();
|
||||
@@ -384,6 +403,8 @@ EMSESPStreamConsole::~EMSESPStreamConsole() {
|
||||
#endif
|
||||
ptys_[pty_] = false;
|
||||
ptys_.shrink_to_fit();
|
||||
|
||||
EMSESPShell::commands->remove_all_commands();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,7 +435,7 @@ void Console::start() {
|
||||
// note, this must be started after the network/wifi for ESP32 otherwise it'll crash
|
||||
#ifndef EMSESP_STANDALONE
|
||||
telnet_.start();
|
||||
telnet_.default_write_timeout(1000); // in ms, socket timeout 1 second
|
||||
// telnet_.default_write_timeout(1000); // in ms, socket timeout 1 second
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -41,8 +41,12 @@ using uuid::log::Level;
|
||||
|
||||
// clang-format off
|
||||
|
||||
#define DEBUG_LOG(...) if (logger_.enabled(Level::DEBUG)) {logger_.debug(__VA_ARGS__);}
|
||||
#define TRACE_LOG(...) if (logger_.enabled(Level::TRACE)) {logger_.trace(__VA_ARGS__);}
|
||||
#define LOG_DEBUG(...) if (logger_.enabled(Level::DEBUG)) {logger_.debug(__VA_ARGS__);}
|
||||
#define LOG_TRACE(...) if (logger_.enabled(Level::TRACE)) {logger_.trace(__VA_ARGS__);}
|
||||
#define LOG_INFO(...) logger_.info(__VA_ARGS__)
|
||||
#define LOG_NOTICE(...) logger_.notice(__VA_ARGS__)
|
||||
#define LOG_WARNING(...) logger_.warning(__VA_ARGS__)
|
||||
#define LOG_ERROR(...) logger_.err(__VA_ARGS__)
|
||||
|
||||
#define MAKE_PSTR(string_name, string_literal) static const char __pstr__##string_name[] __attribute__((__aligned__(sizeof(int)))) PROGMEM = string_literal;
|
||||
#define MAKE_PSTR_WORD(string_name) MAKE_PSTR(string_name, #string_name)
|
||||
@@ -84,6 +88,7 @@ MAKE_PSTR_WORD(debug)
|
||||
MAKE_PSTR_WORD(restart)
|
||||
MAKE_PSTR_WORD(reconnect)
|
||||
MAKE_PSTR_WORD(format)
|
||||
MAKE_PSTR_WORD(raw)
|
||||
|
||||
// context menus
|
||||
MAKE_PSTR_WORD(mqtt)
|
||||
@@ -96,6 +101,8 @@ MAKE_PSTR(asterisks, "********")
|
||||
MAKE_PSTR(n_mandatory, "<n>")
|
||||
MAKE_PSTR(n_optional, "[n]")
|
||||
MAKE_PSTR(traceid_optional, "[trace ID]")
|
||||
MAKE_PSTR(trace_raw_fmt, "Displaying raw bytes = %s")
|
||||
MAKE_PSTR(raw_optional, "[raw]")
|
||||
MAKE_PSTR(bool_mandatory, "<on | off>")
|
||||
MAKE_PSTR(typeid_mandatory, "<type ID>")
|
||||
MAKE_PSTR(deviceid_mandatory, "<device ID>")
|
||||
|
||||
@@ -160,7 +160,7 @@ void EMSdevice::show_values(uuid::console::Shell & shell) {
|
||||
|
||||
// for each telegram that has the fetch value set (true) do a read request
|
||||
void EMSdevice::fetch_values() {
|
||||
DEBUG_LOG(F("Fetching values for device ID 0x%02X"), device_id());
|
||||
LOG_DEBUG(F("Fetching values for device ID 0x%02X"), device_id());
|
||||
|
||||
for (const auto & tf : telegram_functions_) {
|
||||
if (tf.fetch_) {
|
||||
@@ -188,7 +188,7 @@ void EMSdevice::show_mqtt_handlers(uuid::console::Shell & shell) {
|
||||
}
|
||||
|
||||
void EMSdevice::register_mqtt_topic(const std::string & topic, mqtt_function_p f) {
|
||||
DEBUG_LOG(F("Registering MQTT topic %s for device ID %02X"), topic.c_str(), this->device_id_);
|
||||
LOG_DEBUG(F("Registering MQTT topic %s for device ID %02X"), topic.c_str(), this->device_id_);
|
||||
Mqtt::subscribe(this->device_id_, topic, f);
|
||||
}
|
||||
|
||||
@@ -230,7 +230,7 @@ std::string EMSdevice::telegram_type_name(std::shared_ptr<const Telegram> telegr
|
||||
bool EMSdevice::process_telegram(std::shared_ptr<const Telegram> telegram) {
|
||||
for (const auto & tf : telegram_functions_) {
|
||||
if (tf.telegram_type_id_ == telegram->type_id) {
|
||||
DEBUG_LOG(F("Processing %s..."), uuid::read_flash_string(tf.telegram_type_name_).c_str());
|
||||
LOG_DEBUG(F("Processing %s..."), uuid::read_flash_string(tf.telegram_type_name_).c_str());
|
||||
tf.process_function_(telegram);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,8 @@ Shower EMSESP::shower_; // Shower logic
|
||||
|
||||
// static/common variables
|
||||
uint8_t EMSESP::actual_master_thermostat_ = EMSESP_DEFAULT_MASTER_THERMOSTAT; // which thermostat leads when multiple found
|
||||
uint16_t EMSESP::trace_watch_id_ = 0; // for when log is TRACE
|
||||
uint16_t EMSESP::trace_watch_id_ = LOG_TRACE_WATCH_NONE; // for when log is TRACE. 0 means no trace set
|
||||
bool EMSESP::trace_raw_ = false; // not showing raw when in trace logging
|
||||
bool EMSESP::tap_water_active_ = false; // for when Boiler states we having running warm water. used in Shower()
|
||||
bool EMSESP::ems_read_only_;
|
||||
uint32_t EMSESP::last_fetch_ = 0;
|
||||
@@ -332,7 +333,7 @@ void EMSESP::process_UBADevices(std::shared_ptr<const Telegram> telegram) {
|
||||
// if we haven't already detected this device, request it's version details, unless its us (EMS-ESP)
|
||||
// when the version info is received, it will automagically add the device
|
||||
if ((device_id != ems_bus_id) && !(EMSESP::device_exists(device_id))) {
|
||||
logger_.info(F("New EMS device detected with ID 0x%02X. Requesting version information."), device_id);
|
||||
LOG_INFO(F("New EMS device detected with ID 0x%02X. Requesting version information."), device_id);
|
||||
send_read_request(EMSdevice::EMS_TYPE_VERSION, device_id);
|
||||
}
|
||||
}
|
||||
@@ -391,9 +392,11 @@ void EMSESP::process_version(std::shared_ptr<const Telegram> telegram) {
|
||||
// We also check for common telgram types, like the Version(0x02)
|
||||
// returns false if there are none found
|
||||
bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
|
||||
// print to console if logging is TRACE
|
||||
if ((trace_watch_id_ == 0) || (telegram->src == trace_watch_id_) || (telegram->dest == trace_watch_id_) || (telegram->type_id == trace_watch_id_)) {
|
||||
TRACE_LOG(pretty_telegram(telegram).c_str());
|
||||
if ((logger_.enabled(Level::TRACE)) && !trace_raw()) {
|
||||
if ((trace_watch_id_ == LOG_TRACE_WATCH_NONE) || (telegram->src == trace_watch_id_) || (telegram->dest == trace_watch_id_)
|
||||
|| (telegram->type_id == trace_watch_id_)) {
|
||||
LOG_TRACE(pretty_telegram(telegram).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// only process broadcast telegrams or ones sent to us on request
|
||||
@@ -431,7 +434,7 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
DEBUG_LOG(F("No telegram type handler found for type ID 0x%02X (src 0x%02X, dest 0x%02X)"), telegram->type_id, telegram->src, telegram->dest);
|
||||
LOG_DEBUG(F("No telegram type handler found for type ID 0x%02X (src 0x%02X, dest 0x%02X)"), telegram->type_id, telegram->src, telegram->dest);
|
||||
}
|
||||
|
||||
return found;
|
||||
@@ -504,7 +507,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
if (emsdevice) {
|
||||
if (emsdevice->is_device_id(device_id)) {
|
||||
DEBUG_LOG(F("Updating details for already existing device with ID 0x%02X"), device_id);
|
||||
LOG_DEBUG(F("Updating details for already existing device with ID 0x%02X"), device_id);
|
||||
emsdevice->product_id(product_id);
|
||||
emsdevice->version(version);
|
||||
emsdevice->brand(brand);
|
||||
@@ -535,10 +538,10 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::
|
||||
|
||||
// if we don't recognize the product ID report it, but don't add it.
|
||||
if (!found) {
|
||||
logger_.notice(F("Unrecognized EMS device with device ID 0x%02X with product ID %d. Please report on GitHub."), device_id, product_id);
|
||||
LOG_NOTICE(F("Unrecognized EMS device with device ID 0x%02X with product ID %d. Please report on GitHub."), device_id, product_id);
|
||||
return false; // not found
|
||||
} else {
|
||||
DEBUG_LOG(F("Adding new device with device ID 0x%02X with product ID %d"), device_id, product_id);
|
||||
LOG_DEBUG(F("Adding new device with device ID 0x%02X with product ID %d"), device_id, product_id);
|
||||
// go and fetch its data, including asking for the version
|
||||
send_read_request(EMSdevice::EMS_TYPE_VERSION, device_id);
|
||||
fetch_device_values(device_id);
|
||||
@@ -578,12 +581,12 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
|
||||
EMSbus::tx_waiting(false); // reset Tx wait state
|
||||
if (length == 1) {
|
||||
if (first_value == TxService::TX_WRITE_SUCCESS) {
|
||||
DEBUG_LOG(F("Last Tx write successful. Sending read request."));
|
||||
LOG_DEBUG(F("Last Tx write successful. Sending read request."));
|
||||
txservice_.increment_telegram_write_count(); // last tx/write was confirmed ok
|
||||
txservice_.send_poll(); // close the bus
|
||||
txservice_.post_send_query(); // send type_id to last destination
|
||||
} else if (first_value == TxService::TX_WRITE_FAIL) {
|
||||
DEBUG_LOG(F("Last Tx write rejected by host"));
|
||||
LOG_DEBUG(F("Last Tx write rejected by host"));
|
||||
txservice_.send_poll(); // close the bus
|
||||
} else {
|
||||
// ignore it, it's probably a poll and we can wait for the next one
|
||||
@@ -595,7 +598,7 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
|
||||
uint8_t src = data[0];
|
||||
uint8_t dest = data[1];
|
||||
if (txservice_.is_last_tx(src, dest)) {
|
||||
DEBUG_LOG(F("Last Tx read successful"));
|
||||
LOG_DEBUG(F("Last Tx read successful"));
|
||||
txservice_.increment_telegram_read_count();
|
||||
txservice_.send_poll();
|
||||
} else {
|
||||
@@ -603,9 +606,9 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
|
||||
// So re-send the last Tx and increment retry count
|
||||
uint8_t retries = txservice_.retry_tx(); // returns 0 if exceeded count
|
||||
if (retries) {
|
||||
DEBUG_LOG(F("Last Tx read failed. Retrying #%d..."), retries);
|
||||
LOG_DEBUG(F("Last Tx read failed. Retrying #%d..."), retries);
|
||||
} else {
|
||||
DEBUG_LOG(F("Last Tx read failed. Giving up"));
|
||||
LOG_DEBUG(F("Last Tx read failed. Giving up"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -634,7 +637,7 @@ void EMSESP::send_raw_telegram(const char * data) {
|
||||
// sets the ems read only flag preventing any Tx from going out
|
||||
void EMSESP::set_ems_read_only() {
|
||||
ems_read_only_ = Settings().ems_read_only();
|
||||
DEBUG_LOG(F("Setting EMS read-only mode to %s"), ems_read_only_ ? F("on") : F("off"));
|
||||
LOG_DEBUG(F("Setting EMS read-only mode to %s"), ems_read_only_ ? F("on") : F("off"));
|
||||
}
|
||||
|
||||
// console commands to add
|
||||
@@ -786,7 +789,7 @@ void EMSESP::console_commands(Shell & shell, unsigned int context) {
|
||||
|
||||
// kick off the party, start all the services
|
||||
void EMSESP::start() {
|
||||
// Load our libary of known devices
|
||||
// Load our library of known devices
|
||||
device_library_ = {
|
||||
#include "device_library.h"
|
||||
};
|
||||
@@ -798,6 +801,7 @@ void EMSESP::start() {
|
||||
rxservice_.start();
|
||||
txservice_.start();
|
||||
shower_.start();
|
||||
mqtt_.start();
|
||||
|
||||
set_ems_read_only(); // see if we have Tx disabled and set the flag
|
||||
}
|
||||
|
||||
15
src/emsesp.h
15
src/emsesp.h
@@ -45,6 +45,8 @@
|
||||
#include "boiler.h"
|
||||
#include "shower.h"
|
||||
|
||||
#define LOG_TRACE_WATCH_NONE 0 // no watch set
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class Shower; // forward declaration for compiler
|
||||
@@ -79,8 +81,6 @@ class EMSESP {
|
||||
static uint8_t actual_master_thermostat();
|
||||
static void actual_master_thermostat(const uint8_t device_id);
|
||||
|
||||
static void trace_watch_id(uint16_t);
|
||||
|
||||
static void show_values(uuid::console::Shell & shell);
|
||||
static void show_devices(uuid::console::Shell & shell);
|
||||
static void show_emsbus(uuid::console::Shell & shell);
|
||||
@@ -93,10 +93,20 @@ class EMSESP {
|
||||
return sensors_.devices();
|
||||
}
|
||||
|
||||
static void trace_watch_id(uint16_t id);
|
||||
|
||||
static uint16_t trace_watch_id() {
|
||||
return trace_watch_id_;
|
||||
}
|
||||
|
||||
static void trace_raw(bool trace_raw) {
|
||||
trace_raw_ = trace_raw;
|
||||
}
|
||||
|
||||
static bool trace_raw() {
|
||||
return trace_raw_;
|
||||
}
|
||||
|
||||
static bool tap_water_active() {
|
||||
return tap_water_active_;
|
||||
}
|
||||
@@ -151,6 +161,7 @@ class EMSESP {
|
||||
|
||||
static uint8_t actual_master_thermostat_;
|
||||
static uint16_t trace_watch_id_;
|
||||
static bool trace_raw_;
|
||||
static bool tap_water_active_;
|
||||
static bool ems_read_only_;
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@ uuid::log::Logger Heatpump::logger_{F_(logger_name), uuid::log::Facility::CONSOL
|
||||
|
||||
Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
|
||||
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
|
||||
DEBUG_LOG(F("Registering new Heat Pump module with device ID 0x%02X"), device_id);
|
||||
LOG_DEBUG(F("Registering new Heat Pump module with device ID 0x%02X"), device_id);
|
||||
|
||||
// telegram handlers
|
||||
register_telegram_type(0x047B, F("HP1"), true, std::bind(&Heatpump::process_HPMonitor1, this, _1));
|
||||
|
||||
@@ -265,7 +265,8 @@ std::string Helpers::data_to_hex(const uint8_t * data, const uint8_t length) {
|
||||
}
|
||||
|
||||
|
||||
// takes a hex string and convert it to a 32bit number (max 8 hex digits)
|
||||
// takes a hex string and convert it to an unsigned 32bit number (max 8 hex digits)
|
||||
// works with only positive numbers
|
||||
uint32_t Helpers::hextoint(const char * hex) {
|
||||
uint32_t val = 0;
|
||||
while (*hex) {
|
||||
|
||||
@@ -26,7 +26,7 @@ uuid::log::Logger Mixing::logger_{F_(logger_name), uuid::log::Facility::CONSOLE}
|
||||
|
||||
Mixing::Mixing(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
|
||||
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
|
||||
DEBUG_LOG(F("Registering new Mixing module with device ID 0x%02X"), device_id);
|
||||
LOG_DEBUG(F("Registering new Mixing module with device ID 0x%02X"), device_id);
|
||||
|
||||
// telegram handlers 0x20 - 0x27 for HC
|
||||
register_telegram_type(0x02D7, F("MMPLUSStatusMessage_HC"), true, std::bind(&Mixing::process_MMPLUSStatusMessage_HC, this, _1));
|
||||
@@ -105,7 +105,7 @@ void Mixing::publish_values() {
|
||||
}
|
||||
|
||||
#ifdef EMSESP_DEBUG
|
||||
DEBUG_LOG(F("[DEBUG] Performing a mixing module publish"));
|
||||
LOG_DEBUG(F("[DEBUG] Performing a mixing module publish"));
|
||||
#endif
|
||||
|
||||
char topic[30];
|
||||
|
||||
129
src/mqtt.cpp
129
src/mqtt.cpp
@@ -93,11 +93,11 @@ void Mqtt::reconnect() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
mqttClient_.disconnect();
|
||||
#endif
|
||||
DEBUG_LOG(F("Reconnecting..."));
|
||||
LOG_DEBUG(F("Reconnecting..."));
|
||||
}
|
||||
|
||||
// MQTT setup
|
||||
void Mqtt::start() {
|
||||
void Mqtt::setup() {
|
||||
// exit if already initialized
|
||||
if (mqtt_start_) {
|
||||
return;
|
||||
@@ -145,7 +145,7 @@ void Mqtt::start() {
|
||||
mqtt_last_connection_ = millis();
|
||||
mqtt_reconnect_delay_ = Mqtt::MQTT_RECONNECT_DELAY_MIN;
|
||||
|
||||
DEBUG_LOG(F("Configuring MQTT service..."));
|
||||
LOG_DEBUG(F("Configuring MQTT service..."));
|
||||
}
|
||||
|
||||
// MQTT init callbacks
|
||||
@@ -161,19 +161,19 @@ void Mqtt::init() {
|
||||
|
||||
mqttClient_.onDisconnect([this](AsyncMqttClientDisconnectReason reason) {
|
||||
if (reason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) {
|
||||
logger_.err(F("Disconnected from server"));
|
||||
LOG_DEBUG(F("Disconnected from server"));
|
||||
}
|
||||
if (reason == AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED) {
|
||||
logger_.err(F("Server identifier Rejected"));
|
||||
LOG_ERROR(F("Server identifier Rejected"));
|
||||
}
|
||||
if (reason == AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE) {
|
||||
logger_.err(F("Server unavailable"));
|
||||
LOG_ERROR(F("Server unavailable"));
|
||||
}
|
||||
if (reason == AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS) {
|
||||
logger_.err(F("Malformed credentials"));
|
||||
LOG_ERROR(F("Malformed credentials"));
|
||||
}
|
||||
if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED) {
|
||||
logger_.err(F("Not authorized"));
|
||||
LOG_ERROR(F("Not authorized"));
|
||||
}
|
||||
|
||||
// Reset reconnection delay
|
||||
@@ -182,7 +182,7 @@ void Mqtt::init() {
|
||||
mqtt_start_ = false; // will force a new start()
|
||||
});
|
||||
|
||||
// mqttClient.onSubscribe([this](uint16_t packetId, uint8_t qos) { DEBUG_LOG(F("Subscribe ACK for PID %d"), packetId); });
|
||||
// mqttClient.onSubscribe([this](uint16_t packetId, uint8_t qos) { LOG_DEBUG(F("Subscribe ACK for PID %d"), packetId); });
|
||||
|
||||
mqttClient_.onPublish([this](uint16_t packetId) { on_publish(packetId); });
|
||||
|
||||
@@ -271,8 +271,8 @@ void Mqtt::loop() {
|
||||
}
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
start();
|
||||
logger_.info(F("Connecting to MQTT server..."));
|
||||
setup();
|
||||
LOG_INFO(F("Connecting to the MQTT server..."));
|
||||
mqttClient_.connect(); // Connect to the MQTT broker
|
||||
#endif
|
||||
}
|
||||
@@ -338,7 +338,7 @@ void Mqtt::on_message(char * topic, char * payload, size_t len) {
|
||||
strlcpy(message, payload, len + 1);
|
||||
|
||||
#ifdef EMSESP_DEBUG
|
||||
DEBUG_LOG(F("Received %s => %s (length %d)"), topic, message, len);
|
||||
LOG_DEBUG(F("Received %s => %s (length %d)"), topic, message, len);
|
||||
#endif
|
||||
|
||||
// strip out everything until the last /
|
||||
@@ -357,7 +357,7 @@ void Mqtt::on_message(char * topic, char * payload, size_t len) {
|
||||
}
|
||||
|
||||
// if we got here we didn't find a topic match
|
||||
DEBUG_LOG(F("No responding handler found for topic %s"), topic);
|
||||
LOG_DEBUG(F("No responding handler found for topic %s"), topic);
|
||||
}
|
||||
|
||||
// print all the topics related to a specific device_id
|
||||
@@ -382,7 +382,7 @@ void Mqtt::show_topic_handlers(uuid::console::Shell & shell, const uint8_t devic
|
||||
// and always remove from queue
|
||||
void Mqtt::on_publish(uint16_t packetId) {
|
||||
// find the MQTT message in the queue and remove it
|
||||
if ((mqtt_messages_.empty()) || (mqtt_qos_ == 0)) {
|
||||
if (mqtt_messages_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -394,9 +394,9 @@ void Mqtt::on_publish(uint16_t packetId) {
|
||||
}
|
||||
|
||||
if (mqtt_message.packet_id_ == packetId) {
|
||||
DEBUG_LOG(F("Acknowledged PID %d. Removing from queue"), packetId);
|
||||
LOG_DEBUG(F("Acknowledged PID %d. Removing from queue"), packetId);
|
||||
} else {
|
||||
DEBUG_LOG(F("Mismatch, expecting PID %d, got %d"), mqtt_message.packet_id_, packetId);
|
||||
LOG_DEBUG(F("Mismatch, expecting PID %d, got %d"), mqtt_message.packet_id_, packetId);
|
||||
mqtt_publish_fails_++; // increment error count
|
||||
}
|
||||
|
||||
@@ -419,31 +419,26 @@ char * Mqtt::make_topic(char * result, const std::string & topic) {
|
||||
return result;
|
||||
}
|
||||
|
||||
void Mqtt::start() {
|
||||
publish("status", "online", true); // say we're alive to the Last Will topic, with retain on
|
||||
send_start_topic();
|
||||
send_heartbeat(); // send heartbeat if enabled
|
||||
}
|
||||
|
||||
// send online appended with the version information as JSON
|
||||
void Mqtt::send_start_topic() {
|
||||
StaticJsonDocument<90> doc;
|
||||
doc["event"] = "start";
|
||||
doc["version"] = Settings().app_version();
|
||||
#ifndef EMSESP_STANDALONE
|
||||
doc["IP"] = WiFi.localIP().toString();
|
||||
#endif
|
||||
|
||||
publish("info", doc, false); // send with retain off
|
||||
}
|
||||
|
||||
// MQTT onConnect - when a connect is established
|
||||
void Mqtt::on_connect() {
|
||||
DEBUG_LOG(F("MQTT connected"));
|
||||
|
||||
mqtt_reconnect_delay_ = Mqtt::MQTT_RECONNECT_DELAY_MIN;
|
||||
mqtt_last_connection_ = millis();
|
||||
mqtt_connecting_ = false;
|
||||
|
||||
publish("status", "online", true); // say we're alive to the Last Will topic, with retain on
|
||||
|
||||
send_start_topic();
|
||||
|
||||
send_heartbeat(); // send heartbeat if enabled
|
||||
LOG_INFO(F("MQTT connected"));
|
||||
}
|
||||
|
||||
// send periodic MQTT message with system information
|
||||
@@ -462,38 +457,6 @@ void Mqtt::send_heartbeat() {
|
||||
publish("heartbeat", doc, false); // send to MQTT with retain off
|
||||
}
|
||||
|
||||
// add MQTT message to queue, payload is a JSON doc.
|
||||
// NOTE this only prints first 255 chars
|
||||
void Mqtt::queue_publish_message(const std::string & topic, const JsonDocument & payload, const bool retain) {
|
||||
// can't have bogus topics, but empty payloads are ok
|
||||
if (topic.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
// check for empty JSON doc - we don't like those
|
||||
size_t capacity = measureJson(payload);
|
||||
if (capacity <= 3) {
|
||||
// DEBUG_LOG(("Empty JSON payload for topic %s. Skipping"), topic);
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
std::string payload_text;
|
||||
serializeJson(payload, payload_text);
|
||||
|
||||
auto message = std::make_shared<MqttMessage>(Operation::PUBLISH, topic, payload_text, retain);
|
||||
|
||||
// DEBUG_LOG(F("Adding JSON publish message created with topic %s, message %s"), topic, payload_text.c_str());
|
||||
|
||||
// if the queue is full, make room but removing the last one
|
||||
if (mqtt_messages_.size() >= maximum_mqtt_messages_) {
|
||||
mqtt_messages_.pop_front();
|
||||
}
|
||||
|
||||
mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message));
|
||||
}
|
||||
|
||||
// add MQTT message to queue, payload is a string
|
||||
void Mqtt::queue_publish_message(const std::string & topic, const std::string & payload, const bool retain) {
|
||||
// can't have bogus topics, but empty payloads are ok
|
||||
@@ -518,7 +481,7 @@ void Mqtt::queue_subscribe_message(const std::string & topic) {
|
||||
}
|
||||
|
||||
auto message = std::make_shared<MqttMessage>(Operation::SUBSCRIBE, topic, "", false);
|
||||
DEBUG_LOG(F("Adding a subscription for %s"), topic.c_str());
|
||||
LOG_DEBUG(F("Adding a subscription for %s"), topic.c_str());
|
||||
|
||||
// if the queue is full, make room but removing the last one
|
||||
if (mqtt_messages_.size() >= maximum_mqtt_messages_) {
|
||||
@@ -528,24 +491,27 @@ void Mqtt::queue_subscribe_message(const std::string & topic) {
|
||||
mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message));
|
||||
}
|
||||
|
||||
// Publish using the user's custom retain flag
|
||||
void Mqtt::publish(const std::string & topic, const std::string & payload) {
|
||||
publish(topic, payload, mqtt_retain_);
|
||||
}
|
||||
void Mqtt::publish(const std::string & topic, const JsonDocument & payload) {
|
||||
publish(topic, payload, mqtt_retain_);
|
||||
}
|
||||
|
||||
// MQTT Publish, using a specific retain flag
|
||||
void Mqtt::publish(const std::string & topic, const std::string & payload, bool retain) {
|
||||
queue_publish_message(topic, payload, retain);
|
||||
}
|
||||
|
||||
// Publish using the user's custom retain flag
|
||||
void Mqtt::publish(const std::string & topic, const std::string & payload) {
|
||||
publish(topic, payload, mqtt_retain_);
|
||||
}
|
||||
|
||||
void Mqtt::publish(const std::string & topic, const JsonDocument & payload) {
|
||||
publish(topic, payload, mqtt_retain_);
|
||||
}
|
||||
|
||||
void Mqtt::publish(const std::string & topic, const JsonDocument & payload, bool retain) {
|
||||
queue_publish_message(topic, payload, retain);
|
||||
// convert json to string
|
||||
std::string payload_text;
|
||||
serializeJson(payload, payload_text);
|
||||
queue_publish_message(topic, payload_text, retain);
|
||||
}
|
||||
|
||||
// for booleans, which get converted to string values 1 and 0
|
||||
void Mqtt::publish(const std::string & topic, const bool value) {
|
||||
queue_publish_message(topic, value ? "1" : "0", mqtt_retain_);
|
||||
}
|
||||
@@ -576,7 +542,7 @@ void Mqtt::process_queue() {
|
||||
// append the hostname and base to the topic, unless we're doing native HA which has a different format
|
||||
char full_topic[MQTT_TOPIC_MAX_SIZE];
|
||||
|
||||
// if the topic starts with "homeassistant" we leave it untouched, otherwise append ho st and base
|
||||
// if the topic starts with "homeassistant" we leave it untouched, otherwise append host and base
|
||||
if (strncmp(message->topic.c_str(), "homeassistant/", 13) == 0) {
|
||||
strcpy(full_topic, message->topic.c_str());
|
||||
} else {
|
||||
@@ -585,14 +551,14 @@ void Mqtt::process_queue() {
|
||||
|
||||
// if we're subscribing...
|
||||
if (message->operation == Operation::SUBSCRIBE) {
|
||||
DEBUG_LOG(F("Subscribing to topic: %s"), full_topic);
|
||||
LOG_DEBUG(F("Subscribing to topic: %s"), full_topic);
|
||||
#ifndef EMSESP_STANDALONE
|
||||
uint16_t packet_id = mqttClient_.subscribe(full_topic, mqtt_qos_);
|
||||
#else
|
||||
uint16_t packet_id = 1;
|
||||
#endif
|
||||
if (!packet_id) {
|
||||
DEBUG_LOG(F("Error subscribing to %s, error %d"), full_topic, packet_id);
|
||||
LOG_DEBUG(F("Error subscribing to %s, error %d"), full_topic, packet_id);
|
||||
}
|
||||
|
||||
mqtt_messages_.pop_front(); // remove the message from the queue
|
||||
@@ -606,25 +572,26 @@ void Mqtt::process_queue() {
|
||||
return;
|
||||
}
|
||||
|
||||
// else try and publish it
|
||||
// else try and publish it
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
uint16_t packet_id = mqttClient_.publish(full_topic, mqtt_qos_, message->retain, message->payload.c_str());
|
||||
// uint16_t packet_id = mqttClient_.publish(full_topic, mqtt_qos_, message->retain, message->payload.c_str());
|
||||
uint16_t packet_id = mqttClient_.publish(full_topic, mqtt_qos_, message->retain, message->payload.c_str(), message->payload.size(), false, mqtt_message.id_);
|
||||
#else
|
||||
uint16_t packet_id = 1;
|
||||
#endif
|
||||
DEBUG_LOG(F("Publishing topic %s (#%02d, attempt #%d, pid %d)"), full_topic, mqtt_message.id_, mqtt_message.retry_count_ + 1, packet_id);
|
||||
LOG_DEBUG(F("Publishing topic %s (#%02d, attempt #%d, pid %d)"), full_topic, mqtt_message.id_, mqtt_message.retry_count_ + 1, packet_id);
|
||||
|
||||
if (packet_id == 0) {
|
||||
// it failed. if we retried n times, give up. remove from queue
|
||||
if (mqtt_message.retry_count_ == (MQTT_PUBLISH_MAX_RETRY - 1)) {
|
||||
logger_.err(F("Failed to publish to %s after %d attempts"), full_topic, mqtt_message.retry_count_ + 1);
|
||||
LOG_ERROR(F("Failed to publish to %s after %d attempts"), full_topic, mqtt_message.retry_count_ + 1);
|
||||
mqtt_publish_fails_++; // increment failure counter
|
||||
mqtt_messages_.pop_front(); // delete
|
||||
return;
|
||||
} else {
|
||||
mqtt_messages_.front().retry_count_++;
|
||||
// logger_.err(F("Failed to publish to %s. Trying again, #%d"), full_topic, mqtt_message.retry_count_ + 1);
|
||||
DEBUG_LOG(F("Failed to publish to %s. Trying again, #%d"), full_topic, mqtt_message.retry_count_ + 1);
|
||||
LOG_DEBUG(F("Failed to publish to %s. Trying again, #%d"), full_topic, mqtt_message.retry_count_ + 1);
|
||||
return; // leave on queue for next time so it gets republished
|
||||
}
|
||||
}
|
||||
@@ -633,7 +600,7 @@ void Mqtt::process_queue() {
|
||||
// but add the packet_id so we can check it later
|
||||
if (mqtt_qos_ != 0) {
|
||||
mqtt_messages_.front().packet_id_ = packet_id;
|
||||
DEBUG_LOG(F("Setting packetID for ACK to %d"), packet_id);
|
||||
LOG_DEBUG(F("Setting packetID for ACK to %d"), packet_id);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
13
src/mqtt.h
13
src/mqtt.h
@@ -63,6 +63,7 @@ struct MqttMessage {
|
||||
class Mqtt {
|
||||
public:
|
||||
void loop();
|
||||
void start();
|
||||
void send_heartbeat();
|
||||
|
||||
enum Operation { PUBLISH, SUBSCRIBE };
|
||||
@@ -109,32 +110,28 @@ class Mqtt {
|
||||
};
|
||||
static std::deque<QueuedMqttMessage> mqtt_messages_;
|
||||
|
||||
void start();
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
static AsyncMqttClient mqttClient_;
|
||||
#endif
|
||||
|
||||
void flush_message_queue();
|
||||
void setup();
|
||||
|
||||
static constexpr size_t MAX_MQTT_MESSAGES = 50;
|
||||
static size_t maximum_mqtt_messages_;
|
||||
static uint16_t mqtt_message_id_;
|
||||
static bool mqtt_retain_;
|
||||
|
||||
static constexpr uint8_t MQTT_QUEUE_MAX_SIZE = 50;
|
||||
static constexpr uint32_t MQTT_PUBLISH_WAIT = 750; // delay between sending publishes, although it should be asynchronous!
|
||||
static constexpr uint32_t MQTT_PUBLISH_WAIT = 750; // delay between sending publishes, to account for large payloads
|
||||
static constexpr uint8_t MQTT_PUBLISH_MAX_RETRY = 3; // max retries for giving up on publishing
|
||||
static constexpr uint8_t MQTT_KEEP_ALIVE = 60;
|
||||
static constexpr uint8_t MQTT_KEEP_ALIVE = 60; // 60 seconds. This could also be less, like 30 seconds
|
||||
static constexpr uint32_t MQTT_RECONNECT_DELAY_MIN = 2000; // Try to reconnect in 2 seconds upon disconnection
|
||||
static constexpr uint32_t MQTT_RECONNECT_DELAY_STEP = 3000; // Increase the reconnect delay in 3 seconds after each failed attempt
|
||||
static constexpr uint32_t MQTT_RECONNECT_DELAY_MAX = 120000; // Set reconnect time to 2 minutes at most
|
||||
static constexpr uint32_t MQTT_HEARTBEAT_INTERVAL = 60000; // in milliseconds, how often the MQTT heartbeat is sent (1 min)
|
||||
|
||||
static bool mqtt_retain_;
|
||||
|
||||
static void queue_publish_message(const std::string & topic, const JsonDocument & payload, const bool retain);
|
||||
static void queue_publish_message(const std::string & topic, const std::string & payload, const bool retain);
|
||||
|
||||
static void queue_subscribe_message(const std::string & topic);
|
||||
|
||||
void on_publish(uint16_t packetId);
|
||||
|
||||
@@ -36,6 +36,7 @@ void Network::start() {
|
||||
WiFi.persistent(false);
|
||||
WiFi.disconnect(true);
|
||||
WiFi.setAutoReconnect(false);
|
||||
WiFi.mode(WIFI_STA);
|
||||
#endif
|
||||
|
||||
#if defined(ESP8266)
|
||||
@@ -71,7 +72,7 @@ void Network::sta_mode_start(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
|
||||
#if defined(ESP8266)
|
||||
void Network::sta_mode_connected(const WiFiEventStationModeConnected & event) {
|
||||
logger_.info(F("Connected to %s (%02X:%02X:%02X:%02X:%02X:%02X) on channel %u"),
|
||||
LOG_INFO(F("Connected to %s (%02X:%02X:%02X:%02X:%02X:%02X) on channel %u"),
|
||||
event.ssid.c_str(),
|
||||
event.bssid[0],
|
||||
event.bssid[1],
|
||||
@@ -86,7 +87,7 @@ void Network::sta_mode_connected(const WiFiEventStationModeConnected & event) {
|
||||
}
|
||||
#elif defined(ESP32)
|
||||
void Network::sta_mode_connected(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
logger_.info(F("Connected to %s (%02X:%02X:%02X:%02X:%02X:%02X) on channel %u"),
|
||||
LOG_INFO(F("Connected to %s (%02X:%02X:%02X:%02X:%02X:%02X) on channel %u"),
|
||||
info.connected.ssid,
|
||||
info.sta_connected.mac[0],
|
||||
info.sta_connected.mac[1],
|
||||
@@ -107,10 +108,10 @@ void Network::sta_mode_disconnected(const WiFiEventStationModeDisconnected & eve
|
||||
if (event.reason == 201) {
|
||||
if (++disconnect_count_ == 3) {
|
||||
if (System::safe_mode()) {
|
||||
logger_.err(F("Failed to connect to WiFi %s after %d attempts"), event.ssid.c_str(), disconnect_count_ - 1);
|
||||
LOG_ERROR(F("Failed to connect to WiFi %s after %d attempts"), event.ssid.c_str(), disconnect_count_ - 1);
|
||||
disconnect_count_ = 0;
|
||||
} else {
|
||||
logger_.err(F("Failed to connect to WiFi. Rebooting into Safe mode"));
|
||||
LOG_ERROR(F("Failed to connect to WiFi. Rebooting into Safe mode"));
|
||||
System::restart(true); // set safe mode and restart
|
||||
}
|
||||
}
|
||||
@@ -118,20 +119,20 @@ void Network::sta_mode_disconnected(const WiFiEventStationModeDisconnected & eve
|
||||
}
|
||||
#elif defined(ESP32)
|
||||
void Network::sta_mode_disconnected(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
// logger_.err(F("Failed to connect to WiFi %s, reason code %d"), info.disconnected.ssid, info.disconnected.reason);
|
||||
// LOG_ERROR(F("Failed to connect to WiFi %s, reason code %d"), info.disconnected.ssid, info.disconnected.reason);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(ESP8266)
|
||||
void Network::sta_mode_got_ip(const WiFiEventStationModeGotIP & event) {
|
||||
logger_.info(F("Obtained IPv4 address %s/%s and gateway %s"),
|
||||
LOG_INFO(F("Obtained IPv4 address %s/%s and gateway %s"),
|
||||
uuid::printable_to_string(event.ip).c_str(),
|
||||
uuid::printable_to_string(event.mask).c_str(),
|
||||
uuid::printable_to_string(event.gw).c_str());
|
||||
}
|
||||
#elif defined(ESP32)
|
||||
void Network::sta_mode_got_ip(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
logger_.info(F("Obtained IPv4 address %s/%s and gateway %s"),
|
||||
LOG_INFO(F("Obtained IPv4 address %s/%s and gateway %s"),
|
||||
uuid::printable_to_string(IPAddress(info.got_ip.ip_info.ip.addr)).c_str(),
|
||||
uuid::printable_to_string(IPAddress(info.got_ip.ip_info.netmask.addr)).c_str(),
|
||||
uuid::printable_to_string(IPAddress(info.got_ip.ip_info.gw.addr)).c_str());
|
||||
@@ -202,21 +203,21 @@ void Network::ota_setup() {
|
||||
ota_->setPassword(settings.admin_password().c_str());
|
||||
|
||||
ota_->onStart([this]() {
|
||||
DEBUG_LOG(F("OTA starting (send type %d)..."), ota_->getCommand());
|
||||
LOG_DEBUG(F("OTA starting (send type %d)..."), ota_->getCommand());
|
||||
|
||||
// turn off stuff to stop interference
|
||||
EMSuart::stop(); // UART stop
|
||||
in_ota_ = true; // set flag so all other services stop
|
||||
});
|
||||
|
||||
ota_->onEnd([this]() { DEBUG_LOG(F("OTA done, automatically restarting")); });
|
||||
ota_->onEnd([this]() { LOG_DEBUG(F("OTA done, automatically restarting")); });
|
||||
|
||||
ota_->onProgress([this](unsigned int progress, unsigned int total) {
|
||||
/*
|
||||
static unsigned int _progOld;
|
||||
unsigned int _prog = (progress / (total / 100));
|
||||
if (_prog != _progOld) {
|
||||
DEBUG_LOG(F("[OTA] Progress: %u%%"), _prog);
|
||||
LOG_DEBUG(F("[OTA] Progress: %u%%"), _prog);
|
||||
_progOld = _prog;
|
||||
}
|
||||
*/
|
||||
@@ -224,23 +225,23 @@ void Network::ota_setup() {
|
||||
|
||||
ota_->onError([this](ota_error_t error) {
|
||||
if (error == OTA_AUTH_ERROR) {
|
||||
logger_.err(F("[OTA] Auth Failed"));
|
||||
LOG_ERROR(F("[OTA] Auth Failed"));
|
||||
} else if (error == OTA_BEGIN_ERROR) {
|
||||
logger_.err(F("[OTA] Begin Failed"));
|
||||
LOG_ERROR(F("[OTA] Begin Failed"));
|
||||
} else if (error == OTA_CONNECT_ERROR) {
|
||||
logger_.err(F("[OTA] Connect Failed"));
|
||||
LOG_ERROR(F("[OTA] Connect Failed"));
|
||||
} else if (error == OTA_RECEIVE_ERROR) {
|
||||
logger_.err(F("[OTA] Receive Failed"));
|
||||
LOG_ERROR(F("[OTA] Receive Failed"));
|
||||
} else if (error == OTA_END_ERROR) {
|
||||
logger_.err(F("[OTA] End Failed"));
|
||||
LOG_ERROR(F("[OTA] End Failed"));
|
||||
} else {
|
||||
logger_.err(F("[OTA] Error %d"), error);
|
||||
LOG_ERROR(F("[OTA] Error %d"), error);
|
||||
};
|
||||
});
|
||||
|
||||
// start ota service
|
||||
ota_->begin();
|
||||
logger_.info(F("Listening to firmware updates on %s.local:%u"), ota_->getHostname().c_str(), OTA_PORT);
|
||||
LOG_INFO(F("Listening to firmware updates on %s.local:%u"), ota_->getHostname().c_str(), OTA_PORT);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -30,6 +30,23 @@ void Sensors::start() {
|
||||
// copy over values from MQTT so we don't keep on quering the filesystem
|
||||
mqtt_format_ = Settings().mqtt_format();
|
||||
|
||||
// if we're using HA MQTT Discovery, send out the config
|
||||
// currently we just do this for a single sensor (sensor1)
|
||||
if (mqtt_format_ == Settings::MQTT_format::HA) {
|
||||
// Mqtt::publish(topic); // empty payload, this remove any previous config sent to HA
|
||||
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
|
||||
doc["dev_cla"] = "temperature";
|
||||
doc["name"] = "ems-esp-sensor1";
|
||||
doc["uniq_id"] = "ems-esp-sensor1";
|
||||
doc["~"] = "homeassistant/sensor/ems-esp/external";
|
||||
doc["stat_t"] = "~/state";
|
||||
doc["unit_of_meas"] = "°C";
|
||||
doc["val_tpl"] = "{{value_json.sensor1.temp}}";
|
||||
|
||||
Mqtt::publish("homeassistant/sensor/ems-esp/external/config", doc, true); // publish the config payload with retain flag
|
||||
}
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
bus_.begin(SENSOR_GPIO);
|
||||
#endif
|
||||
@@ -39,7 +56,7 @@ void Sensors::loop() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if (state_ == State::IDLE) {
|
||||
if (millis() - last_activity_ >= READ_INTERVAL_MS) {
|
||||
// DEBUG_LOG(F("Read sensor temperature")); // uncomment for debug
|
||||
// LOG_DEBUG(F("Read sensor temperature")); // uncomment for debug
|
||||
if (bus_.reset()) {
|
||||
bus_.skip();
|
||||
bus_.write(CMD_CONVERT_TEMP);
|
||||
@@ -47,28 +64,28 @@ void Sensors::loop() {
|
||||
state_ = State::READING;
|
||||
} else {
|
||||
// no sensors found
|
||||
// logger_.err(F("Bus reset failed")); // uncomment for debug
|
||||
// LOG_ERROR(F("Bus reset failed")); // uncomment for debug
|
||||
devices_.clear(); // remove all know devices incase we have a disconnect
|
||||
}
|
||||
last_activity_ = millis();
|
||||
}
|
||||
} else if (state_ == State::READING) {
|
||||
if (temperature_convert_complete()) {
|
||||
// DEBUG_LOG(F("Scanning for sensors")); // uncomment for debug
|
||||
// LOG_DEBUG(F("Scanning for sensors")); // uncomment for debug
|
||||
bus_.reset_search();
|
||||
found_.clear();
|
||||
|
||||
state_ = State::SCANNING;
|
||||
last_activity_ = millis();
|
||||
} else if (millis() - last_activity_ > READ_TIMEOUT_MS) {
|
||||
logger_.err(F("Sensor read timeout"));
|
||||
LOG_ERROR(F("Sensor read timeout"));
|
||||
|
||||
state_ = State::IDLE;
|
||||
last_activity_ = millis();
|
||||
}
|
||||
} else if (state_ == State::SCANNING) {
|
||||
if (millis() - last_activity_ > SCAN_TIMEOUT_MS) {
|
||||
logger_.err(F("Sensor scan timeout"));
|
||||
LOG_ERROR(F("Sensor scan timeout"));
|
||||
state_ = State::IDLE;
|
||||
last_activity_ = millis();
|
||||
} else {
|
||||
@@ -89,24 +106,24 @@ void Sensors::loop() {
|
||||
/*
|
||||
// comment out for debugging
|
||||
char result[10];
|
||||
DEBUG_LOG(F("Temp of %s = %s"),
|
||||
LOG_DEBUG(F("Temp of %s = %s"),
|
||||
found_.back().to_string().c_str(),
|
||||
Helpers::render_value(result, found_.back().temperature_c_, 2));
|
||||
*/
|
||||
break;
|
||||
|
||||
default:
|
||||
logger_.err(F("Unknown sensor %s"), Device(addr).to_string().c_str());
|
||||
LOG_ERROR(F("Unknown sensor %s"), Device(addr).to_string().c_str());
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
logger_.err(F("Invalid sensor %s"), Device(addr).to_string().c_str());
|
||||
LOG_ERROR(F("Invalid sensor %s"), Device(addr).to_string().c_str());
|
||||
}
|
||||
} else {
|
||||
bus_.depower();
|
||||
devices_ = std::move(found_);
|
||||
found_.clear();
|
||||
// DEBUG_LOG(F("Found %zu sensor(s). Adding them."), devices_.size()); // uncomment for debug
|
||||
// LOG_DEBUG(F("Found %zu sensor(s). Adding them."), devices_.size()); // uncomment for debug
|
||||
state_ = State::IDLE;
|
||||
last_activity_ = millis();
|
||||
}
|
||||
@@ -129,7 +146,7 @@ bool Sensors::temperature_convert_complete() {
|
||||
float Sensors::get_temperature_c(const uint8_t addr[]) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if (!bus_.reset()) {
|
||||
logger_.err(F("Bus reset failed before reading scratchpad from %s"), Device(addr).to_string().c_str());
|
||||
LOG_ERROR(F("Bus reset failed before reading scratchpad from %s"), Device(addr).to_string().c_str());
|
||||
return NAN;
|
||||
}
|
||||
|
||||
@@ -140,12 +157,12 @@ float Sensors::get_temperature_c(const uint8_t addr[]) {
|
||||
bus_.read_bytes(scratchpad, SCRATCHPAD_LEN);
|
||||
|
||||
if (!bus_.reset()) {
|
||||
logger_.err(F("Bus reset failed after reading scratchpad from %s"), Device(addr).to_string().c_str());
|
||||
LOG_ERROR(F("Bus reset failed after reading scratchpad from %s"), Device(addr).to_string().c_str());
|
||||
return NAN;
|
||||
}
|
||||
|
||||
if (bus_.crc8(scratchpad, SCRATCHPAD_LEN - 1) != scratchpad[SCRATCHPAD_LEN - 1]) {
|
||||
logger_.warning(F("Invalid scratchpad CRC: %02X%02X%02X%02X%02X%02X%02X%02X%02X from device %s"),
|
||||
LOG_WARNING(F("Invalid scratchpad CRC: %02X%02X%02X%02X%02X%02X%02X%02X%02X from device %s"),
|
||||
scratchpad[0],
|
||||
scratchpad[1],
|
||||
scratchpad[2],
|
||||
@@ -227,7 +244,7 @@ void Sensors::publish_values() {
|
||||
// if we're not using nested JSON, send each sensor out seperately
|
||||
// sensor1, sensor2 etc...
|
||||
// e.g. sensor_1 = {"temp":20.2}
|
||||
if (mqtt_format_ != Settings::MQTT_format::NESTED) {
|
||||
if (mqtt_format_ == Settings::MQTT_format::SINGLE) {
|
||||
StaticJsonDocument<100> doc;
|
||||
for (const auto & device : devices_) {
|
||||
char s[5];
|
||||
@@ -242,13 +259,14 @@ void Sensors::publish_values() {
|
||||
}
|
||||
|
||||
// group all sensors together - https://github.com/proddy/EMS-ESP/issues/327
|
||||
// https://arduinojson.org/v6/assistant/
|
||||
// This is used for both NESTED and HA modes
|
||||
// sensors = {
|
||||
// "sensor1":{"id":"28-EA41-9497-0E03-5F","temp":"23.25"},
|
||||
// "sensor2":{"id":"28-EA41-9497-0E03-5F","temp":"23.25"},
|
||||
// "sensor3":{"id":"28-EA41-9497-0E03-5F","temp":"23.25"},
|
||||
// "sensor4":{"id":"28-EA41-9497-0E03-5F","temp":"23.25"}
|
||||
// }
|
||||
|
||||
// const size_t capacity = num_devices * JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(num_devices);
|
||||
DynamicJsonDocument doc(100 * num_devices);
|
||||
|
||||
@@ -263,7 +281,11 @@ void Sensors::publish_values() {
|
||||
dataSensor["temp"] = Helpers::render_value(s, device.temperature_c_, 2);
|
||||
}
|
||||
|
||||
if (mqtt_format_ == Settings::MQTT_format::HA) {
|
||||
Mqtt::publish("homeassistant/sensor/ems-esp/external/state", doc);
|
||||
} else {
|
||||
Mqtt::publish("sensors", doc);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -63,7 +63,11 @@ class Sensors {
|
||||
const std::vector<Device> devices() const;
|
||||
|
||||
private:
|
||||
#if defined(ESP8266)
|
||||
static constexpr uint8_t SENSOR_GPIO = 14; // D5
|
||||
#elif defined(ESP32)
|
||||
static constexpr uint8_t SENSOR_GPIO = 18; // Wemos D1-32 for compatibility D5
|
||||
#endif
|
||||
|
||||
enum class State { IDLE, READING, SCANNING };
|
||||
|
||||
|
||||
@@ -139,10 +139,10 @@ Settings::Settings() {
|
||||
if (EMSESP_FS.begin(true)) {
|
||||
#endif
|
||||
#endif
|
||||
logger_.info(F("Mounted filesystem"));
|
||||
LOG_INFO(F("Mounted filesystem"));
|
||||
mounted_ = true;
|
||||
} else {
|
||||
logger_.alert(F("Unable to mount filesystem"));
|
||||
LOG_ERROR(F("Unable to mount filesystem"));
|
||||
unavailable_ = true;
|
||||
}
|
||||
}
|
||||
@@ -154,7 +154,7 @@ Settings::Settings() {
|
||||
}
|
||||
|
||||
if (!loaded_) {
|
||||
logger_.err(F("Failed to load settings. Using defaults"));
|
||||
LOG_ERROR(F("Failed to load settings. Using defaults"));
|
||||
read_settings(ArduinoJson::StaticJsonDocument<0>());
|
||||
loaded_ = true;
|
||||
}
|
||||
@@ -169,7 +169,7 @@ void Settings::commit() {
|
||||
|
||||
EMSuart::stop(); // temporary suspend UART because is can cause interference on the UART
|
||||
|
||||
logger_.debug(F("Saving settings"));
|
||||
LOG_DEBUG(F("Saving settings"));
|
||||
if (write_settings(filename)) {
|
||||
if (read_settings(filename, false)) {
|
||||
write_settings(backup_filename);
|
||||
@@ -190,21 +190,21 @@ bool Settings::read_settings(const std::string & filename, bool load) {
|
||||
auto error = ArduinoJson::deserializeMsgPack(doc, file);
|
||||
|
||||
if (error) {
|
||||
logger_.err(F("Failed to parse settings file %s: %s"), filename.c_str(), error.c_str());
|
||||
LOG_ERROR(F("Failed to parse settings file %s: %s"), filename.c_str(), error.c_str());
|
||||
return false;
|
||||
} else {
|
||||
if (load) {
|
||||
logger_.info(F("Loading settings from file %s"), filename.c_str());
|
||||
LOG_INFO(F("Loading settings from file %s"), filename.c_str());
|
||||
read_settings(doc);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logger_.err(F("Settings file %s does not exist"), filename.c_str());
|
||||
LOG_ERROR(F("Settings file %s does not exist"), filename.c_str());
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
logger_.info(F("Loading settings from file %s (%d)"), filename.c_str(), load);
|
||||
LOG_INFO(F("Loading settings from file %s (%d)"), filename.c_str(), load);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
@@ -221,17 +221,17 @@ bool Settings::write_settings(const std::string & filename) {
|
||||
ArduinoJson::serializeMsgPack(doc, file);
|
||||
|
||||
if (file.getWriteError()) {
|
||||
logger_.alert(F("Failed to write settings file %s: %u"), filename.c_str(), file.getWriteError());
|
||||
LOG_ERROR(F("Failed to write settings file %s: %u"), filename.c_str(), file.getWriteError());
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logger_.alert(F("Unable to open settings file %s for writing"), filename.c_str());
|
||||
LOG_ERROR(F("Unable to open settings file %s for writing"), filename.c_str());
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
logger_.debug(F("Write settings file %s"), filename.c_str());
|
||||
LOG_DEBUG(F("Write settings file %s"), filename.c_str());
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -49,15 +49,15 @@
|
||||
#define EMSESP_DEFAULT_MQTT_ENABLED true
|
||||
#define EMSESP_DEFAULT_MQTT_BASE "home"
|
||||
#define EMSESP_DEFAULT_MQTT_PORT 1883
|
||||
#define EMSESP_DEFAULT_MQTT_QOS 0
|
||||
#define EMSESP_DEFAULT_MQTT_QOS 1
|
||||
#define EMSESP_DEFAULT_MQTT_RETAIN false
|
||||
#define EMSESP_DEFAULT_MQTT_FORMAT 2 // nested
|
||||
#define EMSESP_DEFAULT_MQTT_FORMAT 2 // 2=nested
|
||||
#define EMSESP_DEFAULT_MQTT_HEARTBEAT true
|
||||
#define EMSESP_DEFAULT_EMS_READ_ONLY false
|
||||
#define EMSESP_DEFAULT_SHOWER_TIMER false
|
||||
#define EMSESP_DEFAULT_SHOWER_ALERT false
|
||||
#define EMSESP_DEFAULT_SYSLOG_INTERVAL 0
|
||||
#define EMSESP_DEFAULT_MASTER_THERMOSTAT 0 // not set
|
||||
#define EMSESP_DEFAULT_MASTER_THERMOSTAT 0 // 0=not set
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
#define EMSESP_DEFAULT_MQTT_PUBLISH_TIME 10
|
||||
|
||||
@@ -55,7 +55,7 @@ void Shower::loop() {
|
||||
if (!shower_on_ && (time_now - timer_start_) > SHOWER_MIN_DURATION) {
|
||||
shower_on_ = true;
|
||||
Mqtt::publish("shower_active", (bool)true);
|
||||
DEBUG_LOG(F("[Shower] hot water still running, starting shower timer"));
|
||||
LOG_DEBUG(F("[Shower] hot water still running, starting shower timer"));
|
||||
}
|
||||
// check if the shower has been on too long
|
||||
else if ((((time_now - timer_start_) > SHOWER_MAX_DURATION) && !doing_cold_shot_) && shower_alert_) {
|
||||
@@ -76,7 +76,7 @@ void Shower::loop() {
|
||||
duration_ = (timer_pause_ - timer_start_ - SHOWER_OFFSET_TIME);
|
||||
if (duration_ > SHOWER_MIN_DURATION) {
|
||||
Mqtt::publish("shower_active", (bool)false);
|
||||
DEBUG_LOG(F("[Shower] finished with duration %d"), duration_);
|
||||
LOG_DEBUG(F("[Shower] finished with duration %d"), duration_);
|
||||
publish_values();
|
||||
}
|
||||
}
|
||||
@@ -94,7 +94,7 @@ void Shower::loop() {
|
||||
// turn back on the hot water for the shower
|
||||
void Shower::shower_alert_stop() {
|
||||
if (doing_cold_shot_) {
|
||||
DEBUG_LOG(F("Shower Alert stopped"));
|
||||
LOG_DEBUG(F("Shower Alert stopped"));
|
||||
// Boiler::set_tapwarmwater_activated(true);
|
||||
doing_cold_shot_ = false;
|
||||
// showerColdShotStopTimer.detach(); // disable the timer
|
||||
@@ -104,7 +104,7 @@ void Shower::shower_alert_stop() {
|
||||
// turn off hot water to send a shot of cold
|
||||
void Shower::shower_alert_start() {
|
||||
if (shower_alert_) {
|
||||
DEBUG_LOG(F("Shower Alert started!"));
|
||||
LOG_DEBUG(F("Shower Alert started!"));
|
||||
// Boiler::set_tapwarmwater_activated(false);
|
||||
doing_cold_shot_ = true;
|
||||
// start the timer for n seconds which will reset the water back to hot
|
||||
|
||||
@@ -29,7 +29,7 @@ uuid::log::Logger Solar::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
|
||||
Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
|
||||
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
|
||||
DEBUG_LOG(F("Registering new Solar module with device ID 0x%02X"), device_id);
|
||||
LOG_DEBUG(F("Registering new Solar module with device ID 0x%02X"), device_id);
|
||||
|
||||
// telegram handlers
|
||||
register_telegram_type(0x0097, F("SM10Monitor"), true, std::bind(&Solar::process_SM10Monitor, this, _1));
|
||||
@@ -108,7 +108,7 @@ void Solar::publish_values() {
|
||||
}
|
||||
|
||||
#ifdef EMSESP_DEBUG
|
||||
DEBUG_LOG(F("[DEBUG] Performing a solar module publish"));
|
||||
LOG_DEBUG(F("[DEBUG] Performing a solar module publish"));
|
||||
#endif
|
||||
|
||||
Mqtt::publish("sm_data", doc);
|
||||
|
||||
@@ -60,7 +60,7 @@ void System::mqtt_commands(const char * message) {
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
|
||||
DeserializationError error = deserializeJson(doc, message);
|
||||
if (error) {
|
||||
DEBUG_LOG(F("MQTT error: payload %s, error %s"), message, error.c_str());
|
||||
LOG_DEBUG(F("MQTT error: payload %s, error %s"), message, error.c_str());
|
||||
return;
|
||||
}
|
||||
const char * command = doc["cmd"];
|
||||
@@ -91,9 +91,9 @@ void System::restart(bool mode) {
|
||||
|
||||
// check for safe mode
|
||||
if (mode) {
|
||||
logger_.notice("Restarting system in safe mode...");
|
||||
LOG_NOTICE("Restarting system in safe mode...");
|
||||
} else {
|
||||
logger_.notice("Restarting system...");
|
||||
LOG_NOTICE("Restarting system...");
|
||||
}
|
||||
|
||||
Shell::loop_all();
|
||||
@@ -140,9 +140,11 @@ void System::start() {
|
||||
settings.app_version(EMSESP_APP_VERSION);
|
||||
settings.commit();
|
||||
|
||||
logger_.info(F("System booted (EMS-ESP version %s)"), settings.app_version().c_str());
|
||||
LOG_INFO(F("System booted (EMS-ESP version %s)"), settings.app_version().c_str());
|
||||
|
||||
pinMode(LED_GPIO, OUTPUT); // LED pin
|
||||
if (LED_GPIO) {
|
||||
pinMode(LED_GPIO, OUTPUT); // LED pin, 0 is disabled
|
||||
}
|
||||
|
||||
// register MQTT system commands
|
||||
Mqtt::subscribe("cmd", std::bind(&System::mqtt_commands, this, _1));
|
||||
@@ -218,19 +220,11 @@ void System::loop() {
|
||||
syslog_.loop();
|
||||
#endif
|
||||
|
||||
if (LED_GPIO) {
|
||||
led_monitor(); // check status and report back using the LED
|
||||
}
|
||||
|
||||
system_check(); // check system health
|
||||
|
||||
/*
|
||||
#ifdef EMSESP_DEBUG
|
||||
static uint32_t last_debug_ = 0;
|
||||
if (millis() - last_debug_ >= 5000) {
|
||||
last_debug_ = millis();
|
||||
show_mem("loop");
|
||||
}
|
||||
#endif
|
||||
*/
|
||||
}
|
||||
|
||||
void System::show_mem(const char * text) {
|
||||
@@ -239,7 +233,7 @@ void System::show_mem(const char * text) {
|
||||
#else
|
||||
uint32_t mem = 1000;
|
||||
#endif
|
||||
logger_.notice(F("{%s} Free mem: %ld (%d%%)"), text, mem, (100 * mem / heap_start_));
|
||||
LOG_NOTICE(F("{%s} Free mem: %ld (%d%%)"), text, mem, (100 * mem / heap_start_));
|
||||
}
|
||||
|
||||
// sets rate of led flash
|
||||
@@ -272,10 +266,12 @@ void System::system_check() {
|
||||
// if it was unhealthy but now we're better, make sure the LED is solid again cos we've been healed
|
||||
if (!system_healthy_) {
|
||||
system_healthy_ = true;
|
||||
if (LED_GPIO) {
|
||||
digitalWrite(LED_GPIO, LED_ON); // LED on, for ever
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// flashes the LED
|
||||
|
||||
@@ -60,7 +60,7 @@ class System {
|
||||
|
||||
static void restart(bool safe_mode);
|
||||
static void restart() {
|
||||
restart(false); // no safe mode
|
||||
restart(false); // default, don't boot into safe mode
|
||||
}
|
||||
|
||||
static void show_mem(const char * text);
|
||||
@@ -83,8 +83,8 @@ class System {
|
||||
static constexpr uint8_t LED_GPIO = 2;
|
||||
static constexpr uint8_t LED_ON = LOW;
|
||||
#elif defined(ESP32)
|
||||
static constexpr uint8_t LED_GPIO = 5; // on Wemos D32
|
||||
static constexpr uint8_t LED_ON = LOW;
|
||||
static constexpr uint8_t LED_GPIO = 5; // 5 on Lolin D32, 2 on Wemos D1-32 mini. Use 0 for off.
|
||||
static constexpr uint8_t LED_ON = LOW; // LOW on Lolin D32, HIGH on Wemos D1-32 mini
|
||||
#else
|
||||
static constexpr uint8_t LED_GPIO = 0;
|
||||
static constexpr uint8_t LED_ON = 0;
|
||||
@@ -106,6 +106,7 @@ class System {
|
||||
static int reset_counter_;
|
||||
|
||||
static EMSuart emsuart_;
|
||||
|
||||
#if defined(ESP8266)
|
||||
static RTCVars state_;
|
||||
#endif
|
||||
|
||||
@@ -202,7 +202,7 @@ void RxService::flush_rx_queue() {
|
||||
|
||||
// start and initialize the Rx incoming buffer. Not currently used.
|
||||
void RxService::start() {
|
||||
// DEBUG_LOG(F("RxStart"));
|
||||
// LOG_DEBUG(F("RxStart"));
|
||||
}
|
||||
|
||||
// Rx loop, run as many times as you can
|
||||
@@ -240,7 +240,7 @@ void RxService::add(uint8_t * data, uint8_t length) {
|
||||
// validate the CRC
|
||||
uint8_t crc = calculate_crc(data, length - 1);
|
||||
if (data[length - 1] != crc) {
|
||||
TRACE_LOG(F("Rx: %s %s(BAD, CRC %02X != %02X)%s"), Helpers::data_to_hex(data, length).c_str(), COLOR_RED, data[length - 1], crc, COLOR_RESET);
|
||||
LOG_TRACE(F("Rx: %s %s(BAD, CRC %02X != %02X)%s"), Helpers::data_to_hex(data, length).c_str(), COLOR_RED, data[length - 1], crc, COLOR_RESET);
|
||||
increment_telegram_error_count();
|
||||
return;
|
||||
}
|
||||
@@ -253,9 +253,9 @@ void RxService::add(uint8_t * data, uint8_t length) {
|
||||
ems_mask(data[0]);
|
||||
}
|
||||
|
||||
// if we're in read only mode, just dump out to console
|
||||
if (EMSESP::ems_read_only()) {
|
||||
TRACE_LOG(F("Rx: %s"), Helpers::data_to_hex(data, length).c_str());
|
||||
// if we're in "trace" and "raw" print out actual telegram
|
||||
if (logger_.enabled(Level::TRACE) && EMSESP::trace_raw()) {
|
||||
LOG_TRACE(F("Rx: %s"), Helpers::data_to_hex(data, length).c_str());
|
||||
}
|
||||
|
||||
// src, dest and offset are always in fixed positions
|
||||
@@ -307,7 +307,7 @@ void RxService::add(uint8_t * data, uint8_t length) {
|
||||
}
|
||||
|
||||
// add to queue, with timestamp
|
||||
DEBUG_LOG(F("New Rx [#%d] telegram added, length %d"), rx_telegram_id_, message_length);
|
||||
LOG_DEBUG(F("New Rx [#%d] telegram, length %d"), rx_telegram_id_, message_length);
|
||||
rx_telegrams_.emplace_back(rx_telegram_id_++, std::move(telegram));
|
||||
}
|
||||
|
||||
@@ -325,7 +325,7 @@ void TxService::flush_tx_queue() {
|
||||
|
||||
// start and initialize Tx
|
||||
void TxService::start() {
|
||||
// DEBUG_LOG(F("TxStart()"));
|
||||
// LOG_DEBUG(F("TxStart()"));
|
||||
|
||||
// grab the bus ID
|
||||
Settings settings;
|
||||
@@ -431,11 +431,12 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
|
||||
|
||||
length++; // add one since we want to now include the CRC
|
||||
|
||||
DEBUG_LOG(F("Sending %s Tx [#%d], telegram: %s"),
|
||||
LOG_DEBUG(F("Sending %s Tx [#%d], telegram: %s"),
|
||||
(telegram->operation == Telegram::Operation::TX_WRITE) ? F("write") : F("read"),
|
||||
tx_telegram.id_,
|
||||
telegram->to_string(telegram_raw, length).c_str());
|
||||
|
||||
// if we're watching an ID, then always show
|
||||
if ((logger_.enabled(Level::TRACE))
|
||||
&& ((telegram->src == EMSESP::trace_watch_id()) || (telegram->dest == EMSESP::trace_watch_id()) || (telegram->type_id == EMSESP::trace_watch_id()))) {
|
||||
logger_.trace(F("Sending %s Tx [#%d], telegram: %s"),
|
||||
@@ -448,7 +449,7 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
|
||||
EMSUART_STATUS status = EMSuart::transmit(telegram_raw, length);
|
||||
|
||||
if (status != EMS_TX_STATUS_OK) {
|
||||
logger_.err(F("Failed to transmit Tx via UART. Error: %s"), status == EMS_TX_WTD_TIMEOUT ? F("Timeout") : F("BRK"));
|
||||
LOG_ERROR(F("Failed to transmit Tx via UART. Error: %s"), status == EMS_TX_WTD_TIMEOUT ? F("Timeout") : F("BRK"));
|
||||
}
|
||||
|
||||
tx_waiting(true); // tx now in a wait state
|
||||
@@ -465,12 +466,12 @@ void TxService::send_telegram(const uint8_t * data, const uint8_t length) {
|
||||
}
|
||||
telegram_raw[length] = calculate_crc(telegram_raw, length); // apppend CRC
|
||||
|
||||
DEBUG_LOG(F("Sending Raw telegram: %s (length=%d)"), Helpers::data_to_hex(telegram_raw, length).c_str(), length);
|
||||
LOG_DEBUG(F("Sending Raw telegram: %s (length=%d)"), Helpers::data_to_hex(telegram_raw, length).c_str(), length);
|
||||
|
||||
// send the telegram to the UART Tx
|
||||
EMSUART_STATUS status = EMSuart::transmit(telegram_raw, length);
|
||||
if (status != EMS_TX_STATUS_OK) {
|
||||
logger_.err(F("Failed to transmit Tx via UART. Error: %s"), status == EMS_TX_WTD_TIMEOUT ? F("Timeout") : F("BRK"));
|
||||
LOG_ERROR(F("Failed to transmit Tx via UART. Error: %s"), status == EMS_TX_WTD_TIMEOUT ? F("Timeout") : F("BRK"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,7 +479,7 @@ void TxService::send_telegram(const uint8_t * data, const uint8_t length) {
|
||||
// given some details like the destination, type, offset and message block
|
||||
void TxService::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) {
|
||||
auto telegram = std::make_shared<Telegram>(operation, ems_bus_id(), dest, type_id, offset, message_data, message_length);
|
||||
DEBUG_LOG(F("New Tx [#%d] telegram added, length %d"), tx_telegram_id_, message_length);
|
||||
LOG_DEBUG(F("New Tx [#%d] telegram, length %d"), tx_telegram_id_, message_length);
|
||||
|
||||
// if the queue is full, make room but removing the last one
|
||||
if (tx_telegrams_.size() >= maximum_tx_telegrams_) {
|
||||
@@ -493,7 +494,7 @@ void TxService::add(const uint8_t operation, const uint8_t dest, const uint16_t
|
||||
void TxService::add(uint8_t * data, const uint8_t length) {
|
||||
uint8_t message_length = length - 4;
|
||||
if (!message_length) {
|
||||
logger_.err(F("Bad Tx telegram, too short (message length is %d)"), message_length);
|
||||
LOG_ERROR(F("Bad Tx telegram, too short (message length is %d)"), message_length);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -511,13 +512,13 @@ void TxService::add(uint8_t * data, const uint8_t length) {
|
||||
tx_telegrams_.pop_front();
|
||||
}
|
||||
|
||||
DEBUG_LOG(F("New Tx [#%d] telegram added, length %d"), tx_telegram_id_, message_length);
|
||||
LOG_DEBUG(F("New Tx [#%d] telegram, length %d"), tx_telegram_id_, message_length);
|
||||
tx_telegrams_.emplace_back(tx_telegram_id_++, std::move(telegram));
|
||||
}
|
||||
|
||||
// send a Tx telegram to request data from an EMS device
|
||||
void TxService::read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset) {
|
||||
DEBUG_LOG(F("Tx read request to device 0x%02X for type ID 0x%02X"), dest, type_id);
|
||||
LOG_DEBUG(F("Tx read request to device 0x%02X for type ID 0x%02X"), dest, type_id);
|
||||
|
||||
uint8_t message_data[1] = {EMS_MAX_TELEGRAM_LENGTH}; // request all data, 32 bytes
|
||||
add(Telegram::Operation::TX_READ, dest, type_id, offset, message_data, 1);
|
||||
@@ -582,7 +583,7 @@ uint8_t TxService::retry_tx() {
|
||||
// and incoming Rx dest must be us (our ems_bus_id)
|
||||
// for both src and dest we strip the MSB 8th bit
|
||||
bool TxService::is_last_tx(const uint8_t src, const uint8_t dest) const {
|
||||
// DEBUG_LOG(F("Comparing %02X=%02X , %02X,%02X"), (telegram_last_[1] & 0x7F), (src & 0x7F), (dest & 0x7F), ems_bus_id());
|
||||
// LOG_DEBUG(F("Comparing %02X=%02X , %02X,%02X"), (telegram_last_[1] & 0x7F), (src & 0x7F), (dest & 0x7F), ems_bus_id());
|
||||
return (((telegram_last_[1] & 0x7F) == (src & 0x7F)) && ((dest & 0x7F) == ems_bus_id()));
|
||||
}
|
||||
|
||||
@@ -590,7 +591,7 @@ bool TxService::is_last_tx(const uint8_t src, const uint8_t dest) const {
|
||||
void TxService::post_send_query() {
|
||||
if (telegram_last_post_send_query_) {
|
||||
uint8_t dest = (telegram_last_[1] & 0x7F);
|
||||
DEBUG_LOG(F("Sending post validate read, type ID 0x%02X to dest 0x%02X"), telegram_last_post_send_query_, dest);
|
||||
LOG_DEBUG(F("Sending post validate read, type ID 0x%02X to dest 0x%02X"), telegram_last_post_send_query_, dest);
|
||||
|
||||
read_request(telegram_last_post_send_query_, dest, 0); // no offset
|
||||
}
|
||||
|
||||
@@ -130,10 +130,10 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
|
||||
if (((num_devices == 1) && (actual_master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT) && ((device_id == 0x10) || (device_id == 0x17)))
|
||||
|| (master_thermostat == device_id)) {
|
||||
EMSESP::actual_master_thermostat(device_id);
|
||||
DEBUG_LOG(F("Registering new thermostat with device ID 0x%02X (as the master)"), device_id);
|
||||
LOG_DEBUG(F("Registering new thermostat with device ID 0x%02X (as the master)"), device_id);
|
||||
init_mqtt();
|
||||
} else {
|
||||
DEBUG_LOG(F("Registering new thermostat with device ID 0x%02X"), device_id);
|
||||
LOG_DEBUG(F("Registering new thermostat with device ID 0x%02X"), device_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,29 +145,32 @@ void Thermostat::init_mqtt() {
|
||||
// for each of the heating circuits
|
||||
if (mqtt_format_ == Settings::MQTT_format::HA) {
|
||||
for (uint8_t hc = 0; hc < monitor_typeids.size(); hc++) {
|
||||
std::string topic(100, '\0'); // e.g homeassistant/climate/hc1/thermostat/config
|
||||
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/hc%d/thermostat/config"), hc + 1);
|
||||
|
||||
// Mqtt::publish(topic.c_str()); // empty payload, this remove any previous config sent to HA
|
||||
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
|
||||
std::string payload(100, '\0');
|
||||
snprintf_P(&payload[0], payload.capacity() + 1, PSTR("thermostat_hc%d"), hc + 1);
|
||||
|
||||
doc["name"] = payload; // "name": "thermostat_hc1"
|
||||
doc["unique_id"] = payload; // "unique_id": "thermostat_hc1"
|
||||
|
||||
snprintf_P(&payload[0], payload.capacity() + 1, PSTR("homeassistant/climate/hc%d/thermostat"), hc + 1);
|
||||
doc["~"] = payload; // "homeassistant/climate/hc1/thermostat"
|
||||
std::string hc_text(10, '\0');
|
||||
snprintf_P(&hc_text[0], hc_text.capacity() + 1, PSTR("hc%d"), hc + 1);
|
||||
doc["name"] = hc_text;
|
||||
doc["uniq_id"] = hc_text;
|
||||
|
||||
doc["~"] = "homeassistant/climate/ems-esp";
|
||||
doc["mode_cmd_t"] = "~/cmd_mode";
|
||||
doc["mode_stat_t"] = "~/state";
|
||||
doc["mode_stat_tpl"] = "{{value_json.mode}}";
|
||||
doc["temp_cmd_t"] = "~/cmd_temp";
|
||||
doc["temp_stat_t"] = "~/state";
|
||||
doc["temp_stat_tpl"] = "{{value_json.seltemp}}";
|
||||
doc["curr_temp_t"] = "~/state";
|
||||
doc["curr_temp_tpl"] = "{{value_json.currtemp}}";
|
||||
|
||||
std::string mode_str(30, '\0');
|
||||
snprintf_P(&mode_str[0], 30, PSTR("{{value_json.hc%d.mode}}"), hc + 1);
|
||||
doc["mode_stat_tpl"] = mode_str;
|
||||
|
||||
std::string seltemp_str(30, '\0');
|
||||
snprintf_P(&seltemp_str[0], 30, PSTR("{{value_json.hc%d.seltemp}}"), hc + 1);
|
||||
doc["temp_stat_tpl"] = seltemp_str;
|
||||
|
||||
std::string currtemp_str(30, '\0');
|
||||
snprintf_P(&currtemp_str[0], 30, PSTR("{{value_json.hc%d.currtemp}}"), hc + 1);
|
||||
doc["curr_temp_tpl"] = currtemp_str;
|
||||
|
||||
doc["min_temp"] = "5";
|
||||
doc["max_temp"] = "40";
|
||||
doc["temp_step"] = "0.5";
|
||||
@@ -177,7 +180,10 @@ void Thermostat::init_mqtt() {
|
||||
modes.add("heat");
|
||||
modes.add("auto");
|
||||
|
||||
Mqtt::publish(topic.c_str(), doc, true); // publish the config payload with retain flag
|
||||
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 + 1);
|
||||
// Mqtt::publish(topic); // empty payload, this remove any previous config sent to HA
|
||||
Mqtt::publish(topic, doc, true); // publish the config payload with retain flag
|
||||
|
||||
// subscribe to the temp and mode commands
|
||||
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/hc%d/thermostat/cmd_temp"), hc + 1);
|
||||
@@ -212,7 +218,7 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
|
||||
DeserializationError error = deserializeJson(doc, message);
|
||||
if (error) {
|
||||
DEBUG_LOG(F("MQTT error: payload %s, error %s"), message, error.c_str());
|
||||
LOG_DEBUG(F("MQTT error: payload %s, error %s"), message, error.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -342,7 +348,7 @@ void Thermostat::publish_values() {
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUG_LOG(F("Performing a thermostat publish (device ID 0x%02X)"), device_id());
|
||||
LOG_DEBUG(F("Performing a thermostat publish (device ID 0x%02X)"), device_id());
|
||||
|
||||
uint8_t flags = (this->flags() & 0x0F); // specific thermostat characteristics, stripping the option bits
|
||||
bool has_data = false;
|
||||
@@ -371,8 +377,8 @@ void Thermostat::publish_values() {
|
||||
}
|
||||
|
||||
has_data = true;
|
||||
// if the MQTT format is 'nested' then create the parent object hc<n>
|
||||
if (mqtt_format_ == Settings::MQTT_format::NESTED) {
|
||||
// if the MQTT format is 'nested' or 'ha' then create the parent object hc<n>
|
||||
if (mqtt_format_ != Settings::MQTT_format::SINGLE) {
|
||||
// create nested json for each HC
|
||||
char hc_name[10]; // hc{1-4}
|
||||
strlcpy(hc_name, "hc", 10);
|
||||
@@ -459,9 +465,7 @@ void Thermostat::publish_values() {
|
||||
dataThermostat["modetype"] = mode_tostring(hc->get_mode_type(flags));
|
||||
}
|
||||
|
||||
|
||||
// if format is single, send immediately
|
||||
// if its HA send it to the special topic
|
||||
// if format is single, send immediately and quit
|
||||
if (mqtt_format_ == Settings::MQTT_format::SINGLE) {
|
||||
char topic[30];
|
||||
char s[3]; // for formatting strings
|
||||
@@ -469,17 +473,18 @@ void Thermostat::publish_values() {
|
||||
strlcat(topic, Helpers::itoa(s, hc->hc_num()), 30); // append hc to topic
|
||||
Mqtt::publish(topic, doc);
|
||||
return;
|
||||
} else if (mqtt_format_ == Settings::MQTT_format::HA) {
|
||||
std::string topic(100, '\0');
|
||||
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/hc%d/thermostat/state"), hc->hc_num());
|
||||
Mqtt::publish(topic.c_str(), doc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_data) {
|
||||
return; // nothing to send
|
||||
}
|
||||
|
||||
// if we're using nested json, send all in one go
|
||||
if ((mqtt_format_ == Settings::MQTT_format::NESTED) && has_data) {
|
||||
if (mqtt_format_ == Settings::MQTT_format::NESTED) {
|
||||
Mqtt::publish("thermostat_data", doc);
|
||||
} else if (mqtt_format_ == Settings::MQTT_format::HA) {
|
||||
Mqtt::publish("homeassistant/climate/ems-esp/state", doc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -998,19 +1003,19 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
|
||||
|
||||
// 0xA5 - Set the display settings
|
||||
void Thermostat::set_settings_display(const uint8_t ds) {
|
||||
logger_.info(F("Setting display to %d"), ds);
|
||||
LOG_INFO(F("Setting display to %d"), ds);
|
||||
write_command(EMS_TYPE_IBASettings, 0, ds);
|
||||
}
|
||||
|
||||
// 0xA5 - Set the building settings
|
||||
void Thermostat::set_settings_building(const uint8_t bg) {
|
||||
logger_.info(F("Setting building to %d"), bg);
|
||||
LOG_INFO(F("Setting building to %d"), bg);
|
||||
write_command(EMS_TYPE_IBASettings, 6, bg);
|
||||
}
|
||||
|
||||
// 0xA5 Set the language settings
|
||||
void Thermostat::set_settings_language(const uint8_t lg) {
|
||||
logger_.info(F("Setting building to %d"), lg);
|
||||
LOG_INFO(F("Setting building to %d"), lg);
|
||||
write_command(EMS_TYPE_IBASettings, 1, lg);
|
||||
}
|
||||
|
||||
@@ -1037,21 +1042,21 @@ void Thermostat::set_mode(const std::string & mode, const uint8_t hc_num) {
|
||||
} else if (mode_tostring(HeatingCircuit::Mode::COMFORT) == mode) {
|
||||
set_mode(HeatingCircuit::Mode::COMFORT, hc_num);
|
||||
} else {
|
||||
logger_.warning(F("Invalid mode %s. Cannot set"), mode.c_str());
|
||||
LOG_WARNING(F("Invalid mode %s. Cannot set"), mode.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Set the thermostat working mode
|
||||
void Thermostat::set_mode(const uint8_t mode, const uint8_t hc_num) {
|
||||
if (can_write()) {
|
||||
logger_.warning(F("Write not supported for this model Thermostat"));
|
||||
LOG_WARNING(F("Write not supported for this model Thermostat"));
|
||||
return;
|
||||
}
|
||||
|
||||
// get hc based on number
|
||||
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num);
|
||||
if (hc == nullptr) {
|
||||
logger_.warning(F("set mode: Heating Circuit %d not found or activated"), hc_num);
|
||||
LOG_WARNING(F("set mode: Heating Circuit %d not found or activated"), hc_num);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1127,7 +1132,7 @@ void Thermostat::set_mode(const uint8_t mode, const uint8_t hc_num) {
|
||||
break;
|
||||
}
|
||||
|
||||
logger_.info(F("Setting thermostat mode to %s for heating circuit %d"), mode_tostring(mode).c_str(), hc->hc_num());
|
||||
LOG_INFO(F("Setting thermostat mode to %s for heating circuit %d"), mode_tostring(mode).c_str(), hc->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
|
||||
@@ -1138,14 +1143,14 @@ void Thermostat::set_mode(const uint8_t mode, const uint8_t hc_num) {
|
||||
// Set the temperature of the thermostat
|
||||
void Thermostat::set_temperature(const float temperature, const uint8_t mode, const uint8_t hc_num) {
|
||||
if (can_write()) {
|
||||
logger_.warning(F("Write not supported for this model Thermostat"));
|
||||
LOG_WARNING(F("Write not supported for this model Thermostat"));
|
||||
return;
|
||||
}
|
||||
|
||||
// get hc based on number
|
||||
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num);
|
||||
if (hc == nullptr) {
|
||||
logger_.warning(F("set temperature: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, device_id());
|
||||
LOG_WARNING(F("set temperature: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, device_id());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1244,7 +1249,7 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co
|
||||
// if we know what to send and to where, go and do it
|
||||
if (offset != -1) {
|
||||
char s[10];
|
||||
logger_.info(F("Setting thermostat temperature to %s for heating circuit %d, mode %s"),
|
||||
LOG_INFO(F("Setting thermostat temperature to %s for heating circuit %d, mode %s"),
|
||||
Helpers::render_value(s, temperature, 2),
|
||||
hc->hc_num(),
|
||||
mode_tostring(mode).c_str());
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* ESP32 UART port by Arwed Richert @ArwedL */
|
||||
/*
|
||||
* ESP32 UART port by @ArwedL and improved by @MichaelDvP. See https://github.com/proddy/EMS-ESP/issues/380
|
||||
*/
|
||||
|
||||
#if defined(ESP32)
|
||||
|
||||
@@ -26,19 +28,20 @@
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
static intr_handle_t uart_handle;
|
||||
static RingbufHandle_t buf_handle = NULL;
|
||||
static QueueHandle_t uart_queue = NULL;
|
||||
static volatile uint8_t sending = 0; // If a telegram is send we ++, in receiving we ignore if != 0 and --
|
||||
uint8_t tx_mode_ = EMS_TXMODE_DEFAULT;
|
||||
static uint8_t rxbuf[UART_FIFO_LEN];
|
||||
static uint8_t rxlen;
|
||||
|
||||
// Main interrupt handler
|
||||
void EMSuart::emsuart_parseTask(void * param) {
|
||||
for (;;) {
|
||||
/*
|
||||
* Task to handle the incoming data
|
||||
*/
|
||||
void EMSuart::emsuart_recvTask(void * param) {
|
||||
while (1) {
|
||||
size_t item_size;
|
||||
uint8_t * telegram = (uint8_t *)xRingbufferReceive(buf_handle, &item_size, pdMS_TO_TICKS(1000));
|
||||
uint8_t telegramSize = item_size; // can't be more than EMS_MAXBUFFERSIZE size (checked in sending task)
|
||||
uint8_t * telegram = (uint8_t *)xRingbufferReceive(buf_handle, &item_size, portMAX_DELAY);
|
||||
uint8_t telegramSize = item_size;
|
||||
|
||||
// Did we had a timeout?
|
||||
if (telegram) {
|
||||
EMSESP::incoming_telegram(telegram, telegramSize);
|
||||
vRingbufferReturnItem(buf_handle, (void *)telegram);
|
||||
@@ -47,113 +50,90 @@ void EMSuart::emsuart_parseTask(void * param) {
|
||||
}
|
||||
|
||||
/*
|
||||
* system task triggered on BRK interrupt
|
||||
* incoming received messages are always asynchronous
|
||||
* The full buffer is sent to the ems_parseTelegram() function in ems.cpp.
|
||||
* UART interrupt, on break read the fifo and put the whole telegram to ringbuffer
|
||||
*/
|
||||
void EMSuart::emsuart_recvTask(void * param) {
|
||||
uart_event_t event;
|
||||
for (;;) {
|
||||
if (xQueueReceive(uart_queue, (void *)&event, (portTickType)portMAX_DELAY)) {
|
||||
// We are only interested in UART_BREAK event and ignore all others
|
||||
if (event.type == UART_BREAK) {
|
||||
// If we didn't send it previously it is a newly received telegram
|
||||
if (sending == 0) {
|
||||
// Read all bytes which are already in the buffer
|
||||
uint8_t data[EMSUART_RXBUFSIZE];
|
||||
int length = 0;
|
||||
ESP_ERROR_CHECK(uart_get_buffered_data_len(EMSUART_UART, (size_t *)&length));
|
||||
if (length > EMSUART_RXBUFSIZE) // pedantic check - should never be the case because the receive buffer is of size EMSUART_RXBUFSIZE
|
||||
{
|
||||
length = EMSUART_RXBUFSIZE;
|
||||
}
|
||||
length = uart_read_bytes(EMSUART_UART, data, length, 0); // 0 --> we don't want to wait
|
||||
// we will have runtime performance penalty with impact because of parseTelegram
|
||||
// --> send the data to a 2nd task --> use a ringbuffer to exchange data between both tasks --> no need for pEMSRxBuf or alike
|
||||
// this 2nd tasks processes the message with parseTelegram --> our receiving task is still responsiveness
|
||||
// already do some validty checking here before informing the 2nd task
|
||||
if ((length == 2) || ((length > 4) && (length <= EMS_MAXBUFFERSIZE + 1))) {
|
||||
xRingbufferSend(buf_handle, data, length - 1, 0); // we are not waiting if ringbuffer is full --> just loose telegrams pdMS_TO_TICKS(1000)
|
||||
}
|
||||
} else {
|
||||
--sending;
|
||||
}
|
||||
static void IRAM_ATTR uart_intr_handle(void * arg) {
|
||||
if (EMS_UART.int_st.brk_det) {
|
||||
uint8_t rx_fifo_len = EMS_UART.status.rxfifo_cnt;
|
||||
for (rxlen = 0; rxlen < rx_fifo_len; rxlen++) {
|
||||
rxbuf[rxlen] = EMS_UART.fifo.rw_byte; // read all bytes into buffer
|
||||
}
|
||||
if ((rxlen == 2) || ((rxlen > 4) && (rxlen <= EMS_MAXBUFFERSIZE))) {
|
||||
int baseType = 0;
|
||||
xRingbufferSendFromISR(buf_handle, rxbuf, rxlen - 1, &baseType);
|
||||
}
|
||||
EMS_UART.int_clr.brk_det = 1; // clear flag
|
||||
EMS_UART.conf0.txd_brk = 0; // if it was break from sending, clear bit
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* init UART0 driver
|
||||
* init UART driver
|
||||
*/
|
||||
void EMSuart::start(uint8_t tx_mode) {
|
||||
|
||||
tx_mode_ = tx_mode;
|
||||
|
||||
// Configure UART parameters
|
||||
// clang-format off
|
||||
uart_config_t uart_config = {
|
||||
.baud_rate = EMSUART_BAUD,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
//.rx_flow_ctrl_thresh = 0, // UART HW RTS threshold should not be important
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
ESP_ERROR_CHECK(uart_param_config(EMSUART_UART, &uart_config));
|
||||
|
||||
// Set UART pins(TX: IO16 (UART2 default), RX: IO17 (UART2 default), RTS: IO18, CTS: IO19)
|
||||
ESP_ERROR_CHECK(uart_set_pin(EMSUART_UART, EMSUART_TXPIN, EMSUART_RXPIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
|
||||
|
||||
// Setup UART buffered IO with event queue
|
||||
// const int uart_buffer_size = (1024 * 2);
|
||||
// QueueHandle_t uart_queue;
|
||||
// Install UART driver using an event queue here
|
||||
// ESP_ERROR_CHECK(uart_driver_install(UART2, uart_buffer_size,
|
||||
// uart_buffer_size, 10, &uart_queue, 0));
|
||||
// do not use user defined interrupt handlers --> we are waiting for break event
|
||||
// Decision was against written buffered I/O (seems to be fit better to ESP8266 approach), Queue is needed for receiving the break event, queue size wasn't clear
|
||||
// 1 should be enough - also 10 should be fine
|
||||
ESP_ERROR_CHECK(uart_driver_install(EMSUART_UART, EMSUART_RXBUFSIZE, 0, 10, &uart_queue, 0));
|
||||
|
||||
// Create a ringbuffer for communication between recvTask and parseTask (as max message size is ~32 bytes) 512 bytes has capacity to hold more than 16 telegrams
|
||||
buf_handle = xRingbufferCreate(512, RINGBUF_TYPE_NOSPLIT);
|
||||
|
||||
// start recvTaskQueue stacksize choosen any value 4069 bytes, Priority choosen 2 above normal to be able to fastly react on received signals
|
||||
// xTaskCreate parameters taken from https://github.com/espressif/esp-idf/blob/ce2a99dc23533f40369e4ab0d17ffd40f4b0dd72/examples/peripherals/uart/uart_events/main/uart_events_example_main.c
|
||||
xTaskCreate(emsuart_recvTask, "EMS_recv", 2048, NULL, 12, NULL);
|
||||
|
||||
// create 2 tasks because assumption is that parseTelegram needs some time (even on ESP32) which would block receiving task
|
||||
xTaskCreate(emsuart_parseTask, "EMS_parse", 8000, NULL, 2, NULL);
|
||||
//EMS_UART.conf1.rxfifo_full_thrhd = 127; // enough to hold the incoming telegram, should never reached
|
||||
//EMS_UART.idle_conf.tx_brk_num = 12; // breaklength 12 bit
|
||||
EMS_UART.int_ena.val = 0; // disable all intr.
|
||||
EMS_UART.int_clr.val = 0xFFFFFFFF; // clear all intr. flags
|
||||
EMS_UART.int_ena.brk_det = 1; // activate only break
|
||||
buf_handle = xRingbufferCreate(128, RINGBUF_TYPE_NOSPLIT);
|
||||
ESP_ERROR_CHECK(uart_isr_register(EMSUART_UART, uart_intr_handle, NULL, ESP_INTR_FLAG_IRAM, &uart_handle));
|
||||
xTaskCreate(emsuart_recvTask, "emsuart_recvTask", 2048, NULL, 12, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Stop, disables interrupt
|
||||
*/
|
||||
void EMSuart::stop() {
|
||||
EMS_UART.int_ena.val = 0; // disable all intr.
|
||||
};
|
||||
|
||||
/*
|
||||
* Restart Interrupt
|
||||
*/
|
||||
void EMSuart::restart() {
|
||||
EMS_UART.int_clr.val = 0xFFFFFFFF; // clear all intr. flags
|
||||
EMS_UART.int_ena.brk_det = 1; // activate only break
|
||||
};
|
||||
|
||||
/*
|
||||
* Sends a 1-byte poll, ending with a <BRK>
|
||||
* It's a bit dirty. there is no special wait logic per tx_mode type, fifo flushes or error checking
|
||||
*/
|
||||
void EMSuart::send_poll(uint8_t data) {
|
||||
char buf[1];
|
||||
buf[0] = data;
|
||||
uart_write_bytes_with_break(EMSUART_UART, (const char *)buf, 1, EMSUART_BREAKBITS);
|
||||
EMS_UART.conf0.txd_brk = 0; // just to make sure the bit is cleared
|
||||
EMS_UART.fifo.rw_byte = data;
|
||||
EMS_UART.idle_conf.tx_brk_num = 12; // breaklength 12 bit
|
||||
EMS_UART.conf0.txd_brk = 1; // sending ends in a break
|
||||
}
|
||||
|
||||
/*
|
||||
* Send data to Tx line, ending with a <BRK>
|
||||
* buf contains the CRC and len is #bytes including the CRC
|
||||
* returns code, 0=success, 1=brk error, 2=watchdog timeout
|
||||
* returns code, 1=success
|
||||
*/
|
||||
EMSUART_STATUS EMSuart::transmit(uint8_t * buf, uint8_t len) {
|
||||
if (len == 0) {
|
||||
return EMS_TX_STATUS_OK; // nothing to send
|
||||
if (len > 0) {
|
||||
if (EMS_UART.status.txfifo_cnt > 0) { // fifo not empty
|
||||
return EMS_TX_WTD_TIMEOUT;
|
||||
}
|
||||
|
||||
if (len && buf) {
|
||||
++sending;
|
||||
uart_write_bytes_with_break(EMSUART_UART, (const char *)buf, len, EMSUART_BREAKBITS);
|
||||
EMS_UART.conf0.txd_brk = 0; // just to make sure the bit is cleared
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
EMS_UART.fifo.rw_byte = buf[i];
|
||||
}
|
||||
//uart_tx_chars(EMSUART_UART, (const char *)buf, len);
|
||||
EMS_UART.idle_conf.tx_brk_num = 12; // breaklength 12 bit
|
||||
EMS_UART.conf0.txd_brk = 1; // sending ends in a break
|
||||
}
|
||||
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,19 +27,15 @@
|
||||
#include "freertos/queue.h"
|
||||
#include <driver/uart.h>
|
||||
|
||||
#define EMS_MAXBUFFERSIZE 34 // max size of the buffer. EMS packets are max 32 bytes, plus extra 2 for BRKs
|
||||
#define EMS_MAXBUFFERSIZE 33 // max size of the buffer. EMS packets are max 32 bytes, plus extra 2 for BRKs
|
||||
|
||||
#define EMSUART_UART UART_NUM_2 // UART 0 --> Changed to 2 for ESP32 // To do: Adapt
|
||||
#define EMSUART_RXPIN 17 // To do: Adapt seems to be IO17 for ESP32 UART2 RX pin
|
||||
#define EMSUART_TXPIN 16 // To do: Adapt seems to be IO16 for ESP32 UART2 TX pin
|
||||
//#define EMSUART_CONFIG 0x1C // 8N1 (8 bits, no parity, 1 stopbit)
|
||||
#define EMSUART_UART UART_NUM_2 // on the ESP32 we're using UART2
|
||||
#define EMS_UART UART2 // for intr setting
|
||||
#define EMSUART_BAUD 9600 // uart baud rate for the EMS circuit
|
||||
#define EMSUART_RXBUFSIZE (2 * UART_FIFO_LEN)
|
||||
#define EMSUART_BREAKBITS 11 // 11 bits break signal
|
||||
|
||||
#define EMS_TXMODE_DEFAULT 1
|
||||
#define EMS_TXMODE_EMSPLUS 2
|
||||
#define EMS_TXMODE_HT3 3
|
||||
// customize the GPIO pins for RX and TX here
|
||||
#define EMSUART_RXPIN 17 // 17 is UART2 RX. Use 23 for D7 on a Wemos D1-32 mini for backwards compatabilty
|
||||
#define EMSUART_TXPIN 16 // 16 is UART2 TX. Use 5 for D8 on a Wemos D1-32 mini for backwards compatabilty
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
@@ -56,14 +52,12 @@ class EMSuart {
|
||||
|
||||
static void start(uint8_t tx_mode);
|
||||
static void send_poll(uint8_t data);
|
||||
static void stop();
|
||||
static void restart();
|
||||
static EMSUART_STATUS transmit(uint8_t * buf, uint8_t len);
|
||||
|
||||
static void stop(){}; // not used with ESP32
|
||||
static void restart(){}; // not used with ESP32
|
||||
|
||||
private:
|
||||
static void emsuart_recvTask(void * param);
|
||||
static void emsuart_parseTask(void * param);
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -25,52 +25,32 @@
|
||||
namespace emsesp {
|
||||
|
||||
os_event_t recvTaskQueue[EMSUART_recvTaskQueueLen]; // our Rx queue
|
||||
|
||||
EMSuart::EMSRxBuf_t * pEMSRxBuf;
|
||||
EMSuart::EMSRxBuf_t * paEMSRxBuf[EMS_MAXBUFFERS];
|
||||
uint8_t emsRxBufIdx = 0;
|
||||
uint8_t phantomBreak = 0;
|
||||
uint8_t tx_mode_ = EMS_TXMODE_DEFAULT;
|
||||
|
||||
//
|
||||
// Main interrupt handler
|
||||
// Important: must not use ICACHE_FLASH_ATTR
|
||||
//
|
||||
void ICACHE_RAM_ATTR EMSuart::emsuart_rx_intr_handler(void * para) {
|
||||
static uint8_t length = 0;
|
||||
static bool rx_idle_ = true;
|
||||
static uint8_t uart_buffer[EMS_MAXBUFFERSIZE + 2];
|
||||
|
||||
// is a new buffer? if so init the thing for a new telegram
|
||||
if (rx_idle_) {
|
||||
rx_idle_ = false; // status set to busy
|
||||
length = 0;
|
||||
}
|
||||
|
||||
// fill IRQ buffer, by emptying Rx FIFO
|
||||
if (USIS(EMSUART_UART) & ((1 << UIFF) | (1 << UITO) | (1 << UIBD))) {
|
||||
while ((USS(EMSUART_UART) >> USRXC) & 0xFF) {
|
||||
uint8_t rx = USF(EMSUART_UART);
|
||||
if (length < EMS_MAXBUFFERSIZE)
|
||||
uart_buffer[length++] = rx;
|
||||
}
|
||||
|
||||
// clear Rx FIFO full and Rx FIFO timeout interrupts
|
||||
USIC(EMSUART_UART) = (1 << UIFF) | (1 << UITO);
|
||||
}
|
||||
static uint8_t uart_buffer[128];
|
||||
|
||||
// BREAK detection = End of EMS data block
|
||||
if (USIS(EMSUART_UART) & ((1 << UIBD))) {
|
||||
ETS_UART_INTR_DISABLE(); // disable all interrupts and clear them
|
||||
USIC(EMSUART_UART) = (1 << UIBD); // INT clear the BREAK detect interrupt
|
||||
|
||||
pEMSRxBuf->length = (length > EMS_MAXBUFFERSIZE) ? EMS_MAXBUFFERSIZE : length;
|
||||
uint8_t rxlen = (USS(EMSUART_UART) & 0xFF); // length of buffer
|
||||
for (length = 0; length < rxlen; length++) {
|
||||
uart_buffer[length] = USF(EMSUART_UART);
|
||||
}
|
||||
USIE(EMSUART_UART) = 0; // disable all interrupts and clear them
|
||||
USC0(EMSUART_UART) &= ~(1 << UCBRK); // reset <BRK> from sending
|
||||
if (length < EMS_MAXBUFFERSIZE) { // only a valid telegram
|
||||
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
|
||||
rx_idle_ = true; // check set the status flag stating BRK has been received and we can start a new package
|
||||
ETS_UART_INTR_ENABLE(); // re-enable UART interrupts
|
||||
|
||||
system_os_post(EMSUART_recvTaskPrio, 0, 0); // call emsuart_recvTask() at next opportunity
|
||||
}
|
||||
USIC(EMSUART_UART) = (1 << UIBD); // INT clear the BREAK detect interrupt
|
||||
USIE(EMSUART_UART) = (1 << UIBD); // enable only rx break
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -84,40 +64,23 @@ void ICACHE_FLASH_ATTR EMSuart::emsuart_recvTask(os_event_t * events) {
|
||||
uint8_t length = pCurrent->length; // number of bytes including the BRK at the end
|
||||
pCurrent->length = 0;
|
||||
|
||||
if (phantomBreak) {
|
||||
phantomBreak = 0;
|
||||
length--; // remove phantom break from Rx buffer
|
||||
}
|
||||
|
||||
// it's a poll or status code, single byte and ok to send on, then quit
|
||||
if (length == 2) {
|
||||
EMSESP::incoming_telegram((uint8_t *)pCurrent->buffer, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// ignore double BRK at the end, possibly from the Tx loopback
|
||||
// also telegrams with no data value
|
||||
// then transmit EMS buffer, excluding the BRK
|
||||
if ((length > 4) && (length <= EMS_MAXBUFFERSIZE + 1)) {
|
||||
// then transmit EMS buffer, excluding the BRK, length is checked by irq
|
||||
if (length > 4) {
|
||||
EMSESP::incoming_telegram((uint8_t *)pCurrent->buffer, length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* flush everything left over in buffer, this clears both rx and tx FIFOs
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR EMSuart::emsuart_flush_fifos() {
|
||||
uint32_t tmp = ((1 << UCRXRST) | (1 << UCTXRST)); // bit mask
|
||||
USC0(EMSUART_UART) |= (tmp); // set bits
|
||||
USC0(EMSUART_UART) &= ~(tmp); // clear bits
|
||||
}
|
||||
|
||||
/*
|
||||
* init UART0 driver
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) {
|
||||
tx_mode_ = tx_mode;
|
||||
|
||||
// allocate and preset EMS Receive buffers
|
||||
for (int i = 0; i < EMS_MAXBUFFERS; i++) {
|
||||
EMSRxBuf_t * p = (EMSRxBuf_t *)malloc(sizeof(EMSRxBuf_t));
|
||||
@@ -125,9 +88,6 @@ void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) {
|
||||
}
|
||||
pEMSRxBuf = paEMSRxBuf[0]; // reset EMS Rx Buffer
|
||||
|
||||
ETS_UART_INTR_DISABLE();
|
||||
ETS_UART_INTR_ATTACH(nullptr, nullptr);
|
||||
|
||||
// pin settings
|
||||
PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0TXD_U);
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD);
|
||||
@@ -136,43 +96,21 @@ void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) {
|
||||
|
||||
// set 9600, 8 bits, no parity check, 1 stop bit
|
||||
USD(EMSUART_UART) = (UART_CLK_FREQ / EMSUART_BAUD);
|
||||
USC0(EMSUART_UART) = EMSUART_CONFIG; // 8N1
|
||||
USC0(EMSUART_UART) = EMSUART_CONFIG;
|
||||
|
||||
emsuart_flush_fifos();
|
||||
USC0(EMSUART_UART) |= ((1 << UCRXRST) | (1 << UCTXRST)); // set bits
|
||||
USC0(EMSUART_UART) &= ~((1 << UCRXRST) | (1 << UCTXRST)); // clear bits
|
||||
|
||||
// conf1 params
|
||||
// UCTOE = RX TimeOut enable (default is 1)
|
||||
// UCTOT = RX TimeOut Threshold (7 bit) = want this when no more data after 1 characters (default is 2)
|
||||
// UCFFT = RX FIFO Full Threshold (7 bit) = want this to be 31 for 32 bytes of buffer (default was 127)
|
||||
// see https://www.espressif.com/sites/default/files/documentation/esp8266-technical_reference_en.pdf
|
||||
//
|
||||
// change: we set UCFFT to 1 to get an immediate indicator about incoming traffic.
|
||||
// Otherwise, we're only noticed by UCTOT or RxBRK!
|
||||
USC1(EMSUART_UART) = 0; // reset config first
|
||||
USC1(EMSUART_UART) = (0x01 << UCFFT) | (0x01 << UCTOT) | (0 << UCTOE); // enable interupts
|
||||
|
||||
// set interrupts for triggers
|
||||
USIC(EMSUART_UART) = 0xFFFF; // clear all interupts
|
||||
// UCFFT = RX FIFO Full Threshold (7 bit) = want this to be more than 32)
|
||||
USC1(EMSUART_UART) = (0x7F << UCFFT); // rx buffer full
|
||||
USIE(EMSUART_UART) = 0; // disable all interrupts
|
||||
USIC(EMSUART_UART) = 0xFFFF; // clear all interupts
|
||||
|
||||
// enable rx break, fifo full and timeout.
|
||||
// but not frame error UIFR (because they are too frequent) or overflow UIOF because our buffer is only max 32 bytes
|
||||
// change: we don't care about Rx Timeout - it may lead to wrong readouts
|
||||
USIE(EMSUART_UART) = (1 << UIBD) | (1 << UIFF) | (0 << UITO);
|
||||
|
||||
// set up interrupt callbacks for Rx
|
||||
system_os_task(emsuart_recvTask, EMSUART_recvTaskPrio, recvTaskQueue, EMSUART_recvTaskQueueLen);
|
||||
|
||||
// disable esp debug which will go to Tx and mess up the line - see https://github.com/espruino/Espruino/issues/655
|
||||
system_set_os_print(0);
|
||||
|
||||
// swap Rx and Tx pins to use GPIO13 (D7) and GPIO15 (D8) respectively
|
||||
system_uart_swap();
|
||||
|
||||
system_os_task(emsuart_recvTask, EMSUART_recvTaskPrio, recvTaskQueue, EMSUART_recvTaskQueueLen); // set up interrupt callbacks for Rx
|
||||
system_set_os_print(0); // disable esp debug which will go to Tx and mess up the line - see https://github.com/espruino/Espruino/issues/655
|
||||
system_uart_swap(); // swap Rx and Tx pins to use GPIO13 (D7) and GPIO15 (D8) respectively
|
||||
ETS_UART_INTR_ATTACH(emsuart_rx_intr_handler, nullptr);
|
||||
ETS_UART_INTR_ENABLE();
|
||||
|
||||
// logger_.info(F("UART service for Rx/Tx started"));
|
||||
USIE(EMSUART_UART) = (1 << UIBD); // enable only rx break interrupt
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -180,170 +118,38 @@ void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) {
|
||||
* This is called prior to an OTA upload and also before a save to the filesystem to prevent conflicts
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR EMSuart::stop() {
|
||||
ETS_UART_INTR_DISABLE();
|
||||
USIE(EMSUART_UART) = 0; // disable interrup
|
||||
}
|
||||
|
||||
/*
|
||||
* re-start UART0 driver
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR EMSuart::restart() {
|
||||
ETS_UART_INTR_ENABLE();
|
||||
USIE(EMSUART_UART) = (1 << UIBD); // enable only rx break
|
||||
}
|
||||
|
||||
/*
|
||||
* Send a BRK signal
|
||||
* Which is a 11-bit set of zero's (11 cycles)
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR EMSuart::tx_brk() {
|
||||
uint32_t tmp;
|
||||
|
||||
// must make sure Tx FIFO is empty
|
||||
while (((USS(EMSUART_UART) >> USTXC) & 0xFF))
|
||||
;
|
||||
|
||||
tmp = ((1 << UCRXRST) | (1 << UCTXRST)); // bit mask
|
||||
USC0(EMSUART_UART) |= (tmp); // set bits
|
||||
USC0(EMSUART_UART) &= ~(tmp); // clear bits
|
||||
|
||||
// To create a 11-bit <BRK> we set TXD_BRK bit so the break signal will
|
||||
// automatically be sent when the tx fifo is empty
|
||||
tmp = (1 << UCBRK);
|
||||
USC0(EMSUART_UART) |= (tmp); // set bit
|
||||
|
||||
if (tx_mode_ == EMS_TX_WTD_TIMEOUT) { // EMS+ mode
|
||||
delayMicroseconds(EMSUART_TX_BRK_WAIT);
|
||||
} else if (tx_mode_ == EMS_TXMODE_HT3) { // junkers mode
|
||||
delayMicroseconds(EMSUART_TX_WAIT_BRK - EMSUART_TX_LAG); // 1144 (11 Bits)
|
||||
}
|
||||
|
||||
USC0(EMSUART_UART) &= ~(tmp); // clear bit
|
||||
}
|
||||
|
||||
/*
|
||||
* Sends a 1-byte poll, ending with a <BRK>
|
||||
* It's a bit dirty. there is no special wait logic per tx_mode type, fifo flushes or error checking
|
||||
*/
|
||||
void EMSuart::send_poll(uint8_t data) {
|
||||
USC0(EMSUART_UART) &= ~(1 << UCBRK); // clear <BRK> bit
|
||||
USF(EMSUART_UART) = data;
|
||||
delayMicroseconds(EMSUART_TX_BRK_WAIT);
|
||||
tx_brk(); // send <BRK>
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK); // send <BRK> at the end
|
||||
}
|
||||
|
||||
/*
|
||||
* Send data to Tx line, ending with a <BRK>
|
||||
* buf contains the CRC and len is #bytes including the CRC
|
||||
* returns code, 0=success, 1=brk error, 2=watchdog timeout
|
||||
*/
|
||||
EMSUART_STATUS ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) {
|
||||
if (len == 0) {
|
||||
return EMS_TX_STATUS_OK; // nothing to send
|
||||
}
|
||||
|
||||
// EMS+ https://github.com/proddy/EMS-ESP/issues/23#
|
||||
if (tx_mode_ == EMS_TXMODE_EMSPLUS) { // With extra tx delay for EMS+
|
||||
if (len) {
|
||||
USC0(EMSUART_UART) &= ~(1 << UCBRK); // clear <BRK> bit
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
USF(EMSUART_UART) = buf[i];
|
||||
delayMicroseconds(EMSUART_TX_BRK_WAIT); // 2070
|
||||
}
|
||||
tx_brk(); // send <BRK>
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK); // send <BRK> at the end
|
||||
}
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
// Junkers logic by @philrich
|
||||
if (tx_mode_ == EMS_TXMODE_HT3) {
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
USF(EMSUART_UART) = buf[i];
|
||||
|
||||
// just to be safe wait for tx fifo empty (still needed?)
|
||||
while (((USS(EMSUART_UART) >> USTXC) & 0xff))
|
||||
;
|
||||
|
||||
// wait until bits are sent on wire
|
||||
delayMicroseconds(EMSUART_TX_WAIT_BYTE - EMSUART_TX_LAG + EMSUART_TX_WAIT_GAP); // 1760
|
||||
}
|
||||
tx_brk(); // send <BRK>
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Logic for tx_mode of 0 (EMS_TXMODE_DEFAULT)
|
||||
* based on code from https://github.com/proddy/EMS-ESP/issues/103 by @susisstrolch
|
||||
*
|
||||
* Logic:
|
||||
* we emit the whole telegram, with Rx interrupt disabled, collecting busmaster response in FIFO.
|
||||
* after sending the last char we poll the Rx status until either
|
||||
* - size(Rx FIFO) == size(Tx-Telegram)
|
||||
* - <BRK> is detected
|
||||
* At end of receive we re-enable Rx-INT and send a Tx-BRK in loopback mode.
|
||||
*
|
||||
* EMS-Bus error handling
|
||||
* 1. Busmaster stops echoing on Tx w/o permission
|
||||
* 2. Busmaster cancel telegram by sending a BRK
|
||||
*
|
||||
* Case 1. is handled by a watchdog counter which is reset on each
|
||||
* Tx attempt. The timeout should be 20x EMSUART_BIT_TIME plus
|
||||
* some smart guess for processing time on targeted EMS device.
|
||||
* We set Status to EMS_TX_WTD_TIMEOUT and return
|
||||
*
|
||||
* Case 2. is handled via a BRK chk during transmission.
|
||||
* We set Status to EMS_TX_BRK_DETECT and return
|
||||
*
|
||||
*/
|
||||
|
||||
EMSUART_STATUS result = EMS_TX_STATUS_OK;
|
||||
|
||||
// disable rx interrupt
|
||||
// clear Rx status register, resetting the Rx FIFO and flush it
|
||||
ETS_UART_INTR_DISABLE();
|
||||
USC0(EMSUART_UART) |= (1 << UCRXRST);
|
||||
emsuart_flush_fifos();
|
||||
|
||||
// send the bytes along the serial line
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
uint16_t wdc = EMS_TX_TO_COUNT; // 1760
|
||||
volatile uint8_t _usrxc = (USS(EMSUART_UART) >> USRXC) & 0xFF;
|
||||
USF(EMSUART_UART) = buf[i]; // send each Tx byte
|
||||
// wait for echo from the busmaster
|
||||
while (((USS(EMSUART_UART) >> USRXC) & 0xFF) == _usrxc) {
|
||||
delayMicroseconds(EMSUART_BUSY_WAIT); // burn CPU cycles...
|
||||
if (--wdc == 0) {
|
||||
ETS_UART_INTR_ENABLE();
|
||||
return EMS_TX_WTD_TIMEOUT;
|
||||
}
|
||||
if (USIR(EMSUART_UART) & (1 << UIBD)) {
|
||||
USIC(EMSUART_UART) = (1 << UIBD); // clear BRK detect IRQ
|
||||
ETS_UART_INTR_ENABLE();
|
||||
return EMS_TX_BRK_DETECT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we got the whole telegram in the Rx buffer
|
||||
// on Rx-BRK (bus collision), we simply enable Rx and leave it
|
||||
// otherwise we send the final Tx-BRK in the loopback and re=enable Rx-INT.
|
||||
// worst case, we'll see an additional Rx-BRK...
|
||||
if (result == EMS_TX_STATUS_OK) {
|
||||
// neither bus collision nor timeout - send terminating BRK signal
|
||||
if (!(USIS(EMSUART_UART) & (1 << UIBD))) {
|
||||
// no bus collision - send terminating BRK signal
|
||||
USC0(EMSUART_UART) |= (1 << UCLBE) | (1 << UCBRK); // enable loopback & set <BRK>
|
||||
|
||||
// wait until BRK detected...
|
||||
while (!(USIR(EMSUART_UART) & (1 << UIBD))) {
|
||||
delayMicroseconds(EMSUART_BIT_TIME);
|
||||
}
|
||||
|
||||
USC0(EMSUART_UART) &= ~((1 << UCBRK) | (1 << UCLBE)); // disable loopback & clear <BRK>
|
||||
USIC(EMSUART_UART) = (1 << UIBD); // clear BRK detect IRQ
|
||||
phantomBreak = 1;
|
||||
}
|
||||
}
|
||||
|
||||
ETS_UART_INTR_ENABLE(); // open up the FIFO again to start receiving
|
||||
|
||||
return result; // send the Tx status back
|
||||
}
|
||||
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
|
||||
@@ -28,19 +28,7 @@
|
||||
#define EMSUART_BAUD 9600 // uart baud rate for the EMS circuit
|
||||
|
||||
#define EMS_MAXBUFFERS 3 // buffers for circular filling to avoid collisions
|
||||
#define EMS_MAXBUFFERSIZE 34 // max size of the buffer. EMS packets are max 32 bytes, plus extra 2 for BRKs
|
||||
|
||||
#define EMSUART_BIT_TIME 104 // bit time @9600 baud
|
||||
|
||||
#define EMSUART_TX_BRK_WAIT 2070 // the BRK from Boiler master is roughly 1.039ms, so accounting for hardware lag using around 2078 (for half-duplex) - 8 (lag)
|
||||
#define EMSUART_TX_WAIT_BYTE (EMSUART_BIT_TIME * 10) // Time to send one Byte (8 Bits, 1 Start Bit, 1 Stop Bit)
|
||||
#define EMSUART_TX_WAIT_BRK (EMSUART_BIT_TIME * 11) // Time to send a BRK Signal (11 Bit)
|
||||
#define EMSUART_TX_WAIT_GAP (EMSUART_BIT_TIME * 7) // Gap between to Bytes
|
||||
#define EMSUART_TX_LAG 8
|
||||
|
||||
#define EMSUART_BUSY_WAIT (EMSUART_BIT_TIME / 8)
|
||||
#define EMS_TX_TO_CHARS (2 + 20)
|
||||
#define EMS_TX_TO_COUNT ((EMS_TX_TO_CHARS)*10 * 8)
|
||||
#define EMS_MAXBUFFERSIZE 33 // max size of the buffer. EMS packets are max 32 bytes, plus extra 2 for BRKs
|
||||
|
||||
#define EMSUART_recvTaskPrio 1 // 0, 1 or 2. 0 being the lowest
|
||||
#define EMSUART_recvTaskQueueLen 10 // number of queued'd Rx triggers
|
||||
@@ -75,10 +63,7 @@ class EMSuart {
|
||||
|
||||
private:
|
||||
static void ICACHE_RAM_ATTR emsuart_rx_intr_handler(void * para);
|
||||
|
||||
static void ICACHE_FLASH_ATTR emsuart_recvTask(os_event_t * events);
|
||||
static void ICACHE_FLASH_ATTR emsuart_flush_fifos();
|
||||
static void ICACHE_FLASH_ATTR tx_brk();
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -1 +1 @@
|
||||
#define EMSESP_APP_VERSION "2.0.0a6"
|
||||
#define EMSESP_APP_VERSION "2.0.0a7"
|
||||
|
||||
Reference in New Issue
Block a user