diff --git a/src/console.cpp b/src/console.cpp index df5b9b270..6b1882883 100644 --- a/src/console.cpp +++ b/src/console.cpp @@ -431,6 +431,10 @@ void Console::start() { shell->maximum_log_messages(100); // default is 50 shell->start(); shell->log_level(uuid::log::Level::DEBUG); // order is: err, warning, notice, info, trace, debug, all + +#if defined(EMSESP_STANDALONE) + shell->add_flags(CommandFlags::ADMIN); +#endif } // always start the telnet service, except on an ESP8266 diff --git a/src/device_library.h b/src/device_library.h index 7f40fdae5..828a02ad2 100644 --- a/src/device_library.h +++ b/src/device_library.h @@ -39,50 +39,6 @@ {208, DeviceType::BOILER, F("Logamax plus/GB192/Condens GC9000"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {234, DeviceType::BOILER, F("Logamax Plus GB122"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, -// Solar Modules - 0x30 -{ 73, DeviceType::SOLAR, F("SM10"), DeviceFlags::EMS_DEVICE_FLAG_SM10}, -{101, DeviceType::SOLAR, F("ISM1"), DeviceFlags::EMS_DEVICE_FLAG_SM100}, -{162, DeviceType::SOLAR, F("SM50"), DeviceFlags::EMS_DEVICE_FLAG_SM100}, -{163, DeviceType::SOLAR, F("SM100"), DeviceFlags::EMS_DEVICE_FLAG_SM100}, -{164, DeviceType::SOLAR, F("SM200"), DeviceFlags::EMS_DEVICE_FLAG_SM100}, - -// Mixing Modules - 0x20-0x27 for HC, 0x28-0x29 for WWC -{ 69, DeviceType::MIXING, F("MM10"), DeviceFlags::EMS_DEVICE_FLAG_MM10}, -{159, DeviceType::MIXING, F("MM50"), DeviceFlags::EMS_DEVICE_FLAG_MMPLUS}, -{160, DeviceType::MIXING, F("MM100"), DeviceFlags::EMS_DEVICE_FLAG_MMPLUS}, -{161, DeviceType::MIXING, F("MM200"), DeviceFlags::EMS_DEVICE_FLAG_MMPLUS}, - -// Heat Pumps - 0x38 -{200, DeviceType::HEATPUMP, F("HP Module"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, -{252, DeviceType::HEATPUMP, F("HP Module"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, - -// Switches - 0x11 -{ 71, DeviceType::SWITCH, F("WM10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, - -// Controllers - 0x09 / 0x10 / 0x50 -{ 68, DeviceType::CONTROLLER, F("BC10/RFM20"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 -{ 89, DeviceType::CONTROLLER, F("BC10 GB142"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 -{ 95, DeviceType::CONTROLLER, F("HT3"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 -{114, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 -{125, DeviceType::CONTROLLER, F("BC25"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 -{152, DeviceType::CONTROLLER, F("Controller"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 -{169, DeviceType::CONTROLLER, F("BC40"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 -{190, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 -{207, DeviceType::CONTROLLER, F("Sense II/CS200"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x10 -{209, DeviceType::CONTROLLER, F("ErP"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 -{218, DeviceType::CONTROLLER, F("M200/RFM200"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x50 -{230, DeviceType::CONTROLLER, F("BC Base"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 -{241, DeviceType::CONTROLLER, F("Condens 5000i"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 - -// Connect devices - 0x02 -{171, DeviceType::CONNECT, F("OpenTherm Converter"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, -{205, DeviceType::CONNECT, F("Moduline Easy Connect"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, -{206, DeviceType::CONNECT, F("Easy Connect"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, - -// Gateways - 0x48 / 0x18 -{ 94, DeviceType::GATEWAY, F("RFM20 Remote Base for RC20RF"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x18 -{189, DeviceType::GATEWAY, F("KM200"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x48 - // Thermostat - not currently supporting write operations, like the Easy/100 types - 0x18 {202, DeviceType::THERMOSTAT, F("Logamatic TC100/Moduline Easy"), DeviceFlags::EMS_DEVICE_FLAG_EASY | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write {203, DeviceType::THERMOSTAT, F("EasyControl CT200"), DeviceFlags::EMS_DEVICE_FLAG_EASY | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write @@ -109,8 +65,53 @@ {107, DeviceType::THERMOSTAT, F("FR100"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_2}, // older model {108, DeviceType::THERMOSTAT, F("FR110"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_2}, // older model {111, DeviceType::THERMOSTAT, F("FR10"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS}, -{147, DeviceType::THERMOSTAT, F("FR50"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS}, +{147, DeviceType::THERMOSTAT, F("FR50"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS}, {191, DeviceType::THERMOSTAT, F("FR120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS}, -{192, DeviceType::THERMOSTAT, F("FW120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS} +{192, DeviceType::THERMOSTAT, F("FW120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS}, + +// Solar Modules - 0x30 +{ 73, DeviceType::SOLAR, F("SM10"), DeviceFlags::EMS_DEVICE_FLAG_SM10}, +{101, DeviceType::SOLAR, F("ISM1"), DeviceFlags::EMS_DEVICE_FLAG_SM100}, +{162, DeviceType::SOLAR, F("SM50"), DeviceFlags::EMS_DEVICE_FLAG_SM100}, +{163, DeviceType::SOLAR, F("SM100"), DeviceFlags::EMS_DEVICE_FLAG_SM100}, +{164, DeviceType::SOLAR, F("SM200"), DeviceFlags::EMS_DEVICE_FLAG_SM100}, + +// Mixing Modules - 0x20-0x27 for HC, 0x28-0x29 for WWC +{ 69, DeviceType::MIXING, F("MM10"), DeviceFlags::EMS_DEVICE_FLAG_MM10}, +{159, DeviceType::MIXING, F("MM50"), DeviceFlags::EMS_DEVICE_FLAG_MMPLUS}, +{160, DeviceType::MIXING, F("MM100"), DeviceFlags::EMS_DEVICE_FLAG_MMPLUS}, +{161, DeviceType::MIXING, F("MM200"), DeviceFlags::EMS_DEVICE_FLAG_MMPLUS}, + +// Heat Pumps - 0x38 +{200, DeviceType::HEATPUMP, F("HP Module"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, +{252, DeviceType::HEATPUMP, F("HP Module"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, + +// Controllers - 0x09 / 0x10 / 0x50 +{ 68, DeviceType::CONTROLLER, F("BC10/RFM20"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 +{ 89, DeviceType::CONTROLLER, F("BC10 GB142"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 +{ 95, DeviceType::CONTROLLER, F("HT3"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 +{114, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 +{125, DeviceType::CONTROLLER, F("BC25"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 +{152, DeviceType::CONTROLLER, F("Controller"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 +{169, DeviceType::CONTROLLER, F("BC40"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 +{190, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 +{207, DeviceType::CONTROLLER, F("Sense II/CS200"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x10 +{209, DeviceType::CONTROLLER, F("ErP"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 +{218, DeviceType::CONTROLLER, F("M200/RFM200"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x50 +{230, DeviceType::CONTROLLER, F("BC Base"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 +{241, DeviceType::CONTROLLER, F("Condens 5000i"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 + +// Connect devices - 0x02 +{171, DeviceType::CONNECT, F("OpenTherm Converter"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, +{205, DeviceType::CONNECT, F("Moduline Easy Connect"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, +{206, DeviceType::CONNECT, F("Easy Connect"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, + +// Switches - 0x11 +{ 71, DeviceType::SWITCH, F("WM10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, + +// Gateways - 0x48 / 0x18 +{ 94, DeviceType::GATEWAY, F("RFM20 Remote Base for RC20RF"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x18 +{189, DeviceType::GATEWAY, F("KM200"), DeviceFlags::EMS_DEVICE_FLAG_NONE} // 0x48 + // clang-format on diff --git a/src/devices/helpers.cpp b/src/devices/helpers.cpp new file mode 100644 index 000000000..c0c176559 --- /dev/null +++ b/src/devices/helpers.cpp @@ -0,0 +1,307 @@ +/* + * EMS-ESP - https://github.com/proddy/EMS-ESP + * Copyright 2019 Paul Derbyshire + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "helpers.h" +#include "telegram.h" // for EMS_VALUE_* settings + +namespace emsesp { + +// like itoa but for hex, and quicker +char * Helpers::hextoa(char * result, const uint8_t value) { + char * p = result; + uint8_t nib1 = (value >> 4) & 0x0F; + uint8_t nib2 = (value >> 0) & 0x0F; + *p++ = nib1 < 0xA ? '0' + nib1 : 'A' + nib1 - 0xA; + *p++ = nib2 < 0xA ? '0' + nib2 : 'A' + nib2 - 0xA; + *p = '\0'; // null terminate just in case + return result; +} + +/* + * itoa for 2 byte integers + * written by Lukás Chmela, Released under GPLv3. http://www.strudel.org.uk/itoa/ version 0.4 + */ +char * Helpers::itoa(char * result, int16_t value, const uint8_t base) { + // check that the base if valid + if (base < 2 || base > 36) { + *result = '\0'; + return result; + } + + char * ptr = result, *ptr1 = result, tmp_char; + int16_t tmp_value; + + do { + tmp_value = value; + value /= base; + *ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz"[35 + (tmp_value - value * base)]; + } while (value); + + // Apply negative sign + if (tmp_value < 0) { + *ptr++ = '-'; + } + *ptr-- = '\0'; + while (ptr1 < ptr) { + tmp_char = *ptr; + *ptr-- = *ptr1; + *ptr1++ = tmp_char; + } + return result; +} + +// for decimals 0 to 99, printed as a 2 char string +char * Helpers::smallitoa(char * result, const uint8_t value) { + result[0] = ((value / 10) == 0) ? '0' : (value / 10) + '0'; + result[1] = (value % 10) + '0'; + result[2] = '\0'; + return result; +} + +// for decimals 0 to 999, printed as a string +char * Helpers::smallitoa(char * result, const uint16_t value) { + result[0] = ((value / 100) == 0) ? '0' : (value / 100) + '0'; + result[1] = (((value % 100) / 10) == 0) ? '0' : ((value % 100) / 10) + '0'; + result[2] = (value % 10) + '0'; + result[3] = '\0'; + return result; +} + +// convert unsigned int (single byte) to text value and returns it +// format: 2=divide by 2, 10=divide by 10, 255=handle as a Boolean +char * Helpers::render_value(char * result, uint8_t value, uint8_t format) { + result[0] = '\0'; + + // check if its a boolean + if (format == EMS_VALUE_BOOL) { + if (value == EMS_VALUE_BOOL_OFF) { + strlcpy(result, "off", 5); + } else if (value == EMS_VALUE_BOOL_NOTSET) { + strlcpy(result, "?", 5); + } else { + strlcpy(result, "on", 5); // assume on. could have value 0x01 or 0xFF + } + return result; + } + + if (value == EMS_VALUE_UINT_NOTSET) { + strlcpy(result, "?", 5); + return (result); + } + + static char s2[5] = {0}; + + switch (format) { + case 2: + strlcpy(result, itoa(s2, value >> 1, 10), 5); + strlcat(result, ".", 5); + strlcat(result, ((value & 0x01) ? "5" : "0"), 5); + break; + + case 10: + strlcpy(result, itoa(s2, value / 10, 10), 5); + strlcat(result, ".", 5); + strlcat(result, itoa(s2, value % 10, 10), 5); + break; + + default: + itoa(result, value, 10); + break; + } + + return result; +} + +// convert float to char +// format is the precision +char * Helpers::render_value(char * result, const float value, const uint8_t format) { + long p[] = {0, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000}; + + char * ret = result; + long whole = (long)value; + Helpers::itoa(result, whole, 10); + while (*result != '\0') { + result++; + } + *result++ = '.'; + long decimal = abs((long)((value - whole) * p[format])); + itoa(result, decimal, 10); + + return ret; +} + +// convert short (two bytes) to text string and returns string +// decimals: 0 = no division, 10=divide value by 10, 2=divide by 2, 100=divide value by 100 +// negative values are assumed stored as 1-compliment (https://medium.com/@LeeJulija/how-integers-are-stored-in-memory-using-twos-complement-5ba04d61a56c) +char * Helpers::render_value(char * result, const int16_t value, const uint8_t format) { + result[0] = '\0'; + + // remove errors or invalid values, 0x7D00 and higher + if ((value == EMS_VALUE_SHORT_NOTSET) || (value == EMS_VALUE_SHORT_INVALID) || (value == EMS_VALUE_USHORT_NOTSET)) { + strlcpy(result, "?", 10); + return result; + } + + // just print it if mo conversion required + if ((format == 0) || (format == 1)) { + itoa(result, value, 10); + return result; + } + + int16_t new_value = value; + + // check for negative values + if (new_value < 0) { + strlcpy(result, "-", 10); + new_value *= -1; // convert to positive + } else { + strlcpy(result, "", 10); + } + + // do floating point + char s2[10] = {0}; + if (format == 2) { + // divide by 2 + strlcat(result, itoa(s2, new_value / 2, 10), 10); + strlcat(result, ".", 10); + strlcat(result, ((new_value & 0x01) ? "5" : "0"), 10); + + } else { + strlcat(result, itoa(s2, new_value / format, 10), 10); + strlcat(result, ".", 10); + strlcat(result, itoa(s2, new_value % format, 10), 10); + } + + return result; +} + +// convert unsigned short (two bytes) to text string and prints it +// format: 0 = no division, 10=divide value by 10, 2=divide by 2, 100=divide value by 100 +char * Helpers::render_value(char * result, const uint16_t value, const uint8_t format) { + result[0] = '\0'; + + if ((value == EMS_VALUE_USHORT_NOTSET) || (value == EMS_VALUE_USHORT_INVALID)) { + strlcpy(result, "?", 10); + return result; + } + + return (render_value(result, (int16_t)value, format)); // use same code, force it to a signed int +} + +// convert signed byte to text string and prints it +// format: 0 = no division, 10=divide value by 10, 2=divide by 2, 100=divide value by 100 +char * Helpers::render_value(char * result, const int8_t value, const uint8_t format) { + result[0] = '\0'; + + if (value == EMS_VALUE_INT_NOTSET) { + strlcpy(result, "?", 10); + return result; + } + + return (render_value(result, (int16_t)value, format)); // use same code, force it to a signed int +} + +// render long (4 byte) unsigned values +// format = 0 for normal, any other value for divide by format +char * Helpers::render_value(char * result, const uint32_t value, const uint8_t format) { + result[0] = '\0'; + + if ((value == EMS_VALUE_ULONG_NOTSET) || (value == EMS_VALUE_ULONG_INVALID)) { + strlcpy(result, "?", 10); + return (result); + } + + static char s[20] = {0}; + +#ifndef EMSESP_STANDALONE + if (format <= 1) { + strlcat(result, ltoa(value, s, 10), 20); + } else { + strlcat(result, ltoa(value / format, s, 10), 20); + strlcat(result, ".", 2); + strlcat(result, ltoa(value % format, s, 10), 20); + } + +#else + strncat(result, itoa(s, value / format, 10), 20); +#endif + + return result; +} + +// creates string of hex values from an arrray of bytes +std::string Helpers::data_to_hex(const uint8_t * data, const uint8_t length) { + if (length == 0) { + return uuid::read_flash_string(F("")); + } + + std::string str(160, '\0'); + char buffer[4]; + char * p = &str[0]; + for (uint8_t i = 0; i < length; i++) { + Helpers::hextoa(buffer, data[i]); + *p++ = buffer[0]; + *p++ = buffer[1]; + *p++ = ' '; // space + } + *--p = '\0'; // null terminate just in case, loosing the trailing space + + return str; +} + + +// 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) { + // get current character then increment + char byte = *hex++; + // transform hex character to the 4bit equivalent number, using the ascii table indexes + if (byte >= '0' && byte <= '9') + byte = byte - '0'; + else if (byte >= 'a' && byte <= 'f') + byte = byte - 'a' + 10; + else if (byte >= 'A' && byte <= 'F') + byte = byte - 'A' + 10; + else + return 0; // error + // shift 4 to make space for new digit, and add the 4 bits of the new digit + val = (val << 4) | (byte & 0xF); + } + return val; +} + +// quick char to long +uint16_t Helpers::atoint(const char * value) { + unsigned int x = 0; + while (*value != '\0') { + x = (x * 10) + (*value - '0'); + ++value; + } + return x; +} + +// rounds a number to 2 decimal places +// example: round2(3.14159) -> 3.14 +double Helpers::round2(double value) { + return (int)(value * 100 + 0.5) / 100.0; +} + + +} // namespace emsesp diff --git a/src/emsesp.cpp b/src/emsesp.cpp index 2bb15bbdd..38d9b9fa5 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -63,7 +63,7 @@ bool EMSESP::ems_read_only_; uint32_t EMSESP::last_fetch_ = 0; #ifdef EMSESP_DEBUG -#include "test/test_data.h" // used with the 'test' command, under su/admin +#include "test_data.h" // used with the 'test' command, under su/admin #endif // for a specific EMS device go and request data values @@ -172,7 +172,7 @@ void EMSESP::show_emsbus(uuid::console::Shell & shell) { // and for each associated EMS device go and request data values void EMSESP::show_values(uuid::console::Shell & shell) { if (sensor_devices().empty() && emsdevices.empty()) { - shell.printfln(F("No data available from devices to show")); + shell.printfln(F("No data collected from EMS devices. Try 'refresh'.")); return; } @@ -454,15 +454,16 @@ void EMSESP::add_context_menus() { // for each associated EMS device go and get its system information void EMSESP::show_devices(uuid::console::Shell & shell) { - /* +#ifdef EMSESP_DEBUG // for debugging ony // prints out DeviceType (UNKNOWN = 0, SERVICEKEY, BOILER, THERMOSTAT, MIXING, SOLAR, HEATPUMP, GATEWAY, SWITCH, CONTROLLER, CONNECT) - shell.printf(F("Registered EMS device handlers:")); + // from emsdevice.h + shell.printf(F("(debug) Registered EMS device handlers:")); for (const auto & pair : EMSFactory::device_handlers()) { shell.printf(F(" %d"), pair.first); } shell.println(); - */ +#endif if (emsdevices.empty()) { shell.printfln(F("No EMS devices detected. Try scanning using the 'scan devices' command.")); @@ -514,7 +515,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std:: } } - // look up the rest of the details using the product_id and create the new device + // look up the rest of the details using the product_id and create the new device object // then send a request to the device to get the version and any other info we may have bool found = false; for (const auto & device : device_library_) { @@ -531,9 +532,8 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std:: 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 { - 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); + LOG_DEBUG(F("Adding new device with device ID 0x%02X with product ID %d and version %s"), device_id, product_id, version.c_str()); + // go and fetch its data, fetch_device_values(device_id); } @@ -542,7 +542,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std:: // send a read request, passing it into to the Tx Service, with no offset void EMSESP::send_read_request(const uint16_t type_id, const uint8_t dest) { - txservice_.read_request(type_id, dest, 0); // no offset + txservice_.read_request(type_id, dest, 0); // 0 = no offset } // sends write request diff --git a/src/emsesp.h b/src/emsesp.h index 15c0e69ea..eef42c675 100644 --- a/src/emsesp.h +++ b/src/emsesp.h @@ -42,11 +42,12 @@ #include "system.h" #include "sensors.h" #include "console.h" -#include "boiler.h" #include "shower.h" #include "roomcontrol.h" -#define LOG_TRACE_WATCH_NONE 0 // no watch set +#include "devices/boiler.h" + +#define LOG_TRACE_WATCH_NONE 0 // no watch id set namespace emsesp { @@ -62,6 +63,8 @@ class EMSESP { #ifdef EMSESP_DEBUG static void run_test(uuid::console::Shell & shell, const std::string & command); // only for testing static void dummy_mqtt_commands(const char * message); + static void rx_telegram(const std::vector & data); + static void uart_telegram(const std::vector & rx_data); #endif static bool process_telegram(std::shared_ptr telegram); diff --git a/src/emsfactory.h b/src/emsfactory.h index 3f42310a7..ede08d8fe 100644 --- a/src/emsfactory.h +++ b/src/emsfactory.h @@ -32,10 +32,9 @@ auto registry_##derivedClass = ConcreteEMSFactory(device_type); \ } - namespace emsesp { -class EMSdevice; // forward declaration +class EMSdevice; // forward declaration, for gcc linking class EMSFactory { public: diff --git a/src/test/test_data.h b/src/test_data.h similarity index 66% rename from src/test/test_data.h rename to src/test_data.h index ea8d96f91..f65e9a8db 100644 --- a/src/test/test_data.h +++ b/src/test_data.h @@ -7,18 +7,13 @@ 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 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; + rx_telegram({0x08, 0x00, 0x07, 0x00, 0x0B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); } if (command == "boiler2") { // question: do we need to set the mask? std::string version("1.2.3"); add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline - - return; } if (command == "unknown") { @@ -28,23 +23,33 @@ 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 t[] = {0x09, 0x0B, 0x02, 0x00, 0x59, 0x01, 0x02, 0x56}; - rxservice_.add(t, sizeof(t)); - rxservice_.loop(); - - return; + rx_telegram({0x09, 0x0B, 0x02, 0x00, 0x59, 0x01, 0x02}); } if (command == "unknown2") { // simulate getting version information back from an unknown device - uint8_t t[] = {0x09, 0x0B, 0x02, 0x00, 0x5A, 0x01, 0x02, 0x5A}; // product id is 90 - rxservice_.add(t, sizeof(t)); - rxservice_.loop(); - - return; + rx_telegram({0x09, 0x0B, 0x02, 0x00, 0x5A, 0x01, 0x02}); // product id is 90 which doesn't exist } - if (command == "thermostats") { + if ((command == "gateway") || (command == "g")) { + // add 0x48 KM200, via a version command + rx_telegram({0x48, 0x0B, 0x02, 0x00, 0xBD, 0x04, 0x06, 00, 00, 00, 00, 00, 00, 00}); + + // Boiler(0x08) -> All(0x00), UBADevices(0x07), data: 09 01 00 00 00 00 00 00 01 00 00 00 00 + // check: make sure 0x48 is not detected again ! + rx_telegram({0x08, 0x00, 0x07, 0x00, 0x09, 01, 00, 00, 00, 00, 00, 00, 01, 00, 00, 00, 00}); + + // add thermostat - Thermostat: RC300/RC310/Moduline 3000/CW400/Sense II (DeviceID:0x10, ProductID:158, Version:03.03) ** master device ** + std::string version("01.03"); + add_device(0x10, 158, version, EMSdevice::Brand::BUDERUS); + rxservice_.loop(); + + // simulate incoming telegram + // Thermostat(0x10) -> 48(0x48), ?(0x26B), data: 6B 08 4F 00 00 00 02 00 00 00 02 00 03 00 03 00 03 + rx_telegram({0x10, 0x48, 0xFF, 00, 01, 0x6B, 00, 0x6B, 0x08, 0x4F, 00, 00, 00, 02, 00, 00, 00, 02, 00, 03, 00, 03, 00, 03}); + } + + if (command == "thermostat") { shell.printfln(F("Testing adding devices on the EMS bus...")); // create some fake devices @@ -56,8 +61,6 @@ void EMSESP::run_test(uuid::console::Shell & shell, const std::string & command) // add_device(0x17, 254, version, EMSdevice::Brand::BUDERUS); // test unknown product_id add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355 - - return; } if (command == "solar") { @@ -72,14 +75,8 @@ 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 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(); - - return; + rx_telegram({0xB0, 0x0B, 0xFF, 00, 0x02, 0x62, 00, 0x44, 0x02, 0x7A, 0x80, 00, 0x80, 0x00, 0x80, 00, + 0x80, 00, 0x80, 00, 0x80, 00, 00, 0x7C, 0x80, 00, 0x80, 00, 0x80, 00, 0x80}); } if (command == "cr100") { @@ -93,10 +90,8 @@ 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 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(); + uart_telegram({0x98, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24, + 0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03}); shell.loop_all(); rxservice_.loop(); @@ -115,9 +110,6 @@ void EMSESP::run_test(uuid::console::Shell & shell, const std::string & command) shell.loop_all(); txservice_.send(); // send it to UART - shell.loop_all(); - - return; } if (command == "rx") { @@ -125,79 +117,59 @@ void EMSESP::run_test(uuid::console::Shell & shell, const std::string & command) // fake telegrams. length includes CRC // Boiler -> Me, UBAMonitorFast(0x18), telegram: 08 00 18 00 00 02 5A 73 3D 0A 10 65 40 02 1A 80 00 01 E1 01 76 0E 3D 48 00 C9 44 02 00 (#data=25) - uint8_t t1[] = {0x08, 0x00, 0x18, 0x00, 0x00, 0x02, 0x5A, 0x73, 0x3D, 0x0A, 0x10, 0x65, 0x40, 0x02, 0x1A, - 0x80, 0x00, 0x01, 0xE1, 0x01, 0x76, 0x0E, 0x3D, 0x48, 0x00, 0xC9, 0x44, 0x02, 0x00, 0xFB}; - rxservice_.add(t1, sizeof(t1)); + uart_telegram({0x08, 0x00, 0x18, 0x00, 0x00, 0x02, 0x5A, 0x73, 0x3D, 0x0A, 0x10, 0x65, 0x40, 0x02, 0x1A, + 0x80, 0x00, 0x01, 0xE1, 0x01, 0x76, 0x0E, 0x3D, 0x48, 0x00, 0xC9, 0x44, 0x02, 0x00}); // Boiler -> Thermostat, UBAParameterWW(0x33), telegram: 08 97 33 00 23 24 (#data=2) - uint8_t t2[] = {0x08, 0x97, 0x33, 0x00, 0x23, 0x24, 0x5B}; - rxservice_.add(t2, sizeof(t2)); + uart_telegram({0x08, 0x97, 0x33, 0x00, 0x23, 0x24}); // Boiler -> Me, UBAParameterWW(0x33), telegram: 08 0B 33 00 08 FF 34 FB 00 28 00 00 46 00 FF FF 00 (#data=13) - uint8_t t21[] = {0x08, 0x0B, 0x33, 0x00, 0x08, 0xFF, 0x34, 0xFB, 0x00, 0x28, 0x00, 0x00, 0x46, 0x00, 0xFF, 0xFF, 0x00, 0x5F}; - rxservice_.add(t21, sizeof(t21)); - - // write return code 01, 04 - uint8_t t3[] = {0x04}; - rxservice_.add(t3, sizeof(t3)); + uart_telegram({0x08, 0x0B, 0x33, 0x00, 0x08, 0xFF, 0x34, 0xFB, 0x00, 0x28, 0x00, 0x00, 0x46, 0x00, 0xFF, 0xFF, 0x00}); // Thermostat -> Me, RC20StatusMessage(0x91), telegram: 17 0B 91 05 44 45 46 47 (#data=4) - uint8_t t4[] = {0x17, 0x0B, 0x91, 0x05, 0x44, 0x45, 0x46, 0x47, 0x8E}; - rxservice_.add(t4, sizeof(t4)); + uart_telegram({0x17, 0x0B, 0x91, 0x05, 0x44, 0x45, 0x46, 0x47}); // bad CRC - corrupt telegram - CRC should be 0x8E uint8_t t5[] = {0x17, 0x0B, 0x91, 0x05, 0x44, 0x45, 0x46, 0x47, 0x99}; rxservice_.add(t5, sizeof(t5)); // simulating a Tx record - uint8_t t7[] = {0x0B, 0x88, 0x07, 0x00, 0x20, 0xA8}; - rxservice_.add(t7, sizeof(t7)); + uart_telegram({0x0B, 0x88, 0x07, 0x00, 0x20}); // Version Boiler - uint8_t t8[] = {0x08, 0x0B, 0x02, 0x00, 0x7B, 0x06, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x43}; - rxservice_.add(t8, sizeof(t8)); + uart_telegram({0x08, 0x0B, 0x02, 0x00, 0x7B, 0x06, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04}); // Version Thermostat, device_id 0x11 - uint8_t t9[] = {0x11, 0x0B, 0x02, 0x00, 0x4D, 0x03, 0x03, 0x55}; - rxservice_.add(t9, sizeof(t9)); + uart_telegram({0x11, 0x0B, 0x02, 0x00, 0x4D, 0x03, 0x03}); // Thermostat -> all, telegram: 10 00 FF 00 01 A5 00 D7 21 00 00 00 00 30 01 84 01 01 03 01 84 01 F1 00 00 11 01 00 08 63 00 // 0x1A5 test ems+ - uint8_t t10a[] = {0x10, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xD7, 0x21, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x84, - 0x01, 0x01, 0x03, 0x01, 0x84, 0x01, 0xF1, 0x00, 0x00, 0x11, 0x01, 0x00, 0x08, 0x63, 0x00, 0xCC}; - rxservice_.add(t10a, sizeof(t10a)); + uart_telegram({0x10, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xD7, 0x21, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x84, + 0x01, 0x01, 0x03, 0x01, 0x84, 0x01, 0xF1, 0x00, 0x00, 0x11, 0x01, 0x00, 0x08, 0x63, 0x00}); // setting temp from 21.5 to 19.9C - uint8_t t10b[] = {0x10, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xC7, 0x21, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x84, - 0x01, 0x01, 0x03, 0x01, 0x84, 0x01, 0xF1, 0x00, 0x00, 0x11, 0x01, 0x00, 0x08, 0x63, 0x00, 0x46}; - rxservice_.add(t10b, sizeof(t10b)); + uart_telegram({0x10, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xC7, 0x21, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x84, + 0x01, 0x01, 0x03, 0x01, 0x84, 0x01, 0xF1, 0x00, 0x00, 0x11, 0x01, 0x00, 0x08, 0x63, 0x00}); // Thermostat -> Boiler, UBAFlags(0x35), telegram: 17 08 35 00 11 00 (#data=2) - uint8_t t13[] = {0x17, 0x08, 0x35, 0x00, 0x11, 0x00, 0xC1}; - rxservice_.add(t13, sizeof(t13)); + uart_telegram({0x17, 0x08, 0x35, 0x00, 0x11, 0x00}); // Thermostat -> Boiler, UBASetPoints(0x1A), telegram: 17 08 1A 00 00 00 00 00 (#data=4) - uint8_t t14[] = {0x17, 0x08, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A}; - rxservice_.add(t14, sizeof(t14)); + uart_telegram({0x17, 0x08, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00}); // Thermostat -> Me, RC20Set(0xA8), telegram: 17 0B A8 00 01 00 FF F6 01 06 00 01 0D 01 00 FF FF 01 02 02 02 00 00 05 1F 05 1F 02 0E 00 FF (#data=27) - uint8_t t15[] = {0x17, 0x0B, 0xA8, 0x00, 0x01, 0x00, 0xFF, 0xF6, 0x01, 0x06, 0x00, 0x01, 0x0D, 0x01, 0x00, 0xFF, - 0xFF, 0x01, 0x02, 0x02, 0x02, 0x00, 0x00, 0x05, 0x1F, 0x05, 0x1F, 0x02, 0x0E, 0x00, 0xFF, 0xDF}; - rxservice_.add(t15, sizeof(t15)); + uart_telegram({0x17, 0x0B, 0xA8, 0x00, 0x01, 0x00, 0xFF, 0xF6, 0x01, 0x06, 0x00, 0x01, 0x0D, 0x01, 0x00, 0xFF, + 0xFF, 0x01, 0x02, 0x02, 0x02, 0x00, 0x00, 0x05, 0x1F, 0x05, 0x1F, 0x02, 0x0E, 0x00, 0xFF}); // Boiler(0x08) -> All(0x00), UBAMonitorWW(0x34), data: 36 01 A5 80 00 21 00 00 01 00 01 3E 8D 03 77 91 00 80 00 - uint8_t t16[] = {0x08, 0x00, 0x34, 0x00, 0x36, 0x01, 0xA5, 0x80, 0x00, 0x21, 0x00, 0x00, - 0x01, 0x00, 0x01, 0x3E, 0x8D, 0x03, 0x77, 0x91, 0x00, 0x80, 0x00, 0x3E}; - rxservice_.add(t16, sizeof(t16)); - - return; + uart_telegram( + {0x08, 0x00, 0x34, 0x00, 0x36, 0x01, 0xA5, 0x80, 0x00, 0x21, 0x00, 0x00, 0x01, 0x00, 0x01, 0x3E, 0x8D, 0x03, 0x77, 0x91, 0x00, 0x80, 0x00}); } if (command == "send") { shell.printfln(F("Sending to Tx...")); show_emsbus(shell); txservice_.send(); // send it to UART - return; } if (command == "tx") { @@ -228,8 +200,6 @@ void EMSESP::run_test(uuid::console::Shell & shell, const std::string & command) txservice_.send(); // send it to UART txservice_.flush_tx_queue(); - - return; } if (command == "poll") { @@ -249,9 +219,7 @@ void EMSESP::run_test(uuid::console::Shell & shell, const std::string & command) incoming_telegram(poll, 1); // incoming Rx - uint8_t t1[] = {0x17, 0x08, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A}; - incoming_telegram(t1, sizeof(t1)); - rxservice_.loop(); + uart_telegram({0x17, 0x08, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A}); // Simulate adding a Poll - should send retry incoming_telegram(poll, 1); @@ -261,11 +229,7 @@ void EMSESP::run_test(uuid::console::Shell & shell, const std::string & command) send_write_request(0x91, 0x17, 0x00, t2, sizeof(t2), 0); show_emsbus(shell); - incoming_telegram(t1, sizeof(t1)); - txservice_.flush_tx_queue(); - - return; } if (command == "mqtt1") { @@ -311,8 +275,6 @@ void EMSESP::run_test(uuid::console::Shell & shell, const std::string & command) // txservice_.show_tx_queue(); publish_all_values(); - - return; } if (command == "poll2") { @@ -327,24 +289,18 @@ void EMSESP::run_test(uuid::console::Shell & shell, const std::string & command) show_emsbus(shell); txservice_.flush_tx_queue(); - - return; } if (command == "rx2") { // incoming Rx - 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; + uart_telegram({0x1B, 0x5B, 0xFD, 0x2D, 0x9E, 0x3A, 0xB6, 0xE5, 0x02, 0x20, 0x33, 0x30, 0x32, 0x3A, 0x20, 0x5B, + 0x73, 0xFF, 0xFF, 0xCB, 0xDF, 0xB7, 0xA7, 0xB5, 0x67, 0x77, 0x77, 0xE4, 0xFF, 0xFD, 0x77, 0xFF}); } // https://github.com/proddy/EMS-ESP/issues/380#issuecomment-633663007 if (command == "rx3") { // incoming Rx - uint8_t t[] = {0x21, 0x0B, 0xFF, 0x00, 0xDA}; - incoming_telegram(t, sizeof(t)); - return; + uart_telegram({0x21, 0x0B, 0xFF, 0x00}); } if (command == "mqtt2") { @@ -352,22 +308,18 @@ void EMSESP::run_test(uuid::console::Shell & shell, const std::string & command) for (uint8_t i = 0; i < 30; i++) { Mqtt::subscribe("cmd", dummy_mqtt_commands); } - return; } - // testing the UART tx command, without a queue if (command == "tx2") { uint8_t t[] = {0x0B, 0x88, 0x18, 0x00, 0x20, 0xD4}; // including CRC EMSuart::transmit(t, sizeof(t)); - return; } // send read request with offset if (command == "offset") { // send_read_request(0x18, 0x08); txservice_.read_request(0x18, 0x08, 27); // no offset - return; } if (command == "mixing") { @@ -382,21 +334,33 @@ void EMSESP::run_test(uuid::console::Shell & shell, const std::string & command) rxservice_.loop(); // WWC1 on 0x29 - uint8_t m1[] = {0xA9, 0x00, 0xFF, 0x00, 0x02, 0x32, 0x02, 0x6C, 0x00, 0x3C, 0x00, 0x3C, 0x3C, 0x46, 0x02, 0x03, 0x03, 0x00, 0x3C, 0x57}; - rxservice_.add(m1, sizeof(m1)); - rxservice_.loop(); + rx_telegram({0xA9, 0x00, 0xFF, 0x00, 0x02, 0x32, 0x02, 0x6C, 0x00, 0x3C, 0x00, 0x3C, 0x3C, 0x46, 0x02, 0x03, 0x03, 0x00, 0x3C}); // WWC2 on 0x28 - uint8_t m2[] = {0xA8, 0x00, 0xFF, 0x00, 0x02, 0x31, 0x02, 0x35, 0x00, 0x3C, 0x00, 0x3C, 0x3C, 0x46, 0x02, 0x03, 0x03, 0x00, 0x3C, 0x71}; - rxservice_.add(m2, sizeof(m2)); - rxservice_.loop(); - - shell.loop_all(); - - return; + rx_telegram({0xA8, 0x00, 0xFF, 0x00, 0x02, 0x31, 0x02, 0x35, 0x00, 0x3C, 0x00, 0x3C, 0x3C, 0x46, 0x02, 0x03, 0x03, 0x00, 0x3C}); } - shell.printfln(F("[Test] Unknown test command")); + // finally dump to console + shell.loop_all(); +} + +// simulates a telegram in the Rx queue, but without the CRC which is added automatically +void EMSESP::rx_telegram(const std::vector & rx_data) { + uint8_t len = rx_data.size(); + uint8_t data[50]; + std::copy(rx_data.begin(), rx_data.end(), data); + data[len] = rxservice_.calculate_crc(rx_data.data(), len); + rxservice_.add(data, len + 1); + rxservice_.loop(); +} + +// simulates a telegram straight from UART, but without the CRC which is added automatically +void EMSESP::uart_telegram(const std::vector & rx_data) { + uint8_t len = rx_data.size(); + uint8_t data[50]; + std::copy(rx_data.begin(), rx_data.end(), data); + data[len] = rxservice_.calculate_crc(rx_data.data(), len); + incoming_telegram(data, len + 1); } #pragma GCC diagnostic push diff --git a/src/uart/emsuart_esp32.cpp b/src/uart/emsuart_esp32.cpp index 7cef6668b..c2369217a 100644 --- a/src/uart/emsuart_esp32.cpp +++ b/src/uart/emsuart_esp32.cpp @@ -99,7 +99,7 @@ void EMSuart::start(uint8_t tx_mode) { drop_next_rx = true; buf_handle = xRingbufferCreate(128, RINGBUF_TYPE_NOSPLIT); ESP_ERROR_CHECK(uart_isr_register(EMSUART_UART, emsuart_rx_intr_handler, NULL, ESP_INTR_FLAG_IRAM, &uart_handle)); - xTaskCreate(emsuart_recvTask, "emsuart_recvTask", 2048, NULL, configMAX_PRIORITIES -1, NULL); + xTaskCreate(emsuart_recvTask, "emsuart_recvTask", 2048, NULL, configMAX_PRIORITIES - 1, NULL); EMS_UART.int_ena.brk_det = 1; // activate only break } diff --git a/src/uart/emsuart_esp8266.cpp b/src/uart/emsuart_esp8266.cpp index ae317922d..dc029029f 100644 --- a/src/uart/emsuart_esp8266.cpp +++ b/src/uart/emsuart_esp8266.cpp @@ -250,7 +250,7 @@ EMSUART_STATUS ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) { return EMS_TX_STATUS_OK; // nothing to send } #ifdef EMSESP_DEBUG - LOG_INFO(F("UART Responsetime: %d ms"),uuid::get_uptime() - emsRxTime); + LOG_INFO(F("UART Responsetime: %d ms"), uuid::get_uptime() - emsRxTime); #endif // if ((uuid::get_uptime() - emsRxTime) > EMS_RX_TO_TX_TIMEOUT)) { // send allowed within 20 ms // return EMS_TX_WTD_TIMEOUT;