diff --git a/README.md b/README.md index 9f7c63664..a484eaa51 100644 --- a/README.md +++ b/README.md @@ -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] [raw] + log [level] [raw] [trace ID] su (top level) @@ -124,8 +124,9 @@ thermostat ### **Known issues, bugs and improvements currently working on** ``` +TODO ESP32 - when saving SPIFFS the UART stop and restart() functions need to flush queue to avoid miss fires +TODO network issues with ESP8266 - can take a while to get an IP address. DNS issue? 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. TODO console auto-complete with 'set' command in the system context is not showing all commands, only the hostname. ``` diff --git a/lib/uuid-common/src/get_uptime_ms.cpp b/lib/uuid-common/src/get_uptime_ms.cpp index 0ff691dbe..5fa8d0f00 100644 --- a/lib/uuid-common/src/get_uptime_ms.cpp +++ b/lib/uuid-common/src/get_uptime_ms.cpp @@ -26,15 +26,25 @@ uint64_t get_uptime_ms() { static uint32_t high_millis = 0; static uint32_t low_millis = 0; - uint32_t now_millis = ::millis(); - - if (now_millis < low_millis) { + if (get_uptime() < low_millis) { high_millis++; } - low_millis = now_millis; + low_millis = get_uptime(); return ((uint64_t)high_millis << 32) | low_millis; } +// added by proddy + +static uint32_t now_millis; // added by proddy + +void set_uptime() { + now_millis = ::millis(); +} + +uint32_t get_uptime() { + return now_millis; +} + } // namespace uuid diff --git a/lib/uuid-common/src/loop.cpp b/lib/uuid-common/src/loop.cpp index 3f5d89176..184dc5626 100644 --- a/lib/uuid-common/src/loop.cpp +++ b/lib/uuid-common/src/loop.cpp @@ -21,6 +21,7 @@ namespace uuid { void loop() { + set_uptime(); // added by proddy get_uptime_ms(); } diff --git a/lib/uuid-common/src/uuid/common.h b/lib/uuid-common/src/uuid/common.h index 4c1203af5..3811ca727 100644 --- a/lib/uuid-common/src/uuid/common.h +++ b/lib/uuid-common/src/uuid/common.h @@ -86,6 +86,9 @@ void loop(); */ uint64_t get_uptime_ms(); +uint32_t get_uptime(); // added by proddy +void set_uptime(); + } // namespace uuid #endif diff --git a/platformio.ini b/platformio.ini index 8468e8390..b4babdcac 100644 --- a/platformio.ini +++ b/platformio.ini @@ -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\" @@ -69,7 +69,7 @@ upload_protocol = espota upload_flags = --port=8266 --auth=neo -upload_port = ems-esp32.local +upload_port = ems-esp.local [env:esp8266] build_type = release diff --git a/src/console.cpp b/src/console.cpp index 1f57b045e..5ed1a9c78 100644 --- a/src/console.cpp +++ b/src/console.cpp @@ -221,7 +221,7 @@ 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), F_(raw_optional)}, + flash_string_vector{F_(log_level_optional), F_(trace_format_optional), F_(traceid_optional)}, [](Shell & shell, const std::vector & arguments) { uint16_t watch_id; if (!arguments.empty()) { @@ -238,16 +238,17 @@ void Console::load_standard_commands(unsigned int context) { 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 - } + // next argument is raw or full + if (arguments[1] == read_flash_string(F_(raw))) { + emsesp::EMSESP::trace_raw(true); + } else if (arguments[1] == read_flash_string(F_(full))) { + emsesp::EMSESP::trace_raw(false); + } - 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); + // get the watch_id if its set + if (arguments.size() == 3) { + emsesp::EMSESP::trace_watch_id(Helpers::hextoint(arguments[2].c_str())); + } } } } diff --git a/src/console.h b/src/console.h index 049c4d51b..3f4d8c12f 100644 --- a/src/console.h +++ b/src/console.h @@ -89,6 +89,7 @@ MAKE_PSTR_WORD(restart) MAKE_PSTR_WORD(reconnect) MAKE_PSTR_WORD(format) MAKE_PSTR_WORD(raw) +MAKE_PSTR_WORD(full) // context menus MAKE_PSTR_WORD(mqtt) @@ -102,8 +103,8 @@ MAKE_PSTR(n_mandatory, "") 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, "") +MAKE_PSTR(trace_format_optional, "[full|raw]") +MAKE_PSTR(bool_mandatory, "") MAKE_PSTR(typeid_mandatory, "") MAKE_PSTR(deviceid_mandatory, "") MAKE_PSTR(deviceid_optional, "[device ID]") diff --git a/src/emsesp.cpp b/src/emsesp.cpp index 3fbeb4844..8f98e9b6e 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -333,7 +333,7 @@ void EMSESP::process_UBADevices(std::shared_ptr 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))) { - LOG_INFO(F("New EMS device detected with ID 0x%02X. Requesting version information."), device_id); + LOG_DEBUG(F("New EMS device detected with ID 0x%02X. Requesting version information."), device_id); send_read_request(EMSdevice::EMS_TYPE_VERSION, device_id); } } @@ -619,7 +619,7 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) { // check for poll to us, if so send top message from Tx queue immediately and quit // if ht3 poll must be ems_bus_id else if Buderus poll must be (ems_bus_id | 0x80) if ((first_value ^ 0x80 ^ rxservice_.ems_mask()) == txservice_.ems_bus_id()) { - EMSbus::last_bus_activity(millis()); // set the flag indication the EMS bus is active + EMSbus::last_bus_activity(uuid::get_uptime()); // set the flag indication the EMS bus is active txservice_.send(); } return; @@ -695,17 +695,17 @@ void EMSESP::console_commands(Shell & shell, unsigned int context) { flash_string_vector{F_(n_mandatory)}, [](Shell & shell, const std::vector & arguments) { uint8_t tx_mode = (arguments[0]).at(0) - '0'; - if ((tx_mode > 0) && (tx_mode <= 3)) { + if ((tx_mode > 0) && (tx_mode <= 4)) { Settings settings; settings.ems_tx_mode(tx_mode); settings.commit(); shell.printfln(F_(tx_mode_fmt), settings.ems_tx_mode()); } else { - shell.println(F("Must be 1 for EMS generic, 2 for EMS+ or 3 for HT3")); + shell.println(F("Must be 1 for EMS generic, 2 for EMS+, 3 for HT3, 4 for experimental")); } }, [](Shell & shell __attribute__((unused)), const std::vector & arguments __attribute__((unused))) -> const std::vector { - return std::vector{read_flash_string(F("1")), read_flash_string(F("2")), read_flash_string(F("3"))}; + return std::vector{read_flash_string(F("1")), read_flash_string(F("2")), read_flash_string(F("3")), read_flash_string(F("4"))}; }); EMSESPShell::commands->add_command( @@ -808,19 +808,20 @@ void EMSESP::start() { // loop de loop void EMSESP::loop() { - // network returns false if an OTA is being carried out. + // network returns false if an OTA is being carried out + // so we disable all services when an OTA is happening if (network_.loop()) { console_.loop(); // telnet/serial console system_.loop(); // does LED and checks system health, and syslog service mqtt_.loop(); // starts mqtt, and sends out anything in the queue rxservice_.loop(); // process what ever is in the rx queue + txservice_.loop(); // check that the Tx is all ok shower_.loop(); // check for shower on/off sensors_.loop(); // this will also send out via MQTT - // force a query on the EMS devices to fetch latest data - uint32_t currentMillis = millis(); - if ((currentMillis - last_fetch_ > EMS_FETCH_FREQUENCY)) { - last_fetch_ = currentMillis; + // force a query on the EMS devices to fetch latest data at a set interval (1 min) + if ((uuid::get_uptime() - last_fetch_ > EMS_FETCH_FREQUENCY)) { + last_fetch_ = uuid::get_uptime(); fetch_device_values(); } } diff --git a/src/emsesp.h b/src/emsesp.h index c66698e35..02f27eb49 100644 --- a/src/emsesp.h +++ b/src/emsesp.h @@ -45,7 +45,7 @@ #include "boiler.h" #include "shower.h" -#define LOG_TRACE_WATCH_NONE 0 // no watch set +#define LOG_TRACE_WATCH_NONE 0 // no watch set namespace emsesp { diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 55197fa24..9b00788c2 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -142,7 +142,7 @@ void Mqtt::setup() { #endif mqtt_connecting_ = false; - mqtt_last_connection_ = millis(); + mqtt_last_connection_ = uuid::get_uptime(); mqtt_reconnect_delay_ = Mqtt::MQTT_RECONNECT_DELAY_MIN; LOG_DEBUG(F("Configuring MQTT service...")); @@ -177,7 +177,7 @@ void Mqtt::init() { } // Reset reconnection delay - mqtt_last_connection_ = millis(); + mqtt_last_connection_ = uuid::get_uptime(); mqtt_connecting_ = false; mqtt_start_ = false; // will force a new start() }); @@ -236,7 +236,7 @@ void Mqtt::loop() { } // send out heartbeat - uint32_t currentMillis = millis(); + uint32_t currentMillis = uuid::get_uptime(); if ((currentMillis - last_heartbeat_ > MQTT_HEARTBEAT_INTERVAL)) { last_heartbeat_ = currentMillis; send_heartbeat(); @@ -258,7 +258,7 @@ void Mqtt::loop() { } // We need to reconnect. Check when was the last time we tried this - if (mqtt_last_connection_ && (millis() - mqtt_last_connection_ < mqtt_reconnect_delay_)) { + if (mqtt_last_connection_ && (uuid::get_uptime() - mqtt_last_connection_ < mqtt_reconnect_delay_)) { return; } @@ -394,7 +394,9 @@ void Mqtt::on_publish(uint16_t packetId) { } if (mqtt_message.packet_id_ == packetId) { +#ifdef EMSESP_DEBUG LOG_DEBUG(F("Acknowledged PID %d. Removing from queue"), packetId); +#endif } else { LOG_DEBUG(F("Mismatch, expecting PID %d, got %d"), mqtt_message.packet_id_, packetId); mqtt_publish_fails_++; // increment error count @@ -436,7 +438,7 @@ void Mqtt::send_start_topic() { // MQTT onConnect - when a connect is established void Mqtt::on_connect() { mqtt_reconnect_delay_ = Mqtt::MQTT_RECONNECT_DELAY_MIN; - mqtt_last_connection_ = millis(); + mqtt_last_connection_ = uuid::get_uptime(); mqtt_connecting_ = false; LOG_INFO(F("MQTT connected")); } @@ -600,7 +602,9 @@ 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; +#ifdef EMSESP_DEBUG LOG_DEBUG(F("Setting packetID for ACK to %d"), packet_id); +#endif return; } diff --git a/src/sensors.cpp b/src/sensors.cpp index 339af7eed..c187a124e 100644 --- a/src/sensors.cpp +++ b/src/sensors.cpp @@ -54,8 +54,10 @@ void Sensors::start() { void Sensors::loop() { #ifndef EMSESP_STANDALONE + uint32_t time_now = uuid::get_uptime(); + if (state_ == State::IDLE) { - if (millis() - last_activity_ >= READ_INTERVAL_MS) { + if (time_now - last_activity_ >= READ_INTERVAL_MS) { // LOG_DEBUG(F("Read sensor temperature")); // uncomment for debug if (bus_.reset()) { bus_.skip(); @@ -67,7 +69,7 @@ void Sensors::loop() { // LOG_ERROR(F("Bus reset failed")); // uncomment for debug devices_.clear(); // remove all know devices incase we have a disconnect } - last_activity_ = millis(); + last_activity_ = time_now; } } else if (state_ == State::READING) { if (temperature_convert_complete()) { @@ -76,18 +78,18 @@ void Sensors::loop() { found_.clear(); state_ = State::SCANNING; - last_activity_ = millis(); - } else if (millis() - last_activity_ > READ_TIMEOUT_MS) { + last_activity_ = time_now; + } else if (time_now - last_activity_ > READ_TIMEOUT_MS) { LOG_ERROR(F("Sensor read timeout")); state_ = State::IDLE; - last_activity_ = millis(); + last_activity_ = time_now; } } else if (state_ == State::SCANNING) { - if (millis() - last_activity_ > SCAN_TIMEOUT_MS) { + if (time_now - last_activity_ > SCAN_TIMEOUT_MS) { LOG_ERROR(F("Sensor scan timeout")); state_ = State::IDLE; - last_activity_ = millis(); + last_activity_ = time_now; } else { uint8_t addr[ADDR_LEN] = {0}; @@ -125,7 +127,7 @@ void Sensors::loop() { found_.clear(); // LOG_DEBUG(F("Found %zu sensor(s). Adding them."), devices_.size()); // uncomment for debug state_ = State::IDLE; - last_activity_ = millis(); + last_activity_ = time_now; } } } diff --git a/src/sensors.h b/src/sensors.h index f8553f334..9c66eedd7 100644 --- a/src/sensors.h +++ b/src/sensors.h @@ -100,8 +100,8 @@ class Sensors { bool temperature_convert_complete(); float get_temperature_c(const uint8_t addr[]); - uint32_t last_activity_ = millis(); - uint32_t last_publish_ = millis(); + uint32_t last_activity_ = uuid::get_uptime(); + uint32_t last_publish_ = uuid::get_uptime(); State state_ = State::IDLE; std::vector found_; std::vector devices_; diff --git a/src/shower.cpp b/src/shower.cpp index 6a539f820..70a5a1298 100644 --- a/src/shower.cpp +++ b/src/shower.cpp @@ -36,7 +36,8 @@ void Shower::loop() { return; } - uint32_t time_now = millis(); + uint32_t time_now = uuid::get_uptime(); + // if already in cold mode, ignore all this logic until we're out of the cold blast if (!doing_cold_shot_) { // is the hot water running? diff --git a/src/system.cpp b/src/system.cpp index f8c0d1ea5..5dafeb6b6 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -246,9 +246,8 @@ void System::set_led_speed(uint32_t speed) { void System::system_check() { static uint32_t last_system_check_ = 0; - uint32_t currentMillis = millis(); - if (!last_system_check_ || ((uint32_t)(currentMillis - last_system_check_) >= SYSTEM_CHECK_FREQUENCY)) { - last_system_check_ = currentMillis; + if (!last_system_check_ || ((uint32_t)(uuid::get_uptime() - last_system_check_) >= SYSTEM_CHECK_FREQUENCY)) { + last_system_check_ = uuid::get_uptime(); #ifndef EMSESP_STANDALONE if ((WiFi.status() != WL_CONNECTED) || safe_mode()) { @@ -262,6 +261,7 @@ void System::system_check() { if (!EMSbus::bus_connected()) { system_healthy_ = false; set_led_speed(LED_WARNING_BLINK); // flash every 1/2 second from now on + LOG_ERROR(F("No connection to the EMS bus!")); } else { // if it was unhealthy but now we're better, make sure the LED is solid again cos we've been healed if (!system_healthy_) { @@ -278,9 +278,8 @@ void System::system_check() { void System::led_monitor() { static uint32_t led_last_blink_ = 0; - uint32_t currentMillis = millis(); - if (!led_last_blink_ || (uint32_t)(currentMillis - led_last_blink_) >= led_flash_speed_) { - led_last_blink_ = currentMillis; + if (!led_last_blink_ || (uint32_t)(uuid::get_uptime() - led_last_blink_) >= led_flash_speed_) { + led_last_blink_ = uuid::get_uptime(); // if bus_not_connected or network not connected, start flashing if (!system_healthy_) { diff --git a/src/system.h b/src/system.h index c56d4791c..b1396752f 100644 --- a/src/system.h +++ b/src/system.h @@ -74,9 +74,9 @@ class System { static uuid::syslog::SyslogService syslog_; #endif - static constexpr uint32_t SYSTEM_CHECK_FREQUENCY = 5000; // check every 5 seconds - static constexpr uint32_t LED_WARNING_BLINK = 1000; // pulse to show no connection - static constexpr uint32_t LED_WARNING_BLINK_FAST = 100; // flash quickly for boot up sequence or safe-mode + static constexpr uint32_t SYSTEM_CHECK_FREQUENCY = 10000; // check every 10 seconds + static constexpr uint32_t LED_WARNING_BLINK = 1000; // pulse to show no connection, 1 sec + static constexpr uint32_t LED_WARNING_BLINK_FAST = 100; // flash quickly for boot up sequence or safe-mode // internal LED #if defined(ESP8266) diff --git a/src/telegram.cpp b/src/telegram.cpp index 64bd3946f..3a3990281 100644 --- a/src/telegram.cpp +++ b/src/telegram.cpp @@ -200,9 +200,10 @@ void RxService::flush_rx_queue() { rx_telegram_id_ = 0; } -// start and initialize the Rx incoming buffer. Not currently used. +// start and initialize the Rx incoming buffer void RxService::start() { // LOG_DEBUG(F("RxStart")); + // function not currently used } // Rx loop, run as many times as you can @@ -210,11 +211,10 @@ void RxService::start() { void RxService::loop() { #ifndef EMSESP_STANDALONE // give rx some breathing space - uint32_t time_now = millis(); - if ((time_now - last_rx_check_) < RX_LOOP_WAIT) { + if ((uuid::get_uptime() - last_rx_check_) < RX_LOOP_WAIT) { return; } - last_rx_check_ = time_now; + last_rx_check_ = uuid::get_uptime(); #endif while (!rx_telegrams_.empty()) { @@ -235,6 +235,7 @@ void RxService::loop() { // add a new rx telegram object // data is the whole telegram, assuming last byte holds the CRC +// length includes the CRC // for EMS+ the type_id has the value + 256. We look for these type of telegrams with F7, F9 and FF in 3rd byte void RxService::add(uint8_t * data, uint8_t length) { // validate the CRC @@ -263,7 +264,7 @@ void RxService::add(uint8_t * data, uint8_t length) { uint8_t dest = data[1] & 0x7F; // strip MSB, don't care if its read or write for processing uint8_t offset = data[3]; // offset is always 4th byte - uint16_t type_id; // this could be 2 bytes for ems+ + uint16_t type_id = 0; // this could be 2 bytes for ems+ uint8_t * message_data; uint8_t message_length; @@ -274,15 +275,21 @@ void RxService::add(uint8_t * data, uint8_t length) { message_data = data + 4; // message block starts at 5th byte message_length = length - 5; // remove 4 bytes header plus CRC } else { - // EMS 2.0 + // EMS 2.0 / EMS+ if (data[2] == 0xFF) { - type_id = (data[4] << 8) + data[5] + 256; - message_data = data + 6; // message block starts at 7th position - + // check for empty data + // special broadcast telegrams on ems+ have no data values, some even don't have a type ID if (length <= 7) { - message_length = 0; // special broadcast on ems+ have no data values + message_data = data; // bogus pointer, will not be used + message_length = 0; + if (length <= 5) { + type_id = 0; // has also an empty type_id + } else { + type_id = (data[4] << 8) + data[5] + 256; + } } else { message_length = length - 7; // remove 6 byte header plus CRC + message_data = data + 6; // message block starts at 7th position } } else { // its F9 or F7 @@ -297,20 +304,28 @@ void RxService::add(uint8_t * data, uint8_t length) { } } + // if we don't have a type_id, exit + if (type_id == 0) { + return; + } + // create the telegram auto telegram = std::make_shared(Telegram::Operation::RX, src, dest, type_id, offset, message_data, message_length); // check if queue is full - if (rx_telegrams_.size() >= maximum_rx_telegrams_) { + if (rx_telegrams_.size() >= MAX_RX_TELEGRAMS) { // rx_telegrams_overflow_ = true; rx_telegrams_.pop_front(); } - // add to queue, with timestamp + // add to queue LOG_DEBUG(F("New Rx [#%d] telegram, length %d"), rx_telegram_id_, message_length); rx_telegrams_.emplace_back(rx_telegram_id_++, std::move(telegram)); } +// +// Tx CODE here +// TxService::QueuedTxTelegram::QueuedTxTelegram(uint16_t id, std::shared_ptr && telegram) : id_(id) @@ -325,8 +340,6 @@ void TxService::flush_tx_queue() { // start and initialize Tx void TxService::start() { - // LOG_DEBUG(F("TxStart()")); - // grab the bus ID Settings settings; ems_bus_id(settings.ems_bus_id()); @@ -336,6 +349,19 @@ void TxService::start() { read_request(EMSdevice::EMS_TYPE_UBADevices, EMSdevice::EMS_DEVICE_ID_BOILER); } +// Tx loop +// here we check if the Tx is not full and report an error +void TxService::loop() { +#ifndef EMSESP_STANDALONE + if ((uuid::get_uptime() - last_tx_check_) > TX_LOOP_WAIT) { + last_tx_check_ = uuid::get_uptime(); + if ((tx_telegrams_.size() >= MAX_TX_TELEGRAMS - 1) && (EMSbus::bus_connected())) { + LOG_ERROR(F("Tx buffer full. Looks like Tx is not working?")); + } + } +#endif +} + // sends a 1 byte poll which is our own device ID void TxService::send_poll() { EMSuart::send_poll(ems_bus_id() ^ ems_mask()); @@ -352,9 +378,8 @@ void TxService::send() { // if there's nothing in the queue to send // optionally, send back a poll and quit - // for now I've disabled the poll if (tx_telegrams_.empty()) { - // send_poll(); + // send_poll(); // TODO commented out poll for now. should add back when stable. return; } @@ -482,7 +507,7 @@ void TxService::add(const uint8_t operation, const uint8_t dest, const uint16_t 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_) { + if (tx_telegrams_.size() >= MAX_TX_TELEGRAMS) { tx_telegrams_.pop_front(); } @@ -508,7 +533,7 @@ void TxService::add(uint8_t * data, const uint8_t length) { auto telegram = std::make_shared(Telegram::Operation::TX_RAW, src, dest, type_id, offset, message_data, message_length); // if the queue is full, make room but removing the last one - if (tx_telegrams_.size() >= maximum_tx_telegrams_) { + if (tx_telegrams_.size() >= MAX_TX_TELEGRAMS) { tx_telegrams_.pop_front(); } diff --git a/src/telegram.h b/src/telegram.h index 6153bc5f8..4bef66150 100644 --- a/src/telegram.h +++ b/src/telegram.h @@ -96,7 +96,7 @@ class EMSbus { static bool bus_connected() { #ifndef EMSESP_STANDALONE - if ((millis() - last_bus_activity_) > EMS_BUS_TIMEOUT) { + if ((uuid::get_uptime() - last_bus_activity_) > EMS_BUS_TIMEOUT) { bus_connected_ = false; } return bus_connected_; @@ -202,8 +202,6 @@ class RxService : public EMSbus { static constexpr uint32_t RX_LOOP_WAIT = 800; // delay in processing Rx queue uint32_t last_rx_check_ = 0; - size_t maximum_rx_telegrams_ = MAX_RX_TELEGRAMS; - // std::atomic rx_telegrams_overflow_{false}; uint8_t rx_telegram_id_ = 0; // queue counter @@ -224,6 +222,7 @@ class TxService : public EMSbus { ~TxService() = default; void start(); + void loop(); void send(); void add(const uint8_t operation, const uint8_t dest, const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length); @@ -292,8 +291,10 @@ class TxService : public EMSbus { private: static constexpr uint8_t MAXIMUM_TX_RETRIES = 3; - size_t maximum_tx_telegrams_ = MAX_TX_TELEGRAMS; - uint8_t tx_telegram_id_ = 0; // queue counter + uint8_t tx_telegram_id_ = 0; // queue counter + + static constexpr uint32_t TX_LOOP_WAIT = 10000; // when to check if Tx is up and running (10 sec) + uint32_t last_tx_check_ = 0; std::deque tx_telegrams_; diff --git a/src/test/test_data.h b/src/test/test_data.h index 793cc916e..de01ba0ea 100644 --- a/src/test/test_data.h +++ b/src/test/test_data.h @@ -7,8 +7,8 @@ void EMSESP::run_test(uuid::console::Shell & shell, const std::string & command) emsdevices.push_back(EMSFactory::add(EMSdevice::DeviceType::BOILER, EMSdevice::EMS_DEVICE_ID_BOILER, 0, "", "My Boiler", 0, 0)); // A fake response - UBADevices(0x07) - uint8_t t0[] = {0x08, 0x00, 0x07, 0x00, 0x0B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47}; - rxservice_.add(t0, sizeof(t0)); + uint8_t t[] = {0x08, 0x00, 0x07, 0x00, 0x0B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47}; + rxservice_.add(t, sizeof(t)); return; } @@ -28,8 +28,8 @@ void EMSESP::run_test(uuid::console::Shell & shell, const std::string & command) rxservice_.loop(); // simulate getting version information back from an unknown device - uint8_t u1[] = {0x09, 0x0B, 0x02, 0x00, 0x59, 0x01, 0x02, 0x56}; - rxservice_.add(u1, sizeof(u1)); + uint8_t t[] = {0x09, 0x0B, 0x02, 0x00, 0x59, 0x01, 0x02, 0x56}; + rxservice_.add(t, sizeof(t)); rxservice_.loop(); return; @@ -63,9 +63,9 @@ void EMSESP::run_test(uuid::console::Shell & shell, const std::string & command) // SM100Monitor - type 0x0362 EMS+ - for SM100 and SM200 // B0 0B FF 00 02 62 00 44 02 7A 80 00 80 00 80 00 80 00 80 00 80 00 00 7C 80 00 80 00 80 00 80 - uint8_t s1[] = {0xB0, 0x0B, 0xFF, 00, 0x02, 0x62, 00, 0x44, 0x02, 0x7A, 0x80, 00, 0x80, 0x00, 0x80, 00, - 0x80, 00, 0x80, 00, 0x80, 00, 00, 0x7C, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 0x89}; - rxservice_.add(s1, sizeof(s1)); + uint8_t t[] = {0xB0, 0x0B, 0xFF, 00, 0x02, 0x62, 00, 0x44, 0x02, 0x7A, 0x80, 00, 0x80, 0x00, 0x80, 00, + 0x80, 00, 0x80, 00, 0x80, 00, 00, 0x7C, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 0x89}; + rxservice_.add(t, sizeof(t)); rxservice_.loop(); shell.loop_all(); @@ -84,9 +84,9 @@ void EMSESP::run_test(uuid::console::Shell & shell, const std::string & command) // RCPLUSStatusMessage_HC1(0x01A5) // 98 00 FF 00 01 A5 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03 - uint8_t c1[] = {0x98, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24, 0x03, - 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03, 0x13}; - rxservice_.add(c1, sizeof(c1)); + uint8_t t[] = {0x98, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24, 0x03, + 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03, 0x13}; + rxservice_.add(t, sizeof(t)); rxservice_.loop(); shell.loop_all(); @@ -231,7 +231,7 @@ void EMSESP::run_test(uuid::console::Shell & shell, const std::string & command) // simulate sending a read request // uint8_t t16[] = {0x44, 0x45, 0x46, 0x47}; // Me -> Thermostat, (0x91), telegram: 0B 17 91 05 44 45 46 47 (#data=4) - //txservice_.add(Telegram::Operation::TX_RAW, 0x17, 0x91, 0x05, t16, sizeof(t16)); + // txservice_.add(Telegram::Operation::TX_RAW, 0x17, 0x91, 0x05, t16, sizeof(t16)); send_read_request(0x91, 0x17); // txservice_.show_tx_queue(); @@ -240,19 +240,19 @@ void EMSESP::run_test(uuid::console::Shell & shell, const std::string & command) incoming_telegram(poll, 1); // incoming Rx - uint8_t t17[] = {0x17, 0x08, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A}; - incoming_telegram(t17, sizeof(t17)); + uint8_t t1[] = {0x17, 0x08, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A}; + incoming_telegram(t1, sizeof(t1)); rxservice_.loop(); // Simulate adding a Poll - should send retry incoming_telegram(poll, 1); show_emsbus(shell); - uint8_t t18[] = {0x21, 0x22}; - send_write_request(0x91, 0x17, 0x00, t18, sizeof(t18), 0); + uint8_t t2[] = {0x21, 0x22}; + send_write_request(0x91, 0x17, 0x00, t2, sizeof(t2), 0); show_emsbus(shell); - incoming_telegram(t17, sizeof(t17)); + incoming_telegram(t1, sizeof(t1)); txservice_.flush_tx_queue(); @@ -324,9 +324,17 @@ void EMSESP::run_test(uuid::console::Shell & shell, const std::string & command) if (command == "rx2") { // incoming Rx - uint8_t t71[] = {0x1B, 0x5B, 0xFD, 0x2D, 0x9E, 0x3A, 0xB6, 0xE5, 0x02, 0x20, 0x33, 0x30, 0x32, 0x3A, 0x20, 0x5B, 0x73, - 0xFF, 0xFF, 0xCB, 0xDF, 0xB7, 0xA7, 0xB5, 0x67, 0x77, 0x77, 0xE4, 0xFF, 0xFD, 0x77, 0xFF, 0xD1}; - incoming_telegram(t71, sizeof(t71)); + uint8_t t[] = {0x1B, 0x5B, 0xFD, 0x2D, 0x9E, 0x3A, 0xB6, 0xE5, 0x02, 0x20, 0x33, 0x30, 0x32, 0x3A, 0x20, 0x5B, 0x73, + 0xFF, 0xFF, 0xCB, 0xDF, 0xB7, 0xA7, 0xB5, 0x67, 0x77, 0x77, 0xE4, 0xFF, 0xFD, 0x77, 0xFF, 0xD1}; + incoming_telegram(t, sizeof(t)); + return; + } + + // https://github.com/proddy/EMS-ESP/issues/380#issuecomment-633663007 + if (command == "rx3") { + // incoming Rx + uint8_t t[] = {0x21, 0x0B, 0xFF, 0x00, 0xDA}; + incoming_telegram(t, sizeof(t)); return; } @@ -341,8 +349,8 @@ void EMSESP::run_test(uuid::console::Shell & shell, const std::string & command) // testing the UART tx command, without a queue if (command == "tx2") { - uint8_t tx[] = {0x0B, 0x88, 0x18, 0x00, 0x20, 0xD4}; // including CRC - EMSuart::transmit(tx, sizeof(tx)); + uint8_t t[] = {0x0B, 0x88, 0x18, 0x00, 0x20, 0xD4}; // including CRC + EMSuart::transmit(t, sizeof(t)); return; } diff --git a/src/uart/emsuart_esp8266.cpp b/src/uart/emsuart_esp8266.cpp index 6a738b59e..939c1f7d5 100644 --- a/src/uart/emsuart_esp8266.cpp +++ b/src/uart/emsuart_esp8266.cpp @@ -29,6 +29,9 @@ 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) { @@ -64,6 +67,12 @@ 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; + // LEGACY CODE + 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); @@ -81,6 +90,8 @@ void ICACHE_FLASH_ATTR EMSuart::emsuart_recvTask(os_event_t * events) { * 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)); @@ -139,16 +150,163 @@ void EMSuart::send_poll(uint8_t data) { * buf contains the CRC and len is #bytes including the CRC */ EMSUART_STATUS ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) { - if (len) { + if (len == 0) { + return EMS_TX_STATUS_OK; // nothing to send + } + + // new code from Michael. See https://github.com/proddy/EMS-ESP/issues/380 + if (tx_mode_ == EMS_TXMODE_NEW) { USC0(EMSUART_UART) &= ~(1 << UCBRK); // clear bit for (uint8_t i = 0; i < len; i++) { USF(EMSUART_UART) = buf[i]; } USC0(EMSUART_UART) |= (1 << UCBRK); // send at the end + + return EMS_TX_STATUS_OK; } - return EMS_TX_STATUS_OK; + + // EMS+ https://github.com/proddy/EMS-ESP/issues/23# + if (tx_mode_ == EMS_TXMODE_EMSPLUS) { // With extra tx delay for EMS+ + for (uint8_t i = 0; i < len; i++) { + USF(EMSUART_UART) = buf[i]; + delayMicroseconds(EMSUART_TX_BRK_WAIT); // 2070 + } + tx_brk(); // send + 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 + 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) + * - 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 + + // wait until BRK detected... + while (!(USIR(EMSUART_UART) & (1 << UIBD))) { + delayMicroseconds(EMSUART_BIT_TIME); + } + + USC0(EMSUART_UART) &= ~((1 << UCBRK) | (1 << UCLBE)); // disable loopback & clear + 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 } +/* + * 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 +} + +/* + * 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 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 +} } // namespace emsesp diff --git a/src/uart/emsuart_esp8266.h b/src/uart/emsuart_esp8266.h index b7bf94d99..72aee85e3 100644 --- a/src/uart/emsuart_esp8266.h +++ b/src/uart/emsuart_esp8266.h @@ -31,11 +31,23 @@ #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 +#define EMSUART_recvTaskQueueLen 10 // number of queued Rx triggers #define EMS_TXMODE_DEFAULT 1 #define EMS_TXMODE_EMSPLUS 2 #define EMS_TXMODE_HT3 3 +#define EMS_TXMODE_NEW 4 // for michael + +// LEGACY +#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) namespace emsesp { @@ -64,6 +76,9 @@ 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 diff --git a/src/version.h b/src/version.h index ab2be6267..7a4641f7b 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "2.0.0a7" +#define EMSESP_APP_VERSION "2.0.0a8"