From 94100585abd75935e99fc7ebd06455b1bbbabc3a Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 1 Oct 2022 13:56:36 +0200 Subject: [PATCH 01/41] [P044] Uncrustify format source --- src/_P044_P1WifiGateway.ino | 287 +++++++++++---------- src/src/PluginStructs/P044_data_struct.cpp | 89 +++---- src/src/PluginStructs/P044_data_struct.h | 24 +- 3 files changed, 206 insertions(+), 194 deletions(-) diff --git a/src/_P044_P1WifiGateway.ino b/src/_P044_P1WifiGateway.ino index a60cbe165..a6fbe7255 100644 --- a/src/_P044_P1WifiGateway.ino +++ b/src/_P044_P1WifiGateway.ino @@ -1,6 +1,7 @@ #include "_Plugin_Helper.h" #ifdef USES_P044 -//#################################### Plugin 044: P1WifiGateway ######################################## + +// #################################### Plugin 044: P1WifiGateway ######################################## // // based on P020 Ser2Net, extended by Ronald Leenes romix/-at-/macuser.nl // @@ -8,24 +9,25 @@ // Wemos D1 mini (see http://wemos.cc) and // P1 wifi gateway shield (see http://www.esp8266thingies.nl for print design and kits) // See also http://domoticx.com/p1-poort-slimme-meter-hardware/ -//####################################################################################################### +// ####################################################################################################### +/** Changelog: + * 2022-10-01 tonhuisman: Format source using Uncrustify + */ + +# include "src/Helpers/_Plugin_Helper_serial.h" +# include "src/PluginStructs/P044_data_struct.h" +# include + +# define PLUGIN_044 +# define PLUGIN_ID_044 44 +# define PLUGIN_NAME_044 "Communication - P1 Wifi Gateway" -#include "src/Helpers/_Plugin_Helper_serial.h" -#include "src/PluginStructs/P044_data_struct.h" -#include - -#define PLUGIN_044 -#define PLUGIN_ID_044 44 -#define PLUGIN_NAME_044 "Communication - P1 Wifi Gateway" - - -#define P044_WIFI_SERVER_PORT ExtraTaskSettings.TaskDevicePluginConfigLong[0] -#define P044_BAUDRATE ExtraTaskSettings.TaskDevicePluginConfigLong[1] -#define P044_RX_WAIT PCONFIG(0) -#define P044_SERIAL_CONFIG PCONFIG(1) -#define P044_RESET_TARGET_PIN CONFIG_PIN1 - +# define P044_WIFI_SERVER_PORT ExtraTaskSettings.TaskDevicePluginConfigLong[0] +# define P044_BAUDRATE ExtraTaskSettings.TaskDevicePluginConfigLong[1] +# define P044_RX_WAIT PCONFIG(0) +# define P044_SERIAL_CONFIG PCONFIG(1) +# define P044_RESET_TARGET_PIN CONFIG_PIN1 boolean Plugin_044(uint8_t function, struct EventStruct *event, String& string) @@ -34,161 +36,170 @@ boolean Plugin_044(uint8_t function, struct EventStruct *event, String& string) switch (function) { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_044; - Device[deviceCount].Type = DEVICE_TYPE_SINGLE; - Device[deviceCount].Custom = true; - Device[deviceCount].TimerOption = false; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_044; + Device[deviceCount].Type = DEVICE_TYPE_SINGLE; + Device[deviceCount].Custom = true; + Device[deviceCount].TimerOption = false; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_044); - break; - } + { + string = F(PLUGIN_NAME_044); + break; + } case PLUGIN_WEBFORM_LOAD: - { - LoadTaskSettings(event->TaskIndex); - addFormNumericBox(F("TCP Port"), F("p044_port"), P044_WIFI_SERVER_PORT, 0); - addFormNumericBox(F("Baud Rate"), F("p044_baud"), P044_BAUDRATE, 0); + { + LoadTaskSettings(event->TaskIndex); + addFormNumericBox(F("TCP Port"), F("p044_port"), P044_WIFI_SERVER_PORT, 0); + addFormNumericBox(F("Baud Rate"), F("p044_baud"), P044_BAUDRATE, 0); - uint8_t serialConfChoice = serialHelper_convertOldSerialConfig(P044_SERIAL_CONFIG); - serialHelper_serialconfig_webformLoad(event, serialConfChoice); + uint8_t serialConfChoice = serialHelper_convertOldSerialConfig(P044_SERIAL_CONFIG); + serialHelper_serialconfig_webformLoad(event, serialConfChoice); - // FIXME TD-er: Why isn't this using the normal pin selection functions? - addFormPinSelect(PinSelectPurpose::Generic, F("Reset target after boot"), F("taskdevicepin1"), P044_RESET_TARGET_PIN); + // FIXME TD-er: Why isn't this using the normal pin selection functions? + addFormPinSelect(PinSelectPurpose::Generic, F("Reset target after boot"), F("taskdevicepin1"), P044_RESET_TARGET_PIN); - addFormNumericBox(F("RX Receive Timeout (mSec)"), F("p044_rxwait"), P044_RX_WAIT, 0); + addFormNumericBox(F("RX Receive Timeout (mSec)"), F("p044_rxwait"), P044_RX_WAIT, 0); - success = true; - break; - } + success = true; + break; + } case PLUGIN_WEBFORM_SAVE: - { - LoadTaskSettings(event->TaskIndex); - P044_WIFI_SERVER_PORT = getFormItemInt(F("p044_port")); - P044_BAUDRATE = getFormItemInt(F("p044_baud")); - P044_RX_WAIT = getFormItemInt(F("p044_rxwait")); - P044_SERIAL_CONFIG = serialHelper_serialconfig_webformSave(); + { + LoadTaskSettings(event->TaskIndex); + P044_WIFI_SERVER_PORT = getFormItemInt(F("p044_port")); + P044_BAUDRATE = getFormItemInt(F("p044_baud")); + P044_RX_WAIT = getFormItemInt(F("p044_rxwait")); + P044_SERIAL_CONFIG = serialHelper_serialconfig_webformSave(); - success = true; - break; - } + success = true; + break; + } case PLUGIN_INIT: - { - pinMode(P044_STATUS_LED, OUTPUT); - digitalWrite(P044_STATUS_LED, 0); + { + pinMode(P044_STATUS_LED, OUTPUT); + digitalWrite(P044_STATUS_LED, 0); - LoadTaskSettings(event->TaskIndex); - if ((P044_WIFI_SERVER_PORT == 0) || (P044_BAUDRATE == 0)) { - clearPluginTaskData(event->TaskIndex); - break; - } + LoadTaskSettings(event->TaskIndex); - // try to reuse to keep webserver running - P044_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr != task && task->isInit()) { - // It was already created and initialzed - // So don't recreate to keep the webserver running. - } else { - initPluginTaskData(event->TaskIndex, new (std::nothrow) P044_Task()); - task = static_cast(getPluginTaskData(event->TaskIndex)); - } - if (nullptr == task) { - break; - } - - int rxPin; - int txPin; - // FIXME TD-er: Must use proper pin settings and standard ESPEasySerial wrapper - ESPeasySerialType::getSerialTypePins(ESPEasySerialPort::serial0, rxPin, txPin); - uint8_t serialconfig = serialHelper_convertOldSerialConfig(P044_SERIAL_CONFIG); - task->serialBegin(ESPEasySerialPort::not_set, rxPin, txPin, P044_BAUDRATE, serialconfig); - task->startServer(P044_WIFI_SERVER_PORT); - - if (!task->isInit()) { - clearPluginTaskData(event->TaskIndex); - break; - } - - if (validGpio(P044_RESET_TARGET_PIN)) { - pinMode(P044_RESET_TARGET_PIN, OUTPUT); - digitalWrite(P044_RESET_TARGET_PIN, LOW); - delay(500); - digitalWrite(P044_RESET_TARGET_PIN, HIGH); - pinMode(P044_RESET_TARGET_PIN, INPUT_PULLUP); - } - - task->blinkLED(); - if (P044_BAUDRATE == 115200) { - #ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("P1 : DSMR version 4 meter, CRC on")); - #endif - task->CRCcheck = true; - } else { - #ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("P1 : DSMR version 4 meter, CRC off")); - #endif - task->CRCcheck = false; - } - - success = true; + if ((P044_WIFI_SERVER_PORT == 0) || (P044_BAUDRATE == 0)) { + clearPluginTaskData(event->TaskIndex); break; } + // try to reuse to keep webserver running + P044_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != task) && task->isInit()) { + // It was already created and initialzed + // So don't recreate to keep the webserver running. + } else { + initPluginTaskData(event->TaskIndex, new (std::nothrow) P044_Task()); + task = static_cast(getPluginTaskData(event->TaskIndex)); + } + + if (nullptr == task) { + break; + } + + int rxPin; + int txPin; + + // FIXME TD-er: Must use proper pin settings and standard ESPEasySerial wrapper + ESPeasySerialType::getSerialTypePins(ESPEasySerialPort::serial0, rxPin, txPin); + uint8_t serialconfig = serialHelper_convertOldSerialConfig(P044_SERIAL_CONFIG); + task->serialBegin(ESPEasySerialPort::not_set, rxPin, txPin, P044_BAUDRATE, serialconfig); + task->startServer(P044_WIFI_SERVER_PORT); + + if (!task->isInit()) { + clearPluginTaskData(event->TaskIndex); + break; + } + + if (validGpio(P044_RESET_TARGET_PIN)) { + pinMode(P044_RESET_TARGET_PIN, OUTPUT); + digitalWrite(P044_RESET_TARGET_PIN, LOW); + delay(500); + digitalWrite(P044_RESET_TARGET_PIN, HIGH); + pinMode(P044_RESET_TARGET_PIN, INPUT_PULLUP); + } + + task->blinkLED(); + + if (P044_BAUDRATE == 115200) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("P1 : DSMR version 4 meter, CRC on")); + # endif // ifndef BUILD_NO_DEBUG + task->CRCcheck = true; + } else { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("P1 : DSMR version 4 meter, CRC off")); + # endif // ifndef BUILD_NO_DEBUG + task->CRCcheck = false; + } + + success = true; + break; + } + case PLUGIN_EXIT: - { - success = true; - break; - } + { + success = true; + break; + } case PLUGIN_ONCE_A_SECOND: - { - P044_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr == task) { - break; - } - task->checkServer(); - success = true; + { + P044_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr == task) { break; } + task->checkServer(); + success = true; + break; + } case PLUGIN_TEN_PER_SECOND: - { - P044_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr == task) { - break; - } - if (task->hasClientConnected()) { - task->discardClientIn(); - } - task->checkBlinkLED(); - success = true; + { + P044_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr == task) { break; } + if (task->hasClientConnected()) { + task->discardClientIn(); + } + task->checkBlinkLED(); + success = true; + break; + } + case PLUGIN_SERIAL_IN: - { - P044_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr == task) { - break; - } - if (task->hasClientConnected()) { - task->handleSerialIn(event); - } else { - task->discardSerialIn(); - } - success = true; + { + P044_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr == task) { break; } + if (task->hasClientConnected()) { + task->handleSerialIn(event); + } else { + task->discardSerialIn(); + } + success = true; + break; + } } return success; } + #endif // USES_P044 diff --git a/src/src/PluginStructs/P044_data_struct.cpp b/src/src/PluginStructs/P044_data_struct.cpp index 59de51d23..81b6c7749 100644 --- a/src/src/PluginStructs/P044_data_struct.cpp +++ b/src/src/PluginStructs/P044_data_struct.cpp @@ -2,15 +2,15 @@ #ifdef USES_P044 -#include "../ESPEasyCore/Serial.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" +# include "../ESPEasyCore/Serial.h" +# include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../Globals/EventQueue.h" +# include "../Globals/EventQueue.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/Misc.h" +# include "../Helpers/ESPEasy_Storage.h" +# include "../Helpers/Misc.h" -#define P044_RX_WAIT PCONFIG(0) +# define P044_RX_WAIT PCONFIG(0) P044_Task::P044_Task() { @@ -23,11 +23,11 @@ P044_Task::~P044_Task() { } bool P044_Task::serverActive(WiFiServer *server) { -#if defined(ESP8266) + # if defined(ESP8266) return nullptr != server && server->status() != CLOSED; -#elif defined(ESP32) + # elif defined(ESP32) return nullptr != server && *server; -#endif // if defined(ESP8266) + # endif // if defined(ESP8266) } void P044_Task::startServer(uint16_t portnumber) { @@ -78,15 +78,15 @@ bool P044_Task::hasClientConnected() { if (P1GatewayClient) { P1GatewayClient.stop(); } P1GatewayClient = P1GatewayServer->available(); - #ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + # ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS // See: https://github.com/espressif/arduino-esp32/pull/6676 P1GatewayClient.setTimeout((CONTROLLER_CLIENTTIMEOUT_DFLT + 500) / 1000); // in seconds!!!! Client *pClient = &P1GatewayClient; pClient->setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT); - #else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS - P1GatewayClient.setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT); // in msec as it should be! - #endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + # else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + P1GatewayClient.setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT); // in msec as it should be! + # endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS addLog(LOG_LEVEL_INFO, F("P1 : Client connected!")); } @@ -157,11 +157,12 @@ bool P044_Task::checkDatagram() const { const int checksumStartIndex = endChar + 1; - #ifdef PLUGIN_044_DEBUG - for (unsigned int cnt = 0; cnt < serial_buffer.length(); ++cnt) { - serialPrint(serial_buffer.substring(cnt, 1)); - } - #endif + # ifdef PLUGIN_044_DEBUG + + for (unsigned int cnt = 0; cnt < serial_buffer.length(); ++cnt) { + serialPrint(serial_buffer.substring(cnt, 1)); + } + # endif // ifdef PLUGIN_044_DEBUG // calculate the CRC and check if it equals the hexadecimal one attached to the datagram unsigned int crc = CRC16(serial_buffer, checksumStartIndex); @@ -224,14 +225,14 @@ void P044_Task::serialBegin(const ESPEasySerialPort port, int16_t rxPin, int16_t P1EasySerial = new (std::nothrow) ESPeasySerial(port, rxPin, txPin); if (nullptr != P1EasySerial) { -#if defined(ESP8266) + # if defined(ESP8266) P1EasySerial->begin(baud, (SerialConfig)config); -#elif defined(ESP32) + # elif defined(ESP32) P1EasySerial->begin(baud, config); -#endif // if defined(ESP8266) -# ifndef BUILD_NO_DEBUG + # endif // if defined(ESP8266) + # ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_DEBUG, F("P1 : Serial opened")); -#endif + # endif // ifndef BUILD_NO_DEBUG } } state = ParserState::WAITING; @@ -241,9 +242,9 @@ void P044_Task::serialEnd() { if (nullptr != P1EasySerial) { delete P1EasySerial; P1EasySerial = nullptr; -# ifndef BUILD_NO_DEBUG + # ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_DEBUG, F("P1 : Serial closed")); -#endif + # endif // ifndef BUILD_NO_DEBUG } } @@ -271,9 +272,9 @@ void P044_Task::handleSerialIn(struct EventStruct *event) { if (done) { P1GatewayClient.print(serial_buffer); P1GatewayClient.flush(); -# ifndef BUILD_NO_DEBUG + # ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_DEBUG, F("P1 : data send!")); -#endif + # endif // ifndef BUILD_NO_DEBUG blinkLED(); if (Settings.UseRules) @@ -288,10 +289,10 @@ void P044_Task::handleSerialIn(struct EventStruct *event) { bool P044_Task::handleChar(char ch) { if (serial_buffer.length() >= P044_DATAGRAM_MAX_SIZE - 2) { // room for cr/lf -# ifndef BUILD_NO_DEBUG + # ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_DEBUG, F("P1 : Error: Buffer overflow, discarded input.")); -#endif - state = ParserState::WAITING; // reset + # endif // ifndef BUILD_NO_DEBUG + state = ParserState::WAITING; // reset } bool done = false; @@ -320,9 +321,9 @@ bool P044_Task::handleChar(char ch) { done = true; } } else if (ch == P044_DATAGRAM_START_CHAR) { -# ifndef BUILD_NO_DEBUG + # ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_DEBUG, F("P1 : Error: Start detected, discarded input.")); -#endif + # endif // ifndef BUILD_NO_DEBUG state = ParserState::WAITING; // reset return handleChar(ch); } else { @@ -346,15 +347,15 @@ bool P044_Task::handleChar(char ch) { if (invalid) { // input is not a datagram char -# ifndef BUILD_NO_DEBUG + # ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_DEBUG, F("P1 : Error: DATA corrupt, discarded input.")); -#endif + # endif // ifndef BUILD_NO_DEBUG - #ifdef PLUGIN_044_DEBUG - serialPrint(F("faulty char>")); - serialPrint(String(ch)); - serialPrintln("<"); - #endif + # ifdef PLUGIN_044_DEBUG + serialPrint(F("faulty char>")); + serialPrint(String(ch)); + serialPrintln("<"); + # endif // ifdef PLUGIN_044_DEBUG state = ParserState::WAITING; // reset } @@ -367,13 +368,13 @@ bool P044_Task::handleChar(char ch) { addChar('\r'); addChar('\n'); } else if (CRCcheck) { -# ifndef BUILD_NO_DEBUG + # ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_DEBUG, F("P1 : Error: Invalid CRC, dropped data")); -#endif + # endif // ifndef BUILD_NO_DEBUG } else { -# ifndef BUILD_NO_DEBUG + # ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_DEBUG, F("P1 : Error: Invalid datagram, dropped data")); -#endif + # endif // ifndef BUILD_NO_DEBUG } state = ParserState::WAITING; // prepare for next one } @@ -394,4 +395,4 @@ bool P044_Task::isInit() const { return nullptr != P1GatewayServer && nullptr != P1EasySerial; } -#endif +#endif // ifdef USES_P044 diff --git a/src/src/PluginStructs/P044_data_struct.h b/src/src/PluginStructs/P044_data_struct.h index 62469d5c8..49f44f106 100644 --- a/src/src/PluginStructs/P044_data_struct.h +++ b/src/src/PluginStructs/P044_data_struct.h @@ -5,15 +5,15 @@ #ifdef USES_P044 -#include +# include // #define PLUGIN_044_DEBUG // extra logging in serial out -#define P044_STATUS_LED 12 -#define P044_CHECKSUM_LENGTH 4 -#define P044_DATAGRAM_START_CHAR '/' -#define P044_DATAGRAM_END_CHAR '!' -#define P044_DATAGRAM_MAX_SIZE 2048u +# define P044_STATUS_LED 12 +# define P044_CHECKSUM_LENGTH 4 +# define P044_DATAGRAM_START_CHAR '/' +# define P044_DATAGRAM_END_CHAR '!' +# define P044_DATAGRAM_MAX_SIZE 2048u struct P044_Task : public PluginTaskData_base { @@ -70,10 +70,10 @@ struct P044_Task : public PluginTaskData_base { static bool validP1char(char ch); void serialBegin(const ESPEasySerialPort port, - int16_t rxPin, - int16_t txPin, - unsigned long baud, - uint8_t config); + int16_t rxPin, + int16_t txPin, + unsigned long baud, + uint8_t config); void serialEnd(); @@ -98,5 +98,5 @@ struct P044_Task : public PluginTaskData_base { size_t maxMessageSize = P044_DATAGRAM_MAX_SIZE / 4; }; -#endif -#endif +#endif // ifdef USES_P044 +#endif // ifndef PLUGINSTRUCTS_P044_DATA_STRUCT_H From 86dc1f87fdf006a9e13f1d4b841e358b51b08632 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 1 Oct 2022 21:42:40 +0200 Subject: [PATCH 02/41] [P044] Make Led pin configurable --- src/_P044_P1WifiGateway.ino | 120 +++++++++++++-------- src/src/PluginStructs/P044_data_struct.cpp | 54 +++++++--- src/src/PluginStructs/P044_data_struct.h | 16 ++- 3 files changed, 130 insertions(+), 60 deletions(-) diff --git a/src/_P044_P1WifiGateway.ino b/src/_P044_P1WifiGateway.ino index a6fbe7255..7c1128a40 100644 --- a/src/_P044_P1WifiGateway.ino +++ b/src/_P044_P1WifiGateway.ino @@ -10,7 +10,9 @@ // P1 wifi gateway shield (see http://www.esp8266thingies.nl for print design and kits) // See also http://domoticx.com/p1-poort-slimme-meter-hardware/ // ####################################################################################################### + /** Changelog: + * 2022-10-01 tonhuisman: Add Led configuration options (Enabled, Pin, Inverted), changed device configuration * 2022-10-01 tonhuisman: Format source using Uncrustify */ @@ -23,13 +25,6 @@ # define PLUGIN_NAME_044 "Communication - P1 Wifi Gateway" -# define P044_WIFI_SERVER_PORT ExtraTaskSettings.TaskDevicePluginConfigLong[0] -# define P044_BAUDRATE ExtraTaskSettings.TaskDevicePluginConfigLong[1] -# define P044_RX_WAIT PCONFIG(0) -# define P044_SERIAL_CONFIG PCONFIG(1) -# define P044_RESET_TARGET_PIN CONFIG_PIN1 - - boolean Plugin_044(uint8_t function, struct EventStruct *event, String& string) { boolean success = false; @@ -39,7 +34,7 @@ boolean Plugin_044(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_DEVICE_ADD: { Device[++deviceCount].Number = PLUGIN_ID_044; - Device[deviceCount].Type = DEVICE_TYPE_SINGLE; + Device[deviceCount].Type = DEVICE_TYPE_CUSTOM2; Device[deviceCount].Custom = true; Device[deviceCount].TimerOption = false; break; @@ -51,19 +46,59 @@ boolean Plugin_044(uint8_t function, struct EventStruct *event, String& string) break; } + case PLUGIN_SET_DEFAULTS: + { + P044_LED_PIN = P044_STATUS_LED; // Former default + break; + } + + case PLUGIN_WEBFORM_SHOW_GPIO_DESCR: + { + string = concat(F("RST: "), formatGpioLabel(P044_RESET_TARGET_PIN, false)); + string += event->String1; + string += concat(F("LED: "), formatGpioLabel((P044_LED_ENABLED & 0x7f) == 0 ? P044_LED_PIN : -1, false)); + + if ((P044_LED_INVERTED == 1) && ((P044_LED_ENABLED & 0x7f) == 0)) { + string += F(" (inv)"); + } + success = true; + break; + } + case PLUGIN_WEBFORM_LOAD: { LoadTaskSettings(event->TaskIndex); - addFormNumericBox(F("TCP Port"), F("p044_port"), P044_WIFI_SERVER_PORT, 0); - addFormNumericBox(F("Baud Rate"), F("p044_baud"), P044_BAUDRATE, 0); - uint8_t serialConfChoice = serialHelper_convertOldSerialConfig(P044_SERIAL_CONFIG); - serialHelper_serialconfig_webformLoad(event, serialConfChoice); + { // Serial settings + addFormSubHeader(F("Serial")); - // FIXME TD-er: Why isn't this using the normal pin selection functions? - addFormPinSelect(PinSelectPurpose::Generic, F("Reset target after boot"), F("taskdevicepin1"), P044_RESET_TARGET_PIN); + uint8_t serialConfChoice = serialHelper_convertOldSerialConfig(P044_SERIAL_CONFIG); + serialHelper_serialconfig_webformLoad(event, serialConfChoice); - addFormNumericBox(F("RX Receive Timeout (mSec)"), F("p044_rxwait"), P044_RX_WAIT, 0); + addFormNumericBox(F("Baud Rate"), F("pbaud"), P044_BAUDRATE, 0, 115200); + } + + { // Device settings + addFormSubHeader(F("Device")); + + addFormNumericBox(F("TCP Port"), F("pport"), P044_WIFI_SERVER_PORT, 0, 65535); + # ifndef LIMIT_BUILD_SIZE + addUnit(F("0..65535")); + # endif // ifndef LIMIT_BUILD_SIZE + + // FIXME TD-er: Why isn't this using the normal pin selection functions? + addFormPinSelect(PinSelectPurpose::Generic, F("Reset target after boot"), F("taskdevicepin1"), P044_RESET_TARGET_PIN); + + addFormNumericBox(F("RX Receive Timeout (mSec)"), F("prxwait"), P044_RX_WAIT, 0); + } + + { // Led settings + addFormSubHeader(F("Led")); + + addFormCheckBox(F("Led enabled"), F("pled"), (P044_LED_ENABLED & 0x7f) == 0); + addFormPinSelect(PinSelectPurpose::Generic, F("Led pin"), F("taskdevicepin2"), P044_LED_PIN); + addFormCheckBox(F("Led inverted"), F("pledinv"), P044_LED_INVERTED == 1); + } success = true; break; @@ -72,9 +107,11 @@ boolean Plugin_044(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_SAVE: { LoadTaskSettings(event->TaskIndex); - P044_WIFI_SERVER_PORT = getFormItemInt(F("p044_port")); - P044_BAUDRATE = getFormItemInt(F("p044_baud")); - P044_RX_WAIT = getFormItemInt(F("p044_rxwait")); + P044_WIFI_SERVER_PORT = getFormItemInt(F("pport")); + P044_BAUDRATE = getFormItemInt(F("pbaud")); + P044_RX_WAIT = getFormItemInt(F("prxwait")); + P044_LED_ENABLED = 0x80 + (isFormItemChecked(F("pled")) ? 0 : 1); // Invert + set 8th bit to confirm new settings have been saved + P044_LED_INVERTED = isFormItemChecked(F("pledinv")) ? 1 : 0; P044_SERIAL_CONFIG = serialHelper_serialconfig_webformSave(); success = true; @@ -83,8 +120,10 @@ boolean Plugin_044(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_INIT: { - pinMode(P044_STATUS_LED, OUTPUT); - digitalWrite(P044_STATUS_LED, 0); + if (((P044_LED_ENABLED & 0x7f) == 1) && (P044_LED_PIN != -1)) { + pinMode(P044_LED_PIN, OUTPUT); + digitalWrite(P044_LED_PIN, P044_LED_INVERTED == 1 ? 1 : 0); + } LoadTaskSettings(event->TaskIndex); @@ -100,7 +139,7 @@ boolean Plugin_044(uint8_t function, struct EventStruct *event, String& string) // It was already created and initialzed // So don't recreate to keep the webserver running. } else { - initPluginTaskData(event->TaskIndex, new (std::nothrow) P044_Task()); + initPluginTaskData(event->TaskIndex, new (std::nothrow) P044_Task(event)); task = static_cast(getPluginTaskData(event->TaskIndex)); } @@ -134,7 +173,7 @@ boolean Plugin_044(uint8_t function, struct EventStruct *event, String& string) if (P044_BAUDRATE == 115200) { # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("P1 : DSMR version 4 meter, CRC on")); + addLog(LOG_LEVEL_DEBUG, F("P1 : DSMR version 5 meter, CRC on")); # endif // ifndef BUILD_NO_DEBUG task->CRCcheck = true; } else { @@ -158,11 +197,10 @@ boolean Plugin_044(uint8_t function, struct EventStruct *event, String& string) { P044_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr == task) { - break; + if (nullptr != task) { + task->checkServer(); + success = true; } - task->checkServer(); - success = true; break; } @@ -170,15 +208,13 @@ boolean Plugin_044(uint8_t function, struct EventStruct *event, String& string) { P044_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr == task) { - break; + if (nullptr != task) { + if (task->hasClientConnected()) { + task->discardClientIn(); + } + task->checkBlinkLED(); + success = true; } - - if (task->hasClientConnected()) { - task->discardClientIn(); - } - task->checkBlinkLED(); - success = true; break; } @@ -186,16 +222,14 @@ boolean Plugin_044(uint8_t function, struct EventStruct *event, String& string) { P044_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr == task) { - break; + if (nullptr != task) { + if (task->hasClientConnected()) { + task->handleSerialIn(event); + } else { + task->discardSerialIn(); + } + success = true; } - - if (task->hasClientConnected()) { - task->handleSerialIn(event); - } else { - task->discardSerialIn(); - } - success = true; break; } } diff --git a/src/src/PluginStructs/P044_data_struct.cpp b/src/src/PluginStructs/P044_data_struct.cpp index 81b6c7749..104326bce 100644 --- a/src/src/PluginStructs/P044_data_struct.cpp +++ b/src/src/PluginStructs/P044_data_struct.cpp @@ -10,11 +10,15 @@ # include "../Helpers/ESPEasy_Storage.h" # include "../Helpers/Misc.h" -# define P044_RX_WAIT PCONFIG(0) - -P044_Task::P044_Task() { +P044_Task::P044_Task(struct EventStruct *event) { clearBuffer(); + + if (P044_LED_ENABLED & 0x80) { + _ledPin = P044_LED_PIN; // Default pin (12) is already initialized in P044_Task + } + _ledEnabled = (P044_LED_ENABLED & 0x7f) == 0; // Inverted setting and strip off new-settings bit + _ledInverted = P044_LED_INVERTED == 1; } P044_Task::~P044_Task() { @@ -25,9 +29,10 @@ P044_Task::~P044_Task() { bool P044_Task::serverActive(WiFiServer *server) { # if defined(ESP8266) return nullptr != server && server->status() != CLOSED; - # elif defined(ESP32) - return nullptr != server && *server; # endif // if defined(ESP8266) + # if defined(ESP32) + return nullptr != server && *server; + # endif // if defined(ESP32) } void P044_Task::startServer(uint16_t portnumber) { @@ -43,9 +48,11 @@ void P044_Task::startServer(uint16_t portnumber) { P1GatewayServer->begin(); if (serverActive(P1GatewayServer)) { - addLog(LOG_LEVEL_INFO, concat(F("P1 : WiFi server started at port "), portnumber)); + # ifndef P036_LIMIT_BUILD_SIZE + addLog(LOG_LEVEL_INFO, concat(F("P1 : WiFi server started at port "), static_cast(portnumber))); + # endif // ifndef P036_LIMIT_BUILD_SIZE } else { - addLog(LOG_LEVEL_ERROR, concat(F("P1 : WiFi server start failed at port "), portnumber) + F(", retrying...")); + addLog(LOG_LEVEL_ERROR, concat(F("P1 : WiFi server start failed at port "), static_cast(portnumber)) + F(", retrying...")); } } } @@ -56,7 +63,9 @@ void P044_Task::checkServer() { P1GatewayServer->begin(); if (serverActive(P1GatewayServer)) { + # ifndef LIMIT_BUILD_SIZE addLog(LOG_LEVEL_INFO, F("P1 : WiFi server started")); + # endif // ifndef LIMIT_BUILD_SIZE } } } @@ -66,7 +75,9 @@ void P044_Task::stopServer() { if (P1GatewayClient) { P1GatewayClient.stop(); } clientConnected = false; P1GatewayServer->close(); + # ifndef LIMIT_BUILD_SIZE addLog(LOG_LEVEL_INFO, F("P1 : WiFi server closed")); + # endif // ifndef LIMIT_BUILD_SIZE delete P1GatewayServer; P1GatewayServer = nullptr; } @@ -88,7 +99,9 @@ bool P044_Task::hasClientConnected() { P1GatewayClient.setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT); // in msec as it should be! # endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + # ifndef LIMIT_BUILD_SIZE addLog(LOG_LEVEL_INFO, F("P1 : Client connected!")); + # endif // ifndef LIMIT_BUILD_SIZE } if (P1GatewayClient.connected()) @@ -100,7 +113,9 @@ bool P044_Task::hasClientConnected() { if (clientConnected) // there was a client connected before... { clientConnected = false; + # ifndef LIMIT_BUILD_SIZE addLog(LOG_LEVEL_INFO, F("P1 : Client disconnected!")); + # endif // ifndef LIMIT_BUILD_SIZE } } return clientConnected; @@ -115,13 +130,15 @@ void P044_Task::discardClientIn() { } void P044_Task::blinkLED() { - blinkLEDStartTime = millis(); - digitalWrite(P044_STATUS_LED, 1); + if (_ledEnabled) { + blinkLEDStartTime = millis(); + digitalWrite(_ledPin, _ledInverted ? 0 : 1); + } } void P044_Task::checkBlinkLED() { - if ((blinkLEDStartTime > 0) && (timePassedSince(blinkLEDStartTime) >= 500)) { - digitalWrite(P044_STATUS_LED, 0); + if (_ledEnabled && (blinkLEDStartTime > 0) && (timePassedSince(blinkLEDStartTime) >= 500)) { + digitalWrite(_ledPin, _ledInverted ? 1 : 0); blinkLEDStartTime = 0; } } @@ -206,9 +223,9 @@ bool P044_Task::validP1char(char ch) { isAlphaNumeric(ch) || ch == '.' || ch == ' ' || - ch == '\\'|| // Single backslash, but escaped in C++ - ch == '\r'|| - ch == '\n'|| + ch == '\\' || // Single backslash, but escaped in C++ + ch == '\r' || + ch == '\n' || ch == '(' || ch == ')' || ch == '-' || @@ -256,9 +273,14 @@ void P044_Task::handleSerialIn(struct EventStruct *event) { do { if (P1EasySerial->available()) { - digitalWrite(P044_STATUS_LED, 1); + if (_ledEnabled) { + digitalWrite(_ledPin, _ledInverted ? 0 : 1); + } done = handleChar(P1EasySerial->read()); - digitalWrite(P044_STATUS_LED, 0); + + if (_ledEnabled) { + digitalWrite(_ledPin, _ledInverted ? 1 : 0); + } if (done) { break; } timeOut = RXWait; // if serial received, reset timeout counter diff --git a/src/src/PluginStructs/P044_data_struct.h b/src/src/PluginStructs/P044_data_struct.h index 49f44f106..944528104 100644 --- a/src/src/PluginStructs/P044_data_struct.h +++ b/src/src/PluginStructs/P044_data_struct.h @@ -9,6 +9,16 @@ // #define PLUGIN_044_DEBUG // extra logging in serial out +# define P044_WIFI_SERVER_PORT ExtraTaskSettings.TaskDevicePluginConfigLong[0] +# define P044_BAUDRATE ExtraTaskSettings.TaskDevicePluginConfigLong[1] +# define P044_RX_WAIT PCONFIG(0) +# define P044_SERIAL_CONFIG PCONFIG(1) +# define P044_RESET_TARGET_PIN CONFIG_PIN1 +# define P044_LED_PIN CONFIG_PIN2 +# define P044_LED_ENABLED PCONFIG(2) +# define P044_LED_INVERTED PCONFIG(3) + + # define P044_STATUS_LED 12 # define P044_CHECKSUM_LENGTH 4 # define P044_DATAGRAM_START_CHAR '/' @@ -23,7 +33,7 @@ struct P044_Task : public PluginTaskData_base { CHECKSUM }; - P044_Task(); + P044_Task(struct EventStruct *event); ~P044_Task(); @@ -96,6 +106,10 @@ struct P044_Task : public PluginTaskData_base { ESPeasySerial *P1EasySerial = nullptr; unsigned long blinkLEDStartTime = 0; size_t maxMessageSize = P044_DATAGRAM_MAX_SIZE / 4; + + int8_t _ledPin = P044_STATUS_LED; // Former default + bool _ledEnabled = true; // Former default + bool _ledInverted = false; }; #endif // ifdef USES_P044 From 7ed1cc858ae5a904aa652922a43d2f6a0112114d Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 1 Oct 2022 22:02:58 +0200 Subject: [PATCH 03/41] [P044] Correct auto-complete typo --- src/src/PluginStructs/P044_data_struct.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/src/PluginStructs/P044_data_struct.cpp b/src/src/PluginStructs/P044_data_struct.cpp index 104326bce..4b23fb232 100644 --- a/src/src/PluginStructs/P044_data_struct.cpp +++ b/src/src/PluginStructs/P044_data_struct.cpp @@ -48,9 +48,9 @@ void P044_Task::startServer(uint16_t portnumber) { P1GatewayServer->begin(); if (serverActive(P1GatewayServer)) { - # ifndef P036_LIMIT_BUILD_SIZE + # ifndef LIMIT_BUILD_SIZE addLog(LOG_LEVEL_INFO, concat(F("P1 : WiFi server started at port "), static_cast(portnumber))); - # endif // ifndef P036_LIMIT_BUILD_SIZE + # endif // ifndef LIMIT_BUILD_SIZE } else { addLog(LOG_LEVEL_ERROR, concat(F("P1 : WiFi server start failed at port "), static_cast(portnumber)) + F(", retrying...")); } From 3bb189d2bf99fa8645a843971805b741b759d396 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 8 Oct 2022 20:23:36 +0200 Subject: [PATCH 04/41] [Plugins] Call PLUGIN_WEBFORM_PRE_SERIAL_PARAMS before serial settings are displayed to allow settings conversion --- src/src/DataTypes/ESPEasy_plugin_functions.h | 1 + src/src/WebServer/DevicesPage.cpp | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/src/DataTypes/ESPEasy_plugin_functions.h b/src/src/DataTypes/ESPEasy_plugin_functions.h index e5925b69c..6e2783e1a 100644 --- a/src/src/DataTypes/ESPEasy_plugin_functions.h +++ b/src/src/DataTypes/ESPEasy_plugin_functions.h @@ -55,6 +55,7 @@ #define PLUGIN_WEBFORM_SHOW_ERRORSTATE_OPT 43 // Show Error State Value options, so be saved during PLUGIN_WEBFORM_SAVE #define PLUGIN_INIT_VALUE_RANGES 44 // Initialize the ranges of values, called just before PLUGIN_INIT #define PLUGIN_READ_ERROR_OCCURED 45 // Function returns "true" when last measurement was an error, called when PLUGIN_READ returns false +#define PLUGIN_WEBFORM_PRE_SERIAL_PARAMS 46 // Before serial parameters, convert additional parameters like baudrate or specific serial config diff --git a/src/src/WebServer/DevicesPage.cpp b/src/src/WebServer/DevicesPage.cpp index 4b5e0d3ad..3a752f2e9 100644 --- a/src/src/WebServer/DevicesPage.cpp +++ b/src/src/WebServer/DevicesPage.cpp @@ -1079,9 +1079,12 @@ void devicePage_show_serial_config(taskIndex_t taskIndex) { struct EventStruct TempEvent(taskIndex); - serialHelper_webformLoad(&TempEvent); String webformLoadString; + PluginCall(PLUGIN_WEBFORM_PRE_SERIAL_PARAMS, &TempEvent, webformLoadString); + + serialHelper_webformLoad(&TempEvent); + PluginCall(PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS, &TempEvent, webformLoadString); } From 547f57170bdb22d62ec05b89afc0ffc05a4db409 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 8 Oct 2022 20:23:57 +0200 Subject: [PATCH 05/41] [Plugins] Call PLUGIN_WEBFORM_PRE_SERIAL_PARAMS before serial settings are displayed to allow settings conversion --- src/src/Globals/Plugins.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/src/Globals/Plugins.cpp b/src/src/Globals/Plugins.cpp index 3110ca521..0312b812c 100644 --- a/src/src/Globals/Plugins.cpp +++ b/src/src/Globals/Plugins.cpp @@ -709,6 +709,7 @@ bool PluginCall(uint8_t Function, struct EventStruct *event, String& str) case PLUGIN_WEBFORM_SHOW_VALUES: case PLUGIN_WEBFORM_SHOW_CONFIG: case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + case PLUGIN_WEBFORM_PRE_SERIAL_PARAMS: case PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS: case PLUGIN_WEBFORM_SHOW_GPIO_DESCR: #if FEATURE_PLUGIN_STATS @@ -733,7 +734,8 @@ bool PluginCall(uint8_t Function, struct EventStruct *event, String& str) Function == PLUGIN_WEBFORM_LOAD || Function == PLUGIN_SET_DEFAULTS || Function == PLUGIN_INIT_VALUE_RANGES || - Function == PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS + Function == PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS || + Function == PLUGIN_WEBFORM_PRE_SERIAL_PARAMS ) { LoadTaskSettings(event->TaskIndex); } From bb66f3428af2a7a7cf18e664cd317d2811487cce Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 8 Oct 2022 20:30:12 +0200 Subject: [PATCH 06/41] [P020] Merge [P044] code into [P020], so P020 van emulate P044 --- src/_P020_Ser2Net.ino | 333 +++++++++++++-------- src/_P044_P1WifiGateway.ino | 11 +- src/src/CustomBuild/define_plugin_sets.h | 5 +- src/src/PluginStructs/P020_data_struct.cpp | 265 ++++++++++++++-- src/src/PluginStructs/P020_data_struct.h | 106 ++++++- src/src/PluginStructs/P044_data_struct.cpp | 2 +- src/src/PluginStructs/P044_data_struct.h | 2 +- 7 files changed, 563 insertions(+), 161 deletions(-) diff --git a/src/_P020_Ser2Net.ino b/src/_P020_Ser2Net.ino index 7682e0630..7781bb354 100644 --- a/src/_P020_Ser2Net.ino +++ b/src/_P020_Ser2Net.ino @@ -1,12 +1,16 @@ #include "_Plugin_Helper.h" -#ifdef USES_P020 +#if defined(USES_P020) || defined(USES_P044) // ####################################################################################################### // #################################### Plugin 020: Ser2Net ############################################## +// #################################### Plugin 044: P1WifiGateway ######################################## // ####################################################################################################### /************ * Changelog: + * 2022-10-08 tonhuisman: Merged code from P044 into this plugin, and use a global flag to emulate P044 with P020 + * When USES_P044 is enabled, also USES_P020 will be enabled! + * Add Led settings, similar to P044 * 2022-05-28 tonhuisman: Add option to generate events for all lines of a multi-line message * 2022-05-26 tonhuisman: Add option to allow processing without webclient connected. * No older changelog available. @@ -22,33 +26,23 @@ # define PLUGIN_NAME_020 "Communication - Serial Server" # define PLUGIN_VALUENAME1_020 "Ser2Net" +# define PLUGIN_ID_020_044 44 +# define PLUGIN_NAME_020_044 "Communication - P1 Wifi Gateway" -# define P020_SERVER_PORT ExtraTaskSettings.TaskDevicePluginConfigLong[0] -# define P020_BAUDRATE ExtraTaskSettings.TaskDevicePluginConfigLong[1] +bool P020_Emulate_P044 = false; // Global flag +# ifdef USES_P044 -// #define P020_BAUDRATE ExtraTaskSettings.TaskDevicePluginConfigLong[1] -# define P020_RX_WAIT PCONFIG(4) -# define P020_SERIAL_CONFIG PCONFIG(1) -# define P020_SERIAL_PROCESSING PCONFIG(5) -# define P020_RESET_TARGET_PIN PCONFIG(6) -# define P020_RX_BUFFER PCONFIG(7) +// Emulate P044 using P020 with a global flag +boolean Plugin_044(uint8_t function, struct EventStruct *event, String& string) { + P020_Emulate_P044 = true; -# define P020_FLAGS PCONFIG_LONG(0) -# define P020_FLAG_IGNORE_CLIENT 0 -# define P020_FLAG_MULTI_LINE 1 -# define P020_IGNORE_CLIENT_CONNECTED bitRead(P020_FLAGS, P020_FLAG_IGNORE_CLIENT) -# define P020_HANDLE_MULTI_LINE bitRead(P020_FLAGS, P020_FLAG_MULTI_LINE) + boolean result = Plugin_020(function, event, string); + P020_Emulate_P044 = false; + return result; +} -# define P020_QUERY_VALUE 0 // Temp placement holder until we know what selectors are needed. -# define P020_NR_OUTPUT_OPTIONS 1 -# define P020_QUERY1_CONFIG_POS 3 - -# define P020_DEFAULT_SERVER_PORT 1234 -# define P020_DEFAULT_BAUDRATE 115200 -# define P020_DEFAULT_RESET_TARGET_PIN -1 -# define P020_DEFAULT_RX_BUFFER 256 - +# endif // ifdef USES_P044 boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) { @@ -58,7 +52,13 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) { case PLUGIN_DEVICE_ADD: { - Device[++deviceCount].Number = PLUGIN_ID_020; + if (P020_Emulate_P044) { + Device[++deviceCount].Number = PLUGIN_ID_020_044; + Device[deviceCount].SendDataOption = false; + } else { + Device[++deviceCount].Number = PLUGIN_ID_020; + Device[deviceCount].SendDataOption = true; + } Device[deviceCount].Type = DEVICE_TYPE_SERIAL; Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_STRING; Device[deviceCount].Ports = 0; @@ -66,25 +66,38 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) Device[deviceCount].InverseLogicOption = false; Device[deviceCount].FormulaOption = false; Device[deviceCount].ValueCount = 0; - Device[deviceCount].SendDataOption = true; Device[deviceCount].TimerOption = false; Device[deviceCount].GlobalSyncOption = false; break; } case PLUGIN_GET_DEVICENAME: { - string = F(PLUGIN_NAME_020); + string = P020_Emulate_P044 ? F(PLUGIN_NAME_020_044) : F(PLUGIN_NAME_020); break; } case PLUGIN_SET_DEFAULTS: { - P020_BAUDRATE = P020_DEFAULT_BAUDRATE; - P020_SERVER_PORT = P020_DEFAULT_SERVER_PORT; - P020_RESET_TARGET_PIN = P020_DEFAULT_RESET_TARGET_PIN; - P020_RX_BUFFER = P020_DEFAULT_RX_BUFFER; - success = true; + if (P020_Emulate_P044) { + CONFIG_PORT = static_cast(ESPEasySerialPort::serial0); // P044 Serial port + CONFIG_PIN1 = 3; // P044 RX pin + CONFIG_PIN2 = 1; // P044 TX pin + P020_BAUDRATE = P020_DEFAULT_P044_BAUDRATE; + P020_SERVER_PORT = P020_DEFAULT_P044_SERVER_PORT; + P020_RESET_TARGET_PIN = P020_DEFAULT_RESET_TARGET_PIN; + P020_SERIAL_PROCESSING = static_cast(P020_Events::P1WiFiGateway); // Enable P1 WiFi Gateway processing (only) + P020_LED_PIN = P020_STATUS_LED; + bitSet(P020_FLAGS, P020_FLAG_LED_ENABLED); + bitSet(P020_FLAGS, P020_FLAG_P044_MODE_SAVED); // Inital config, no conversion needed + } else { + P020_BAUDRATE = P020_DEFAULT_BAUDRATE; + P020_SERVER_PORT = P020_DEFAULT_SERVER_PORT; + P020_RESET_TARGET_PIN = P020_DEFAULT_RESET_TARGET_PIN; + P020_RX_BUFFER = P020_DEFAULT_RX_BUFFER; + P020_LED_PIN = -1; + } + success = true; break; } @@ -104,38 +117,95 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_SHOW_GPIO_DESCR: { - string = F("RST: "); - string += formatGpioLabel(P020_RESET_TARGET_PIN, false); + string = concat(F("RST: "), formatGpioLabel(P020_RESET_TARGET_PIN, false)); + string += event->String1; + string += concat(F("LED: "), formatGpioLabel(P020_GET_LED_ENABLED ? P020_LED_PIN : -1, false)); + + if ((P020_GET_LED_INVERTED == 1) && (P020_GET_LED_ENABLED)) { + string += F(" (inv)"); + } success = true; break; } + case PLUGIN_WEBFORM_PRE_SERIAL_PARAMS: + { + if (P020_Emulate_P044 && !P020_GET_P044_MODE_SAVED) { + addFormNote(F("Settings migrated from previous plugin version.")); + + // Convert existing P044 settings to P020 settings + P020_RX_WAIT = PCONFIG(0); // No conflict + // P020_SERIAL_CONFIG = PCONFIG(1); // No need to convert + P020_RESET_TARGET_PIN = CONFIG_PIN1; + + // 'Conflicting' stuff, set defaults to: Serial0, RX=gpio-3 and TX=gpio-1 + CONFIG_PORT = static_cast(ESPEasySerialPort::serial0); // P044 Serial port + CONFIG_PIN1 = 3; // P044 RX pin + CONFIG_PIN2 = 1; // P044 TX pin + + // Former P044 defaults + bitSet(P020_FLAGS, P020_FLAG_LED_ENABLED); // Led enabled... + P020_LED_PIN = P020_STATUS_LED; // ...and connected to GPIO-12 + P020_SERIAL_PROCESSING = static_cast(P020_Events::P1WiFiGateway); // Enable P1 WiFi Gateway processing + } + break; + } + case PLUGIN_WEBFORM_LOAD: { - addFormNumericBox(F("TCP Port"), F("p020_port"), P020_SERVER_PORT, 0); - addFormNumericBox(F("Baud Rate"), F("p020_baud"), P020_BAUDRATE, 0); + addFormNumericBox(F("TCP Port"), F("pport"), P020_SERVER_PORT, 0); + # ifndef LIMIT_BUILD_SIZE + addUnit(F("0..65535")); + # endif // ifndef LIMIT_BUILD_SIZE + + addFormNumericBox(F("Baud Rate"), F("pbaud"), P020_BAUDRATE, 0); uint8_t serialConfChoice = serialHelper_convertOldSerialConfig(P020_SERIAL_CONFIG); serialHelper_serialconfig_webformLoad(event, serialConfChoice); { - const __FlashStringHelper *options[3] = { - F("None"), - F("Generic"), - F("RFLink") - }; - addFormSelector(F("Event processing"), F("p020_events"), 3, options, nullptr, P020_SERIAL_PROCESSING); - addFormCheckBox(F("Process events without client"), F("p020_ignoreclient"), P020_IGNORE_CLIENT_CONNECTED); + if (!P020_Emulate_P044) { + const __FlashStringHelper *options[] = { + F("None"), + F("Generic"), + F("RFLink"), + F("P1 WiFi Gateway") + }; + const int optionValues[] = { + static_cast(P020_Events::None), + static_cast(P020_Events::Generic), + static_cast(P020_Events::RFLink), + static_cast(P020_Events::P1WiFiGateway), + }; + addFormSelector(F("Event processing"), F("pevents"), + sizeof(optionValues) / sizeof(int), options, optionValues, + P020_SERIAL_PROCESSING); + } + addFormCheckBox(F("Process events without client"), F("pignoreclient"), P020_IGNORE_CLIENT_CONNECTED); # ifndef LIMIT_BUILD_SIZE addFormNote(F("When enabled, will process serial data without a network client connected.")); # endif // ifndef LIMIT_BUILD_SIZE - addFormCheckBox(F("Multiple lines processing"), F("p020_multiline"), P020_HANDLE_MULTI_LINE); - } - addFormNumericBox(F("RX Receive Timeout (mSec)"), F("p020_rxwait"), P020_RX_WAIT, 0, 20); - addFormPinSelect(PinSelectPurpose::Generic, F("Reset target after init"), F("p020_resetpin"), P020_RESET_TARGET_PIN); - addFormNumericBox(F("RX buffer size (bytes)"), F("p020_rx_buffer"), P020_RX_BUFFER, 256, 1024); - # ifndef LIMIT_BUILD_SIZE - addFormNote(F("Standard RX buffer 256B; higher values could be unstable; energy meters could require 1024B")); - # endif // ifndef LIMIT_BUILD_SIZE + if (!P020_Emulate_P044) { // Not appropriate for P1 WiFi Gateway + addFormCheckBox(F("Multiple lines processing"), F("pmultiline"), P020_HANDLE_MULTI_LINE); + } + } + { + addFormNumericBox(F("RX Receive Timeout (mSec)"), F("prxwait"), P020_RX_WAIT, 0, 200); + addFormPinSelect(PinSelectPurpose::Generic, F("Reset target after init"), F("presetpin"), P020_RESET_TARGET_PIN); + + if (!P020_Emulate_P044) { + addFormNumericBox(F("RX buffer size (bytes)"), F("prx_buffer"), P020_RX_BUFFER, 256, 1024); + # ifndef LIMIT_BUILD_SIZE + addFormNote(F("Standard RX buffer 256B; higher values could be unstable; energy meters could require 1024B")); + # endif // ifndef LIMIT_BUILD_SIZE + } + } + { // Led settings + addFormSubHeader(F("Led")); + + addFormCheckBox(F("Led enabled"), F("pled"), P020_GET_LED_ENABLED); + addFormPinSelect(PinSelectPurpose::Generic, F("Led pin"), F("pledpin"), P020_LED_PIN); + addFormCheckBox(F("Led inverted"), F("pledinv"), P020_GET_LED_INVERTED == 1); + } success = true; break; @@ -143,16 +213,32 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_SAVE: { - P020_SERVER_PORT = getFormItemInt(F("p020_port")); - P020_BAUDRATE = getFormItemInt(F("p020_baud")); - P020_SERIAL_CONFIG = serialHelper_serialconfig_webformSave(); - P020_SERIAL_PROCESSING = getFormItemInt(F("p020_events")); - P020_RX_WAIT = getFormItemInt(F("p020_rxwait")); - P020_RESET_TARGET_PIN = getFormItemInt(F("p020_resetpin")); - P020_RX_BUFFER = getFormItemInt(F("p020_rx_buffer")); + P020_SERVER_PORT = getFormItemInt(F("pport")); + P020_BAUDRATE = getFormItemInt(F("pbaud")); + P020_SERIAL_CONFIG = serialHelper_serialconfig_webformSave(); + P020_RX_WAIT = getFormItemInt(F("prxwait")); + P020_RESET_TARGET_PIN = getFormItemInt(F("presetpin")); - bitWrite(P020_FLAGS, P020_FLAG_IGNORE_CLIENT, isFormItemChecked(F("p020_ignoreclient"))); - bitWrite(P020_FLAGS, P020_FLAG_MULTI_LINE, isFormItemChecked(F("p020_multiline"))); + if (P020_Emulate_P044) { + P020_SERIAL_PROCESSING = static_cast(P020_Events::P1WiFiGateway); // Force P1 WiFi Gateway processing + } else { + P020_SERIAL_PROCESSING = getFormItemInt(F("pevents")); + P020_RX_BUFFER = getFormItemInt(F("prx_buffer")); + } + P020_LED_PIN = getFormItemInt(F("pledpin")); + + uint32_t lSettings = 0u; + bitWrite(lSettings, P020_FLAG_IGNORE_CLIENT, isFormItemChecked(F("pignoreclient"))); + bitWrite(lSettings, P020_FLAG_LED_ENABLED, isFormItemChecked(F("pled"))); + bitWrite(lSettings, P020_FLAG_LED_INVERTED, isFormItemChecked(F("pledinv"))); + + if (P020_Emulate_P044) { + bitSet(lSettings, P020_FLAG_P044_MODE_SAVED); // Set to P044 configuration done on every save + } else { + bitWrite(lSettings, P020_FLAG_MULTI_LINE, isFormItemChecked(F("pmultiline"))); + } + + P020_FLAGS = lSettings; success = true; break; @@ -162,6 +248,11 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) { LoadTaskSettings(event->TaskIndex); + if (P020_GET_LED_ENABLED && validGpio(P020_LED_PIN)) { + pinMode(P020_LED_PIN, OUTPUT); + digitalWrite(P020_LED_PIN, P020_GET_LED_INVERTED ? 1 : 0); + } + if ((P020_SERVER_PORT == 0) || (P020_BAUDRATE == 0)) { clearPluginTaskData(event->TaskIndex); break; @@ -174,22 +265,19 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) // It was already created and initialzed // So don't recreate to keep the webserver running. } else { - initPluginTaskData(event->TaskIndex, new (std::nothrow) P020_Task(event->TaskIndex)); + initPluginTaskData(event->TaskIndex, new (std::nothrow) P020_Task(event)); task = static_cast(getPluginTaskData(event->TaskIndex)); } if (nullptr == task) { break; } - task->handleMultiLine = P020_HANDLE_MULTI_LINE; + task->handleMultiLine = P020_HANDLE_MULTI_LINE && static_cast(P020_SERIAL_PROCESSING) != P020_Events::P1WiFiGateway; - // int rxPin =-1; - // int txPin =-1; int rxPin = CONFIG_PIN1; int txPin = CONFIG_PIN2; const ESPEasySerialPort port = static_cast(CONFIG_PORT); - // const ESPEasySerialPort port= ESPEasySerialPort::serial0; if ((rxPin < 0) && (txPin < 0)) { ESPeasySerialType::getSerialTypePins(port, rxPin, txPin); CONFIG_PIN1 = rxPin; @@ -199,20 +287,15 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) # ifndef LIMIT_BUILD_SIZE if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("Ser2net: TaskIndex="); - log += event->TaskIndex; - log += F(" port="); - log += CONFIG_PORT; - log += F(" rxPin="); - log += rxPin; - log += F(" txPin="); - log += txPin; - log += F(" BAUDRATE="); - log += P020_BAUDRATE; - log += F(" SERVER_PORT="); - log += P020_SERVER_PORT; - log += F(" SERIAL_PROCESSING="); - log += P020_SERIAL_PROCESSING; + String log; + log.reserve(100); + log = concat(F("Ser2net: TaskIndex="), static_cast(event->TaskIndex)); + log += concat(F(" port="), static_cast(CONFIG_PORT)); + log += concat(F(" rxPin="), static_cast(rxPin)); + log += concat(F(" txPin="), static_cast(txPin)); + log += concat(F(" BAUDRATE="), static_cast(P020_BAUDRATE)); + log += concat(F(" SERVER_PORT="), static_cast(P020_SERVER_PORT)); + log += concat(F(" SERIAL_PROCESSING="), static_cast(P020_SERIAL_PROCESSING)); addLogMove(LOG_LEVEL_INFO, log); } # endif // ifndef LIMIT_BUILD_SIZE @@ -232,11 +315,7 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log; - log.reserve(38); - log += F("Ser2net : P020_RESET_TARGET_PIN : "); - log += P020_RESET_TARGET_PIN; - addLogMove(LOG_LEVEL_DEBUG, log); + addLogMove(LOG_LEVEL_DEBUG, concat(F("Ser2net : P020_RESET_TARGET_PIN : "), static_cast(P020_RESET_TARGET_PIN))); } # endif // ifndef BUILD_NO_DEBUG pinMode(P020_RESET_TARGET_PIN, OUTPUT); @@ -246,8 +325,23 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) pinMode(P020_RESET_TARGET_PIN, INPUT_PULLUP); } - task->serial_processing = P020_SERIAL_PROCESSING; - success = true; + task->serial_processing = static_cast(P020_SERIAL_PROCESSING); + + task->blinkLED(); + + if (task->serial_processing == P020_Events::P1WiFiGateway) { + task->_CRCcheck = P020_BAUDRATE == 115200; + # ifndef BUILD_NO_DEBUG + + if (task->_CRCcheck) { + addLog(LOG_LEVEL_DEBUG, F("P1 : DSMR version 5 meter, CRC on")); + } else { + addLog(LOG_LEVEL_DEBUG, F("P1 : DSMR version 4 meter, CRC off")); + } + # endif // ifndef BUILD_NO_DEBUG + } + + success = true; break; } @@ -261,11 +355,10 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) { P020_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr == task) { - break; + if (nullptr != task) { + task->checkServer(); + success = true; } - task->checkServer(); - success = true; break; } @@ -273,19 +366,18 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) { P020_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr == task) { - break; - } + if (nullptr != task) { + bool hasClient = task->hasClientConnected(); - bool hasClient = task->hasClientConnected(); - - if (P020_IGNORE_CLIENT_CONNECTED || hasClient) { - if (hasClient) { - task->handleClientIn(event); + if (P020_IGNORE_CLIENT_CONNECTED || hasClient) { + if (hasClient) { + task->handleClientIn(event); + } + task->handleSerialIn(event); // in case of second serial connected, PLUGIN_SERIAL_IN is not called anymore } - task->handleSerialIn(event); // in case of second serial connected, PLUGIN_SERIAL_IN is not called anymore + task->checkBlinkLED(); + success = true; } - success = true; break; } @@ -293,38 +385,35 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) { P020_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr == task) { - break; + if (nullptr != task) { + if (P020_IGNORE_CLIENT_CONNECTED || task->hasClientConnected()) { + task->handleSerialIn(event); + } else { + task->discardSerialIn(); + } + success = true; } - - if (P020_IGNORE_CLIENT_CONNECTED || task->hasClientConnected()) { - task->handleSerialIn(event); - } else { - task->discardSerialIn(); - } - success = true; break; } case PLUGIN_WRITE: { - String command = parseString(string, 1); P020_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr == task) { - break; - } + if (nullptr != task) { + String command = parseString(string, 1); - if (command.equals(F("serialsend"))) { - task->ser2netSerial->write(string.substring(11).c_str()); - task->ser2netSerial->flush(); - success = true; - } + if (command.equals(F("serialsend"))) { + task->ser2netSerial->write(string.substring(11).c_str()); + task->ser2netSerial->flush(); + success = true; + } - if ((command.equals(F("ser2netclientsend"))) && (task->hasClientConnected())) { - task->ser2netClient.print(string.substring(18)); - task->ser2netClient.flush(); - success = true; + if ((command.equals(F("ser2netclientsend"))) && (task->hasClientConnected())) { + task->ser2netClient.print(string.substring(18)); + task->ser2netClient.flush(); + success = true; + } } break; } @@ -332,4 +421,4 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) return success; } -#endif // USES_P020 +#endif // if defined(USES_P020) || defined(USES_P044) diff --git a/src/_P044_P1WifiGateway.ino b/src/_P044_P1WifiGateway.ino index 7c1128a40..c953b3a0a 100644 --- a/src/_P044_P1WifiGateway.ino +++ b/src/_P044_P1WifiGateway.ino @@ -1,5 +1,5 @@ #include "_Plugin_Helper.h" -#ifdef USES_P044 +#ifdef USES_P044_ORG // #################################### Plugin 044: P1WifiGateway ######################################## // @@ -12,6 +12,8 @@ // ####################################################################################################### /** Changelog: + * 2022-10-08 tonhuisman: Disable plugin-code and merge all functionality into P020 as it was originally a modified copy of that plugin + * *** This code is deprecated *** * 2022-10-01 tonhuisman: Add Led configuration options (Enabled, Pin, Inverted), changed device configuration * 2022-10-01 tonhuisman: Format source using Uncrustify */ @@ -67,6 +69,13 @@ boolean Plugin_044(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_LOAD: { + # ifdef USES_P020 + String msg; + msg.reserve(132); + msg += F("This plugin is deprecated and will be removed in a future release. Please use P020 - "); + msg += getPluginNameFromPluginID(20); + addFormNote(msg); + # endif // ifdef USES_P020 LoadTaskSettings(event->TaskIndex); { // Serial settings diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 9804415a1..27c5941cf 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -1814,7 +1814,10 @@ To create/register a plugin, you have to : /******************************************************************************\ * Libraries dependencies ***************************************************** \******************************************************************************/ -#if defined(USES_P020) || defined(USES_P049) || defined(USES_P052) || defined(USES_P053) || defined(USES_P056) || defined(USES_P071) || defined(USES_P075) || defined(USES_P078) || defined(USES_P082) || defined(USES_P085) || defined(USES_P087) || defined(USES_P093)|| defined(USES_P094) || defined(USES_P102) || defined(USES_P105) || defined(USES_P108) || defined(USES_C018) +#if defined(USES_P044) && !defined(USES_P020) // P020 is used to replace/emulate P044 + #define USES_P020 +#endif +#if defined(USES_P020) || defined(USES_P044) || defined(USES_P049) || defined(USES_P052) || defined(USES_P053) || defined(USES_P056) || defined(USES_P071) || defined(USES_P075) || defined(USES_P078) || defined(USES_P082) || defined(USES_P085) || defined(USES_P087) || defined(USES_P093)|| defined(USES_P094) || defined(USES_P102) || defined(USES_P105) || defined(USES_P108) || defined(USES_C018) // At least one plugin uses serial. #ifndef PLUGIN_USES_SERIAL #define PLUGIN_USES_SERIAL diff --git a/src/src/PluginStructs/P020_data_struct.cpp b/src/src/PluginStructs/P020_data_struct.cpp index 1bede0631..ddfdcb409 100644 --- a/src/src/PluginStructs/P020_data_struct.cpp +++ b/src/src/PluginStructs/P020_data_struct.cpp @@ -10,12 +10,14 @@ # include "../Helpers/ESPEasy_Storage.h" # include "../Helpers/Misc.h" -# define P020_RX_WAIT PCONFIG(4) -# define P020_RX_BUFFER PCONFIG(7) - - -P020_Task::P020_Task(taskIndex_t taskIndex) : _taskIndex(taskIndex) { +P020_Task::P020_Task(struct EventStruct *event) : _taskIndex(event->TaskIndex) { clearBuffer(); + + if (P020_GET_LED_ENABLED) { + _ledPin = P020_LED_PIN; // Default pin (12) is already initialized in P020_Task + } + _ledEnabled = P020_GET_LED_ENABLED == 1; + _ledInverted = P020_GET_LED_INVERTED == 1; } P020_Task::~P020_Task() { @@ -80,15 +82,15 @@ bool P020_Task::hasClientConnected() { if (ser2netClient) { ser2netClient.stop(); } ser2netClient = ser2netServer->available(); - #ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + # ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS // See: https://github.com/espressif/arduino-esp32/pull/6676 ser2netClient.setTimeout((CONTROLLER_CLIENTTIMEOUT_DFLT + 500) / 1000); // in seconds!!!! Client *pClient = &ser2netClient; pClient->setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT); - #else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS - ser2netClient.setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT); // in msec as it should be! - #endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + # else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + ser2netClient.setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT); // in msec as it should be! + # endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS sendConnectedEvent(true); addLog(LOG_LEVEL_INFO, F("Ser2Net : Client connected!")); @@ -119,8 +121,11 @@ void P020_Task::discardClientIn() { } void P020_Task::clearBuffer() { - serial_buffer = String(); - serial_buffer.reserve(P020_DATAGRAM_MAX_SIZE); + serial_buffer = String(); + _maxDataGramSize = serial_processing == P020_Events::P1WiFiGateway + ? P020_P1_DATAGRAM_MAX_SIZE + : P020_DATAGRAM_MAX_SIZE; + serial_buffer.reserve(_maxDataGramSize); } void P020_Task::serialBegin(const ESPEasySerialPort port, int16_t rxPin, int16_t txPin, unsigned long baud, uint8_t config) { @@ -156,10 +161,10 @@ void P020_Task::serialEnd() { void P020_Task::handleClientIn(struct EventStruct *event) { int count = ser2netClient.available(); int bytes_read = 0; - uint8_t net_buf[P020_DATAGRAM_MAX_SIZE]; + uint8_t net_buf[_maxDataGramSize]; if (count > 0) { - if (count > P020_DATAGRAM_MAX_SIZE) { count = P020_DATAGRAM_MAX_SIZE; } + if (count > _maxDataGramSize) { count = _maxDataGramSize; } bytes_read = ser2netClient.read(net_buf, count); ser2netSerial->write(net_buf, bytes_read); ser2netSerial->flush(); // Waits for the transmission of outgoing serial data to @@ -172,18 +177,37 @@ void P020_Task::handleClientIn(struct EventStruct *event) { void P020_Task::handleSerialIn(struct EventStruct *event) { if (nullptr == ser2netSerial) { return; } - int RXWait = P020_RX_WAIT; - int timeOut = RXWait; + int RXWait = P020_RX_WAIT; + int timeOut = RXWait; + bool done = false; do { if (ser2netSerial->available()) { - if (serial_buffer.length() > static_cast(P020_RX_BUFFER)) { + if ((serial_processing != P020_Events::P1WiFiGateway) // P1 handling without this check + && (serial_buffer.length() > static_cast(P020_RX_BUFFER))) { # ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_DEBUG, F("Ser2Net : Error: Buffer overflow, discarded input.")); # endif // ifndef BUILD_NO_DEBUG ser2netSerial->read(); } - else { serial_buffer += (char)ser2netSerial->read(); } + else { + if (_ledEnabled) { + digitalWrite(_ledPin, _ledInverted ? 0 : 1); + } + + if (serial_processing == P020_Events::P1WiFiGateway) { + done = handleP1Char(ser2netSerial->read()); + } else { + addChar((char)ser2netSerial->read()); + } + + + if (_ledEnabled) { + digitalWrite(_ledPin, _ledInverted ? 1 : 0); + } + } + + if (done) { break; } timeOut = RXWait; // if serial received, reset timeout counter } else { if (timeOut <= 0) { break; } @@ -196,6 +220,9 @@ void P020_Task::handleSerialIn(struct EventStruct *event) { if (ser2netClient.connected()) { // Only send out if a client is connected ser2netClient.print(serial_buffer); } + + blinkLED(); + rulesEngine(serial_buffer); ser2netClient.flush(); clearBuffer(); @@ -219,7 +246,7 @@ void P020_Task::rulesEngine(const String& message) { int NewLinePos = 0; uint16_t StartPos = 0; - NewLinePos = message.indexOf('\n', StartPos); + NewLinePos = handleMultiLine ? message.indexOf('\n', StartPos) : message.length(); do { if (NewLinePos < 0) { @@ -238,15 +265,15 @@ void P020_Task::rulesEngine(const String& message) { } switch (serial_processing) { - case 0: { break; } - case 1: { // Generic + case P020_Events::None: { break; } + case P020_Events::Generic: { // Generic if (NewLinePos > StartPos) { eventString = F("!Serial#"); eventString += message.substring(StartPos, NewLinePos); } break; } - case 2: { // RFLink + case P020_Events::RFLink: { // RFLink StartPos += 6; // RFLink, strip 20;xx; from incoming message if (message.substring(StartPos, NewLinePos) @@ -262,6 +289,10 @@ void P020_Task::rulesEngine(const String& message) { } break; } + case P020_Events::P1WiFiGateway: // P1 WiFi Gateway + eventString = getTaskDeviceName(_taskIndex); + eventString += F("#Data"); + break; } // switch // Skip CR/LF @@ -300,4 +331,196 @@ void P020_Task::sendConnectedEvent(bool connected) } } +void P020_Task::blinkLED() { + if (_ledEnabled) { + _blinkLEDStartTime = millis(); + digitalWrite(_ledPin, _ledInverted ? 0 : 1); + } +} + +void P020_Task::checkBlinkLED() { + if (_ledEnabled && (_blinkLEDStartTime > 0) && (timePassedSince(_blinkLEDStartTime) >= 500)) { + digitalWrite(_ledPin, _ledInverted ? 1 : 0); + _blinkLEDStartTime = 0; + } +} + +void P020_Task::addChar(char ch) { + serial_buffer += ch; +} + +/* checkDatagram + checks whether the P020_CHECKSUM of the data received from P1 matches the P020_CHECKSUM + attached to the telegram + */ +bool P020_Task::checkDatagram() const { + int endChar = serial_buffer.length() - 1; + + if (_CRCcheck) { + endChar -= P020_CHECKSUM_LENGTH; + } + + if ((endChar < 0) || (serial_buffer[0] != P020_DATAGRAM_START_CHAR) || + (serial_buffer[endChar] != P020_DATAGRAM_END_CHAR)) { + return false; + } + + if (!_CRCcheck) { + return true; + } + + const int checksumStartIndex = endChar + 1; + + # if PLUGIN_020_DEBUG + + for (unsigned int cnt = 0; cnt < serial_buffer.length(); ++cnt) { + serialPrint(serial_buffer.substring(cnt, 1)); + } + # endif // if PLUGIN_020_DEBUG + + // calculate the CRC and check if it equals the hexadecimal one attached to the datagram + unsigned int crc = CRC16(serial_buffer, checksumStartIndex); + return strtoul(serial_buffer.substring(checksumStartIndex).c_str(), nullptr, 16) == crc; +} + +/* + CRC16 + based on code written by Jan ten Hove + https://github.com/jantenhove/P1-Meter-ESP8266 + */ +unsigned int P020_Task::CRC16(const String& buf, int len) { + unsigned int crc = 0; + + for (int pos = 0; pos < len; pos++) { + crc ^= static_cast(buf[pos]); // XOR byte into least sig. byte of crc + + for (int i = 8; i != 0; i--) { // Loop over each bit + if ((crc & 0x0001) != 0) { // If the LSB is set + crc >>= 1; // Shift right and XOR 0xA001 + crc ^= 0xA001; + } else { // Else LSB is not set + crc >>= 1; // Just shift right + } + } + } + + return crc; +} + +/* + validP1char + Checks if the character is valid as part of the P1 datagram contents and/or checksum. + Returns false on a datagram start ('/'), end ('!') or invalid character + */ +bool P020_Task::validP1char(char ch) { + return + isAlphaNumeric(ch) || + ch == '.' || + ch == ' ' || + ch == '\\' || // Single backslash, but escaped in C++ + ch == '\r' || + ch == '\n' || + ch == '(' || + ch == ')' || + ch == '-' || + ch == '*' || + ch == ':' || + ch == '_'; +} + +bool P020_Task::handleP1Char(char ch) { + if (serial_buffer.length() >= _maxDataGramSize - 2) { // room for cr/lf + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("P1 : Error: Buffer overflow, discarded input.")); + # endif // ifndef BUILD_NO_DEBUG + state = ParserState::WAITING; // reset + } + + bool done = false; + bool invalid = false; + + switch (state) { + case ParserState::WAITING: + + if (ch == P020_DATAGRAM_START_CHAR) { + clearBuffer(); + addChar(ch); + state = ParserState::READING; + } // else ignore data + break; + case ParserState::READING: + + if (validP1char(ch)) { + addChar(ch); + } else if (ch == P020_DATAGRAM_END_CHAR) { + addChar(ch); + + if (_CRCcheck) { + checkI = 0; + state = ParserState::CHECKSUM; + } else { + done = true; + } + } else if (ch == P020_DATAGRAM_START_CHAR) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("P1 : Error: Start detected, discarded input.")); + # endif // ifndef BUILD_NO_DEBUG + state = ParserState::WAITING; // reset + return handleP1Char(ch); + } else { + invalid = true; + } + break; + case ParserState::CHECKSUM: + + if (validP1char(ch)) { + addChar(ch); + ++checkI; + + if (checkI == P020_CHECKSUM_LENGTH) { + done = true; + } + } else { + invalid = true; + } + break; + } // switch + + if (invalid) { + // input is not a datagram char + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("P1 : Error: DATA corrupt, discarded input.")); + # endif // ifndef BUILD_NO_DEBUG + + # if PLUGIN_020_DEBUG + serialPrint(F("faulty char>")); + serialPrint(String(ch)); + serialPrintln("<"); + # endif // if PLUGIN_020_DEBUG + state = ParserState::WAITING; // reset + } + + if (done) { + done = checkDatagram(); + + if (done) { + // add the cr/lf pair to the datagram ahead of reading both + // from serial as the datagram has already been validated + addChar('\r'); + addChar('\n'); + } else if (_CRCcheck) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("P1 : Error: Invalid CRC, dropped data")); + # endif // ifndef BUILD_NO_DEBUG + } else { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("P1 : Error: Invalid datagram, dropped data")); + # endif // ifndef BUILD_NO_DEBUG + } + state = ParserState::WAITING; // prepare for next one + } + + return done; +} + #endif // ifdef USES_P020 diff --git a/src/src/PluginStructs/P020_data_struct.h b/src/src/PluginStructs/P020_data_struct.h index c7f93ab77..4f9dafa8a 100644 --- a/src/src/PluginStructs/P020_data_struct.h +++ b/src/src/PluginStructs/P020_data_struct.h @@ -8,47 +8,117 @@ # include # ifndef PLUGIN_020_DEBUG - # define PLUGIN_020_DEBUG false // extra logging in serial out + # define PLUGIN_020_DEBUG false // when true: extra logging in serial out !?!?! # endif // ifndef PLUGIN_020_DEBUG +# define P020_SERVER_PORT ExtraTaskSettings.TaskDevicePluginConfigLong[0] +# define P020_BAUDRATE ExtraTaskSettings.TaskDevicePluginConfigLong[1] + +# define P020_LED_PIN PCONFIG(0) +# define P020_SERIAL_CONFIG PCONFIG(1) +# define P020_RX_WAIT PCONFIG(4) +# define P020_SERIAL_PROCESSING PCONFIG(5) +# define P020_RESET_TARGET_PIN PCONFIG(6) +# define P020_RX_BUFFER PCONFIG(7) + +# define P020_FLAGS PCONFIG_ULONG(0) +# define P020_FLAG_IGNORE_CLIENT 0 +# define P020_FLAG_MULTI_LINE 1 +# define P020_FLAG_LED_ENABLED 2 +# define P020_FLAG_LED_INVERTED 3 +# define P020_FLAG_P044_MODE_SAVED 8 +# define P020_IGNORE_CLIENT_CONNECTED bitRead(P020_FLAGS, P020_FLAG_IGNORE_CLIENT) +# define P020_HANDLE_MULTI_LINE bitRead(P020_FLAGS, P020_FLAG_MULTI_LINE) +# define P020_GET_LED_ENABLED bitRead(P020_FLAGS, P020_FLAG_LED_ENABLED) +# define P020_GET_LED_INVERTED bitRead(P020_FLAGS, P020_FLAG_LED_INVERTED) +# define P020_GET_P044_MODE_SAVED bitRead(P020_FLAGS, P020_FLAG_P044_MODE_SAVED) + +# define P020_DEFAULT_SERVER_PORT 1234 +# define P020_DEFAULT_BAUDRATE 115200 +# define P020_DEFAULT_RESET_TARGET_PIN -1 +# define P020_DEFAULT_RX_BUFFER 256 + # define P020_STATUS_LED 12 # define P020_DATAGRAM_MAX_SIZE 256 + +# define P020_DEFAULT_P044_SERVER_PORT 0 +# define P020_DEFAULT_P044_BAUDRATE 9600 + +# define P020_CHECKSUM_LENGTH 4 +# define P020_DATAGRAM_START_CHAR '/' +# define P020_DATAGRAM_END_CHAR '!' +# define P020_P1_DATAGRAM_MAX_SIZE 2048u + +enum class P020_Events : uint8_t { + None = 0u, + Generic = 1u, + RFLink = 2u, + P1WiFiGateway = 3u, +}; + struct P020_Task : public PluginTaskData_base { - P020_Task(taskIndex_t taskIndex); + enum class ParserState : uint8_t { + WAITING, + READING, + CHECKSUM + }; + + P020_Task(struct EventStruct *event); ~P020_Task(); inline static bool serverActive(WiFiServer *server); void startServer(uint16_t portnumber); - void checkServer(); - void stopServer(); bool hasClientConnected(); - void discardClientIn(); void clearBuffer(); - void serialBegin(const ESPEasySerialPort port, int16_t rxPin, int16_t txPin, unsigned long baud, uint8_t config); + void serialEnd(); - void serialEnd(); + void handleSerialIn(struct EventStruct *event); + void handleClientIn(struct EventStruct *event); + void discardSerialIn(); + void rulesEngine(const String& message); - void handleSerialIn(struct EventStruct *event); - void handleClientIn(struct EventStruct *event); - void rulesEngine(const String& message); + bool isInit() const; - void discardSerialIn(); + void sendConnectedEvent(bool connected); - bool isInit() const; + void blinkLED(); + void checkBlinkLED(); - void sendConnectedEvent(bool connected); + void addChar(char ch); + + /* checkDatagram + checks whether the P020_CHECKSUM of the data received from P1 matches the P020_CHECKSUM + attached to the telegram + */ + bool checkDatagram() const; + + /* + CRC16 + based on code written by Jan ten Hove + https://github.com/jantenhove/P1-Meter-ESP8266 + */ + static unsigned int CRC16(const String& buf, + int len); + + /* + validP1char + Checks if the character is valid as part of the P1 datagram contents and/or checksum. + Returns false on a datagram start ('/'), end ('!') or invalid character + */ + static bool validP1char(char ch); + bool handleP1Char(char ch); WiFiServer *ser2netServer = nullptr; uint16_t gatewayPort = 0; @@ -58,9 +128,17 @@ struct P020_Task : public PluginTaskData_base { String net_buffer; int checkI = 0; ESPeasySerial *ser2netSerial = nullptr; - uint8_t serial_processing = 0; + P020_Events serial_processing = P020_Events::None; taskIndex_t _taskIndex = INVALID_TASK_INDEX; bool handleMultiLine = false; + + unsigned long _blinkLEDStartTime = 0; + int8_t _ledPin = -1; + bool _ledInverted = false; + bool _ledEnabled = false; + bool _CRCcheck = false; + size_t _maxDataGramSize = P020_DATAGRAM_MAX_SIZE; + ParserState state = ParserState::WAITING; }; #endif // ifdef USES_P020 diff --git a/src/src/PluginStructs/P044_data_struct.cpp b/src/src/PluginStructs/P044_data_struct.cpp index 4b23fb232..1b618b1c7 100644 --- a/src/src/PluginStructs/P044_data_struct.cpp +++ b/src/src/PluginStructs/P044_data_struct.cpp @@ -1,6 +1,6 @@ #include "../PluginStructs/P044_data_struct.h" -#ifdef USES_P044 +#ifdef USES_P044_ORG # include "../ESPEasyCore/Serial.h" # include "../ESPEasyCore/ESPEasyNetwork.h" diff --git a/src/src/PluginStructs/P044_data_struct.h b/src/src/PluginStructs/P044_data_struct.h index 944528104..5ab2fc81c 100644 --- a/src/src/PluginStructs/P044_data_struct.h +++ b/src/src/PluginStructs/P044_data_struct.h @@ -3,7 +3,7 @@ #include "../../_Plugin_Helper.h" -#ifdef USES_P044 +#ifdef USES_P044_ORG # include From 7fb2382aaff4330dfdc34795113a6c60505f2675 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sun, 9 Oct 2022 13:38:58 +0200 Subject: [PATCH 07/41] [P020/P044] Migrate settings also on plugin start without prior save from UI --- src/_P020_Ser2Net.ino | 51 +++++++++++++++------- src/src/PluginStructs/P020_data_struct.cpp | 4 +- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/_P020_Ser2Net.ino b/src/_P020_Ser2Net.ino index 7781bb354..4d47eb985 100644 --- a/src/_P020_Ser2Net.ino +++ b/src/_P020_Ser2Net.ino @@ -8,6 +8,7 @@ /************ * Changelog: + * 2022-10-09 tonhuisman: Check P044 migration on PLUGIN_INIT too, still needs a manual save (from UI or by save command) * 2022-10-08 tonhuisman: Merged code from P044 into this plugin, and use a global flag to emulate P044 with P020 * When USES_P044 is enabled, also USES_P020 will be enabled! * Add Led settings, similar to P044 @@ -42,6 +43,27 @@ boolean Plugin_044(uint8_t function, struct EventStruct *event, String& string) return result; } +bool P020_ConvertP044Settings(struct EventStruct *event) { + if (P020_Emulate_P044 && !P020_GET_P044_MODE_SAVED) { + // Convert existing P044 settings to P020 settings + P020_RX_WAIT = PCONFIG(0); // No conflict + // P020_SERIAL_CONFIG = PCONFIG(1); // No need to convert + P020_RESET_TARGET_PIN = CONFIG_PIN1; + + // 'Conflicting' stuff, set defaults to: Serial0, RX=gpio-3 and TX=gpio-1 + CONFIG_PORT = static_cast(ESPEasySerialPort::serial0); // P044 Serial port + CONFIG_PIN1 = 3; // P044 RX pin + CONFIG_PIN2 = 1; // P044 TX pin + + // Former P044 defaults + bitSet(P020_FLAGS, P020_FLAG_LED_ENABLED); // Led enabled... + P020_LED_PIN = P020_STATUS_LED; // ...and connected to GPIO-12 + P020_SERIAL_PROCESSING = static_cast(P020_Events::P1WiFiGateway); // Enable P1 WiFi Gateway processing + return true; + } + return false; +} + # endif // ifdef USES_P044 boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) @@ -128,28 +150,16 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) break; } + # ifdef USES_P044 case PLUGIN_WEBFORM_PRE_SERIAL_PARAMS: { - if (P020_Emulate_P044 && !P020_GET_P044_MODE_SAVED) { + // P044 Settings to convert? + if (P020_Emulate_P044 && P020_ConvertP044Settings(event)) { addFormNote(F("Settings migrated from previous plugin version.")); - - // Convert existing P044 settings to P020 settings - P020_RX_WAIT = PCONFIG(0); // No conflict - // P020_SERIAL_CONFIG = PCONFIG(1); // No need to convert - P020_RESET_TARGET_PIN = CONFIG_PIN1; - - // 'Conflicting' stuff, set defaults to: Serial0, RX=gpio-3 and TX=gpio-1 - CONFIG_PORT = static_cast(ESPEasySerialPort::serial0); // P044 Serial port - CONFIG_PIN1 = 3; // P044 RX pin - CONFIG_PIN2 = 1; // P044 TX pin - - // Former P044 defaults - bitSet(P020_FLAGS, P020_FLAG_LED_ENABLED); // Led enabled... - P020_LED_PIN = P020_STATUS_LED; // ...and connected to GPIO-12 - P020_SERIAL_PROCESSING = static_cast(P020_Events::P1WiFiGateway); // Enable P1 WiFi Gateway processing } break; } + # endif // ifdef USES_P044 case PLUGIN_WEBFORM_LOAD: { @@ -246,6 +256,15 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_INIT: { + # ifdef USES_P044 + + // P044 Settings to convert? + if (P020_Emulate_P044 && P020_ConvertP044Settings(event)) { + addLog(LOG_LEVEL_INFO, F("P1 : Automatic settings conversion, please save settings manually.")); + bitSet(P020_FLAGS, P020_FLAG_P044_MODE_SAVED); // Set to P044 configuration done on next save + } + # endif // ifdef USES_P044 + LoadTaskSettings(event->TaskIndex); if (P020_GET_LED_ENABLED && validGpio(P020_LED_PIN)) { diff --git a/src/src/PluginStructs/P020_data_struct.cpp b/src/src/PluginStructs/P020_data_struct.cpp index ddfdcb409..c1e92cc85 100644 --- a/src/src/PluginStructs/P020_data_struct.cpp +++ b/src/src/PluginStructs/P020_data_struct.cpp @@ -159,8 +159,8 @@ void P020_Task::serialEnd() { } void P020_Task::handleClientIn(struct EventStruct *event) { - int count = ser2netClient.available(); - int bytes_read = 0; + size_t count = ser2netClient.available(); + size_t bytes_read = 0; uint8_t net_buf[_maxDataGramSize]; if (count > 0) { From 986d53ac35c06ffb41103701fe3e42f71867fd9c Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sun, 9 Oct 2022 22:27:41 +0200 Subject: [PATCH 08/41] [P020] Code improvement --- src/_P020_Ser2Net.ino | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/_P020_Ser2Net.ino b/src/_P020_Ser2Net.ino index 4d47eb985..64ac5abe2 100644 --- a/src/_P020_Ser2Net.ino +++ b/src/_P020_Ser2Net.ino @@ -139,9 +139,11 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_SHOW_GPIO_DESCR: { - string = concat(F("RST: "), formatGpioLabel(P020_RESET_TARGET_PIN, false)); + string = F("RST: "); + string += formatGpioLabel(P020_RESET_TARGET_PIN, false); string += event->String1; - string += concat(F("LED: "), formatGpioLabel(P020_GET_LED_ENABLED ? P020_LED_PIN : -1, false)); + string += F("LED: "); + string += formatGpioLabel(P020_GET_LED_ENABLED ? P020_LED_PIN : -1, false); if ((P020_GET_LED_INVERTED == 1) && (P020_GET_LED_ENABLED)) { string += F(" (inv)"); From dc8ef704c73353ceb367013026d7319070a9bf45 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Tue, 11 Oct 2022 21:06:52 +0200 Subject: [PATCH 09/41] [P020/P044] Add option for including P1 data in #data event --- src/_P020_Ser2Net.ino | 8 ++++++++ src/src/PluginStructs/P020_data_struct.cpp | 5 +++++ src/src/PluginStructs/P020_data_struct.h | 3 +++ 3 files changed, 16 insertions(+) diff --git a/src/_P020_Ser2Net.ino b/src/_P020_Ser2Net.ino index 64ac5abe2..bc309ea47 100644 --- a/src/_P020_Ser2Net.ino +++ b/src/_P020_Ser2Net.ino @@ -8,6 +8,7 @@ /************ * Changelog: + * 2022-10-11 tonhuisman: Add option for including the message in P1 #data event * 2022-10-09 tonhuisman: Check P044 migration on PLUGIN_INIT too, still needs a manual save (from UI or by save command) * 2022-10-08 tonhuisman: Merged code from P044 into this plugin, and use a global flag to emulate P044 with P020 * When USES_P044 is enabled, also USES_P020 will be enabled! @@ -191,6 +192,11 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) sizeof(optionValues) / sizeof(int), options, optionValues, P020_SERIAL_PROCESSING); } + addFormCheckBox(F("P1 #data event with message"), F("pp1event"), P020_GET_P1_EVENT_DATA); + # ifndef LIMIT_BUILD_SIZE + addFormNote(F("When enabled, passes the entire message in the event. Warning: can cause memory overflow issues!")); + # endif // ifndef LIMIT_BUILD_SIZE + addFormCheckBox(F("Process events without client"), F("pignoreclient"), P020_IGNORE_CLIENT_CONNECTED); # ifndef LIMIT_BUILD_SIZE addFormNote(F("When enabled, will process serial data without a network client connected.")); @@ -243,6 +249,7 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) bitWrite(lSettings, P020_FLAG_IGNORE_CLIENT, isFormItemChecked(F("pignoreclient"))); bitWrite(lSettings, P020_FLAG_LED_ENABLED, isFormItemChecked(F("pled"))); bitWrite(lSettings, P020_FLAG_LED_INVERTED, isFormItemChecked(F("pledinv"))); + bitWrite(lSettings, P020_FLAG_P1_EVENT_DATA, isFormItemChecked(F("pp1event"))); if (P020_Emulate_P044) { bitSet(lSettings, P020_FLAG_P044_MODE_SAVED); // Set to P044 configuration done on every save @@ -347,6 +354,7 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) } task->serial_processing = static_cast(P020_SERIAL_PROCESSING); + task->_P1EventData = P020_GET_P1_EVENT_DATA; task->blinkLED(); diff --git a/src/src/PluginStructs/P020_data_struct.cpp b/src/src/PluginStructs/P020_data_struct.cpp index c1e92cc85..7a0682e76 100644 --- a/src/src/PluginStructs/P020_data_struct.cpp +++ b/src/src/PluginStructs/P020_data_struct.cpp @@ -292,6 +292,11 @@ void P020_Task::rulesEngine(const String& message) { case P020_Events::P1WiFiGateway: // P1 WiFi Gateway eventString = getTaskDeviceName(_taskIndex); eventString += F("#Data"); + + if (_P1EventData) { + eventString += '='; + eventString += message; // Include entire message, may cause memory overflow! + } break; } // switch diff --git a/src/src/PluginStructs/P020_data_struct.h b/src/src/PluginStructs/P020_data_struct.h index 4f9dafa8a..94c1b9699 100644 --- a/src/src/PluginStructs/P020_data_struct.h +++ b/src/src/PluginStructs/P020_data_struct.h @@ -26,11 +26,13 @@ # define P020_FLAG_MULTI_LINE 1 # define P020_FLAG_LED_ENABLED 2 # define P020_FLAG_LED_INVERTED 3 +# define P020_FLAG_P1_EVENT_DATA 4 # define P020_FLAG_P044_MODE_SAVED 8 # define P020_IGNORE_CLIENT_CONNECTED bitRead(P020_FLAGS, P020_FLAG_IGNORE_CLIENT) # define P020_HANDLE_MULTI_LINE bitRead(P020_FLAGS, P020_FLAG_MULTI_LINE) # define P020_GET_LED_ENABLED bitRead(P020_FLAGS, P020_FLAG_LED_ENABLED) # define P020_GET_LED_INVERTED bitRead(P020_FLAGS, P020_FLAG_LED_INVERTED) +# define P020_GET_P1_EVENT_DATA bitRead(P020_FLAGS, P020_FLAG_P1_EVENT_DATA) # define P020_GET_P044_MODE_SAVED bitRead(P020_FLAGS, P020_FLAG_P044_MODE_SAVED) # define P020_DEFAULT_SERVER_PORT 1234 @@ -137,6 +139,7 @@ struct P020_Task : public PluginTaskData_base { bool _ledInverted = false; bool _ledEnabled = false; bool _CRCcheck = false; + bool _P1EventData = false; size_t _maxDataGramSize = P020_DATAGRAM_MAX_SIZE; ParserState state = ParserState::WAITING; }; From 2c12f5a3e9f776c0cb29d8b5abb3cca26146ae8c Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Wed, 14 Dec 2022 22:24:06 +0100 Subject: [PATCH 10/41] [UI] Add Separator character input selector --- src/src/WebServer/Markup_Forms.cpp | 26 ++++++++++++++++++++++++++ src/src/WebServer/Markup_Forms.h | 9 +++++++++ 2 files changed, 35 insertions(+) diff --git a/src/src/WebServer/Markup_Forms.cpp b/src/src/WebServer/Markup_Forms.cpp index 98925f1ef..42e4d49ad 100644 --- a/src/src/WebServer/Markup_Forms.cpp +++ b/src/src/WebServer/Markup_Forms.cpp @@ -389,6 +389,32 @@ void addFormIPaccessControlSelect(const __FlashStringHelper * label, const __Fla addIPaccessControlSelect(id, choice); } +// ******************************************************************************** +// a Separator character selector +// ******************************************************************************** +void addFormSeparatorCharInput(const __FlashStringHelper *rowLabel, + const __FlashStringHelper *id, + int value, + const String & charset, + const __FlashStringHelper *additionalText) { + const int len = charset.length() + 1; + String charList[len]; + int charOpts[len]; + + charList[0] = F("None"); + charOpts[0] = 0; + + for (int i = 0; i < charset.length(); i++) { + charList[i + 1] = charset[i]; + charOpts[i + 1] = static_cast(charset[i]); + } + addFormSelector(rowLabel, id, len, charList, charOpts, value); + + if (!String(additionalText).isEmpty()) { + addUnit(additionalText); + } +} + // ******************************************************************************** // Add a selector form // ******************************************************************************** diff --git a/src/src/WebServer/Markup_Forms.h b/src/src/WebServer/Markup_Forms.h index 6377f6210..c4ab65af3 100644 --- a/src/src/WebServer/Markup_Forms.h +++ b/src/src/WebServer/Markup_Forms.h @@ -237,6 +237,15 @@ void addFormIPaccessControlSelect(const __FlashStringHelper * label, const __FlashStringHelper * id, int choice); +// ******************************************************************************** +// a Separator character selector +// ******************************************************************************** +void addFormSeparatorCharInput(const __FlashStringHelper *rowLabel, + const __FlashStringHelper *id, + int value, + const String & charset, + const __FlashStringHelper *additionalText = F("")); + // ******************************************************************************** // Add a selector form // ******************************************************************************** From a6292b79615a655f81497ca895cbf3f7d191170e Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Wed, 14 Dec 2022 22:25:02 +0100 Subject: [PATCH 11/41] [P020/P044] Add feature to replace spaces and newlines in received data --- src/_P020_Ser2Net.ino | 10 ++++++++++ src/src/PluginStructs/P020_data_struct.cpp | 11 +++++++++++ src/src/PluginStructs/P020_data_struct.h | 6 ++++++ 3 files changed, 27 insertions(+) diff --git a/src/_P020_Ser2Net.ino b/src/_P020_Ser2Net.ino index 9eabece35..2874831f8 100644 --- a/src/_P020_Ser2Net.ino +++ b/src/_P020_Ser2Net.ino @@ -8,6 +8,7 @@ /************ * Changelog: + * 2022-12-12 tonhuisman: Add character conversion for the received serial data, act on Space and/or Newline * 2022-10-11 tonhuisman: Add option for including the message in P1 #data event * 2022-10-09 tonhuisman: Check P044 migration on PLUGIN_INIT too, still needs a manual save (from UI or by save command) * 2022-10-08 tonhuisman: Merged code from P044 into this plugin, and use a global flag to emulate P044 with P020 @@ -197,6 +198,12 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) addFormNote(F("When enabled, passes the entire message in the event. Warning: can cause memory overflow issues!")); # endif // ifndef LIMIT_BUILD_SIZE + addFormSeparatorCharInput(F("Replace spaces in event by"), F("replspace"), + P020_REPLACE_SPACE, F(P020_REPLACE_CHAR_SET)); + + addFormSeparatorCharInput(F("Replace newlines in event by"), F("replcrlf"), + P020_REPLACE_NEWLINE, F(P020_REPLACE_CHAR_SET)); + addFormCheckBox(F("Process events without client"), F("pignoreclient"), P020_IGNORE_CLIENT_CONNECTED); # ifndef LIMIT_BUILD_SIZE addFormNote(F("When enabled, will process serial data without a network client connected.")); @@ -245,6 +252,9 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) } P020_LED_PIN = getFormItemInt(F("pledpin")); + P020_REPLACE_SPACE = getFormItemInt(F("replspace")); + P020_REPLACE_NEWLINE = getFormItemInt(F("replcrlf")); + uint32_t lSettings = 0u; bitWrite(lSettings, P020_FLAG_IGNORE_CLIENT, isFormItemChecked(F("pignoreclient"))); bitWrite(lSettings, P020_FLAG_LED_ENABLED, isFormItemChecked(F("pled"))); diff --git a/src/src/PluginStructs/P020_data_struct.cpp b/src/src/PluginStructs/P020_data_struct.cpp index 14e9e866a..afa4be64a 100644 --- a/src/src/PluginStructs/P020_data_struct.cpp +++ b/src/src/PluginStructs/P020_data_struct.cpp @@ -18,6 +18,8 @@ P020_Task::P020_Task(struct EventStruct *event) : _taskIndex(event->TaskIndex) { } _ledEnabled = P020_GET_LED_ENABLED == 1; _ledInverted = P020_GET_LED_INVERTED == 1; + _space = static_cast(P020_REPLACE_SPACE); + _newline = static_cast(P020_REPLACE_NEWLINE); } P020_Task::~P020_Task() { @@ -25,6 +27,7 @@ P020_Task::~P020_Task() { delete ser2netServer; ser2netServer = nullptr; } + if (ser2netSerial != nullptr) { delete ser2netSerial; ser2netSerial = nullptr; @@ -357,6 +360,14 @@ void P020_Task::checkBlinkLED() { } void P020_Task::addChar(char ch) { + if ((ch == 0x20) && (_space > 0)) { ch = _space; } + + if (_newline > 0) { + if (ch == '\n') { ch = _newline; } + + if (ch == '\r') { return; } // Ignore CR if LF is replaced + } + serial_buffer += ch; } diff --git a/src/src/PluginStructs/P020_data_struct.h b/src/src/PluginStructs/P020_data_struct.h index 94c1b9699..73be96185 100644 --- a/src/src/PluginStructs/P020_data_struct.h +++ b/src/src/PluginStructs/P020_data_struct.h @@ -14,8 +14,12 @@ # define P020_SERVER_PORT ExtraTaskSettings.TaskDevicePluginConfigLong[0] # define P020_BAUDRATE ExtraTaskSettings.TaskDevicePluginConfigLong[1] +# define P020_REPLACE_CHAR_SET ",;:.!^|/\\" + # define P020_LED_PIN PCONFIG(0) # define P020_SERIAL_CONFIG PCONFIG(1) +# define P020_REPLACE_SPACE PCONFIG(2) +# define P020_REPLACE_NEWLINE PCONFIG(3) # define P020_RX_WAIT PCONFIG(4) # define P020_SERIAL_PROCESSING PCONFIG(5) # define P020_RESET_TARGET_PIN PCONFIG(6) @@ -142,6 +146,8 @@ struct P020_Task : public PluginTaskData_base { bool _P1EventData = false; size_t _maxDataGramSize = P020_DATAGRAM_MAX_SIZE; ParserState state = ParserState::WAITING; + char _space = 0; + char _newline = 0; }; #endif // ifdef USES_P020 From 47ef2b88294ae54524b5ddc3208dec488861156b Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Wed, 14 Dec 2022 22:25:40 +0100 Subject: [PATCH 12/41] [P020] Add plugin documentation --- docs/source/Plugin/P020.rst | 75 ++++++++++++++++-- .../Plugin/P020_DeviceConfiguration.png | Bin 0 -> 69184 bytes .../Plugin/P020_EventProcessingOptions.png | Bin 0 -> 22160 bytes .../Plugin/P020_ReplaceCharInEventOptions.png | Bin 0 -> 42581 bytes 4 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 docs/source/Plugin/P020_DeviceConfiguration.png create mode 100644 docs/source/Plugin/P020_EventProcessingOptions.png create mode 100644 docs/source/Plugin/P020_ReplaceCharInEventOptions.png diff --git a/docs/source/Plugin/P020.rst b/docs/source/Plugin/P020.rst index 1ec3a7609..9b35ed7e0 100644 --- a/docs/source/Plugin/P020.rst +++ b/docs/source/Plugin/P020.rst @@ -26,23 +26,82 @@ Supported hardware |P020_usedby| +Configuration +------------- + +.. image:: P020_DeviceConfiguration.png + +* **Name** In the Name field a unique name should be entered. + +* **Enabled** When unchecked the plugin is not enabled. + Sensor ^^^^^^ See: :ref:`SerialHelper_page` +Device Settings +^^^^^^^^^^^^^^^ -**TODO**: Complete this documentation... +* **TCP Port**: The port for an external network client to read the data from, range 1..65535. The used port number must be unique within the device. -.. Commands available -.. ^^^^^^^^^^^^^^^^^^ +* **Baud Rate / Serial config**: See *Serial helper configuration*, above. -.. .. include:: P020_commands.repl +* **Event Processing**: Select the type of data that is expected, to enable correct preprocessing. Available options: -.. Events -.. ~~~~~~ +.. image:: P020_EventProcessingOptions.png + +* *None*: No special processing, what is received is sent out to the network client, not generating an event. + +* *Generic*: No special processing, received data is sent to the network client, and an event ``!Serial#``, containing the message as is, is generated. Spaces and newlines are processed as configured below. + +* *RFLink*: Specifically designed for receiving serial data from RFLink devices, it follows this process: + + * Remove the regular RFLink ``20;xx;`` prefix + * Check for prefix ``ESPEASY;``, if found, remove the prefix and generate event ``RFLink#``. The ```` will contain commands to be handled by ESPEasy. + * If previous prefix is not found, generate event ``!RFLink#``, containing the entire received data. Spaces and newlines are processed as configured below. + + *Also see the* **Multiple lines processing** *option, below.* + +* *P1 WiFi Gateway*: Process the data, received from a P1 Energy meter, that does a checksum validation, as included in the message. No separate data values are available in ESPEasy, these are usually handled by Home automation systems that support the P1 protocol via TCP network communication. An event ``#Data`` is generated when a valid P1 packet is received. + + Replacing spaces or newlines should be **disabled** for the P1 protocol data to be handled properly as these replacements will disturb the checksum calculation, and also, the **Multiple lines processing** should be disabled if the data is to be handled as P1 protocol data, as that does contain newlines. + +.. spacer + +* **P1 #data event with message**: When enabled, the *P1 WiFi Gateway* Event Processing option will include the received message. **WARNING** This may easily cause memory overflow exceptions, especially when running on ESP8266 or other low-memory situations! + +* **Replace spaces in event by**: Here a single character can be selected to replace all spaces during receiving the data. +* **Replace newlines in event by**: Here a single character can be selected to replace all newlines during receiving the data. When enabled, all linefeeds are replaced, and all carriage returns (if any) are discarded. + +.. image:: P020_ReplaceCharInEventOptions.png + +The available set of replacement characters is ``, ; : . ! ^ | / \`` (comma, semicolon, colon, period, exclamation, caret, pipe, slash and backslash). When set to None, no replacement will be done. + +* **Process events without client**: By default, if no network client is connected, no serial data will be received and processed either. Enabling this option enables receiving data and generating events without a TCP client connected. + +* **Multiple lines processing**: When enabled, all received data will be split at a linefeed and sent out/event generated as separate messages. + +* **RX Receive timeout (mSec)**: If parts of serial data packets are somewhat delayed, but should still be handled as a single message, then the delay to wait for the next part can be configured here. 0 disables the delay. + +* **Reset target after init**: Select a GPIO pin that should be pulled low once during initialization of the plugin, used to synchronize the external serial data source with the plugin. + +* **RX Buffer size (bytes)**: To not overburden the memory use of the plugin, the buffer size is set rather low. Some serial devices, like energy meters may require a larger buffer if the message exceeds this size. Range: 256..1024. + +Led +^^^ + +* **Led enabled**: To enable a *data is being processed* activity led. + +* **Led pin**: The GPIO pin the Led is connected to. + +* **Led inverted**: Iverts the on/off state for the Led. + +Data Acquisition +^^^^^^^^^^^^^^^^ + +The Data Acquisition and Send to Controller settings are standard available configuration items. Send to Controller only when one or more Controllers are configured. *Single event with all values* option is not applicable for this plugin. -.. .. include:: P020_events.repl Change log ---------- @@ -50,6 +109,8 @@ Change log .. versionchanged:: 2.0 ... + |changed| 2022-12-13: Merge of P020 and P044 to reduce code size and combine features, as P044 was initially started as a spin-off from P020, but not evolved with the P020 features. + |added| Major overhaul for 2.0 release. diff --git a/docs/source/Plugin/P020_DeviceConfiguration.png b/docs/source/Plugin/P020_DeviceConfiguration.png new file mode 100644 index 0000000000000000000000000000000000000000..086d5bc3260db0b9f224500e49e0b162f56ff79b GIT binary patch literal 69184 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 b@lCMn9v1#g{$rWQPLZjRl_A>T&L95^U{OOs literal 0 HcmV?d00001 diff --git a/docs/source/Plugin/P020_EventProcessingOptions.png b/docs/source/Plugin/P020_EventProcessingOptions.png new file mode 100644 index 0000000000000000000000000000000000000000..a65fcfbbdbed5f0581580d1dd5d614108564efe9 GIT binary patch literal 22160 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 literal 0 HcmV?d00001 diff --git a/docs/source/Plugin/P020_ReplaceCharInEventOptions.png b/docs/source/Plugin/P020_ReplaceCharInEventOptions.png new file mode 100644 index 0000000000000000000000000000000000000000..8786ca8887df4429fa8e07dc5af200c3c0e34380 GIT binary patch literal 42581 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 literal 0 HcmV?d00001 From 80a175ee3a205dc5f649655d45331caaf483d920 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Wed, 14 Dec 2022 22:28:23 +0100 Subject: [PATCH 13/41] [P037] Implement replacement character input selector, update documentation --- docs/source/Plugin/P037.rst | 8 ++++++- .../Plugin/P037_ReplaceByCommaOptions.png | Bin 0 -> 36542 bytes src/_P037_MQTTImport.ino | 22 +++++++----------- src/src/PluginStructs/P037_data_struct.h | 6 +++-- 4 files changed, 19 insertions(+), 17 deletions(-) create mode 100644 docs/source/Plugin/P037_ReplaceByCommaOptions.png diff --git a/docs/source/Plugin/P037.rst b/docs/source/Plugin/P037.rst index 00c1d5096..6c7315269 100644 --- a/docs/source/Plugin/P037.rst +++ b/docs/source/Plugin/P037.rst @@ -190,11 +190,17 @@ Option: Limit events being generated * **Max. # events in event queue**: As a protection against event-overflow this configures a check for the queue-length, so if more than the selected number of events is still in the queue, new events will be discarded until some events are processed, and the remaining is less than this count. When set to 0 this check is disabled. -Option: Modify separater character in events +Option: Modify separator character in events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * **To replace by comma in event**: Select a character that is to be replaced by a comma, before it is put into the event-queue. There is a limited set of characters that can be replaced, to avoid ending up with malformed events. +Available options: + +.. image:: P037_ReplaceByCommaOptions.png + +The available set of replacement characters is ``! @ $ % ^ & * ; : . | / \`` (exclamation, at, dollar, percent, caret, ampersand, semicolon, colon, period, pipe, slash and backslash). When set to None, no replacement will be done. + This can be used to 'transform' the content of a JSON message so the used separator is a comma, for easier use in rules. Topic Subscriptions diff --git a/docs/source/Plugin/P037_ReplaceByCommaOptions.png b/docs/source/Plugin/P037_ReplaceByCommaOptions.png new file mode 100644 index 0000000000000000000000000000000000000000..bba17486e63ed5d4204afde6da4c08897930f46f GIT binary patch literal 36542 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 - addRowLabel(F("To replace by comma in event")); - addTextBox(F("preplch"), character, 1, false, false, F("[!@$%^ &*;:.|/\\]"), F("widenumber")); - addUnit(F("Single character only, limited to: ! @ $ % ^ & * ; : . | / \\ is replaced by: , ")); + addFormSeparatorCharInput(F("To replace by comma in event"), F("preplch"), + P037_REPLACE_BY_COMMA, F(P037_REPLACE_CHAR_SET)); } # endif // if P037_REPLACE_BY_COMMA_SUPPORT @@ -234,7 +232,7 @@ boolean Plugin_037(uint8_t function, struct EventStruct *event, String& string) if (nullptr == P037_data) { return success; } - P037_data->loadSettings(); // FIXME TD-er: Is this loadSettings still needed or even desired? + P037_data->loadSettings(); // FIXME TD-er: Is this loadSettings still needed or even desired? # if P037_JSON_SUPPORT P037_PARSE_JSON = getFormItemInt(F("pjson")); @@ -248,13 +246,9 @@ boolean Plugin_037(uint8_t function, struct EventStruct *event, String& string) P037_SEND_EVENTS = isFormItemChecked(F("p037_send_events")) ? 1 : 0; P037_DEDUPLICATE_EVENTS = isFormItemChecked(F("pdedupe")) ? 1 : 0; P037_QUEUEDEPTH_EVENTS = getFormItemInt(F("pquedepth")); - # if P037_REPLACE_BY_COMMA_SUPPORT - String character = webArg(F("preplch")); - P037_REPLACE_BY_COMMA = character[0]; - if (P037_REPLACE_BY_COMMA == 0x20) { // Space -> 0 - P037_REPLACE_BY_COMMA = 0x0; - } + # if P037_REPLACE_BY_COMMA_SUPPORT + P037_REPLACE_BY_COMMA = getFormItemInt(F("preplch")); # endif // if P037_REPLACE_BY_COMMA_SUPPORT success = P037_data->webform_save( @@ -279,7 +273,7 @@ boolean Plugin_037(uint8_t function, struct EventStruct *event, String& string) P037_data_struct *P037_data = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr != P037_data && P037_data->loadSettings()) { + if ((nullptr != P037_data) && P037_data->loadSettings()) { // When we edit the subscription data from the webserver, the plugin is called again with init. // In order to resubscribe we have to disconnect and reconnect in order to get rid of any obsolete subscriptions if (MQTTclient_connected) { @@ -346,7 +340,7 @@ boolean Plugin_037(uint8_t function, struct EventStruct *event, String& string) return success; } - String unparsedPayload; // To keep an unprocessed copy + String unparsedPayload; // To keep an unprocessed copy bool checkJson = false; diff --git a/src/src/PluginStructs/P037_data_struct.h b/src/src/PluginStructs/P037_data_struct.h index a00c602a9..b5b520b27 100644 --- a/src/src/PluginStructs/P037_data_struct.h +++ b/src/src/PluginStructs/P037_data_struct.h @@ -94,9 +94,11 @@ # define P037_OPERAND_LIST F("=%") # define P037_FILTER_COUNT 3 -# define P037_FILTER_LIST F("=-:") // Length should at least match P037_FILTER_COUNT +# define P037_FILTER_LIST F("=-:") // Length should at least match P037_FILTER_COUNT -# define P037_VALUE_SEPARATOR '\x02' // Separator outside of the normal ascii character values +# define P037_VALUE_SEPARATOR '\x02' // Separator outside of the normal ascii character values + +# define P037_REPLACE_CHAR_SET "!@$%^&*;:.|/\\" // Allowable set of characters to be replaced by a comma // Data structure struct P037_data_struct : public PluginTaskData_base From e0b3250302fd72266b0a36d11ae8fd99eeeb442a Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Wed, 14 Dec 2022 22:49:51 +0100 Subject: [PATCH 14/41] [UI] Fix unsupported feature compilation error, fix signed/unsigned warning --- src/_P020_Ser2Net.ino | 4 ++-- src/_P037_MQTTImport.ino | 2 +- src/src/WebServer/Markup_Forms.cpp | 2 +- src/src/WebServer/Markup_Forms.h | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/_P020_Ser2Net.ino b/src/_P020_Ser2Net.ino index 2874831f8..bf362064b 100644 --- a/src/_P020_Ser2Net.ino +++ b/src/_P020_Ser2Net.ino @@ -199,10 +199,10 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) # endif // ifndef LIMIT_BUILD_SIZE addFormSeparatorCharInput(F("Replace spaces in event by"), F("replspace"), - P020_REPLACE_SPACE, F(P020_REPLACE_CHAR_SET)); + P020_REPLACE_SPACE, F(P020_REPLACE_CHAR_SET), F("")); addFormSeparatorCharInput(F("Replace newlines in event by"), F("replcrlf"), - P020_REPLACE_NEWLINE, F(P020_REPLACE_CHAR_SET)); + P020_REPLACE_NEWLINE, F(P020_REPLACE_CHAR_SET), F("")); addFormCheckBox(F("Process events without client"), F("pignoreclient"), P020_IGNORE_CLIENT_CONNECTED); # ifndef LIMIT_BUILD_SIZE diff --git a/src/_P037_MQTTImport.ino b/src/_P037_MQTTImport.ino index f38f3a59c..8819770d8 100644 --- a/src/_P037_MQTTImport.ino +++ b/src/_P037_MQTTImport.ino @@ -195,7 +195,7 @@ boolean Plugin_037(uint8_t function, struct EventStruct *event, String& string) # if P037_REPLACE_BY_COMMA_SUPPORT { addFormSeparatorCharInput(F("To replace by comma in event"), F("preplch"), - P037_REPLACE_BY_COMMA, F(P037_REPLACE_CHAR_SET)); + P037_REPLACE_BY_COMMA, F(P037_REPLACE_CHAR_SET), F("")); } # endif // if P037_REPLACE_BY_COMMA_SUPPORT diff --git a/src/src/WebServer/Markup_Forms.cpp b/src/src/WebServer/Markup_Forms.cpp index 42e4d49ad..ed6254f55 100644 --- a/src/src/WebServer/Markup_Forms.cpp +++ b/src/src/WebServer/Markup_Forms.cpp @@ -404,7 +404,7 @@ void addFormSeparatorCharInput(const __FlashStringHelper *rowLabel, charList[0] = F("None"); charOpts[0] = 0; - for (int i = 0; i < charset.length(); i++) { + for (uint16_t i = 0; i < charset.length(); i++) { charList[i + 1] = charset[i]; charOpts[i + 1] = static_cast(charset[i]); } diff --git a/src/src/WebServer/Markup_Forms.h b/src/src/WebServer/Markup_Forms.h index c4ab65af3..57643e0d6 100644 --- a/src/src/WebServer/Markup_Forms.h +++ b/src/src/WebServer/Markup_Forms.h @@ -244,7 +244,7 @@ void addFormSeparatorCharInput(const __FlashStringHelper *rowLabel, const __FlashStringHelper *id, int value, const String & charset, - const __FlashStringHelper *additionalText = F("")); + const __FlashStringHelper *additionalText); // ******************************************************************************** // Add a selector form From b20af2b950bce7f3087cfb6b64e0b0fbe88b9428 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Fri, 16 Dec 2022 21:43:22 +0100 Subject: [PATCH 15/41] [P044] Only show usable settings --- src/_P020_Ser2Net.ino | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/_P020_Ser2Net.ino b/src/_P020_Ser2Net.ino index bf362064b..20fab292c 100644 --- a/src/_P020_Ser2Net.ino +++ b/src/_P020_Ser2Net.ino @@ -112,6 +112,9 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) P020_RESET_TARGET_PIN = P020_DEFAULT_RESET_TARGET_PIN; P020_SERIAL_PROCESSING = static_cast(P020_Events::P1WiFiGateway); // Enable P1 WiFi Gateway processing (only) P020_LED_PIN = P020_STATUS_LED; + P020_RX_WAIT = 0; + P020_REPLACE_SPACE = 0; // Force empty + P020_REPLACE_NEWLINE = 0; bitSet(P020_FLAGS, P020_FLAG_LED_ENABLED); bitSet(P020_FLAGS, P020_FLAG_P044_MODE_SAVED); // Inital config, no conversion needed } else { @@ -198,11 +201,13 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) addFormNote(F("When enabled, passes the entire message in the event. Warning: can cause memory overflow issues!")); # endif // ifndef LIMIT_BUILD_SIZE - addFormSeparatorCharInput(F("Replace spaces in event by"), F("replspace"), - P020_REPLACE_SPACE, F(P020_REPLACE_CHAR_SET), F("")); + if (!P020_Emulate_P044) { // Not appropriate for P1 WiFi Gateway + addFormSeparatorCharInput(F("Replace spaces in event by"), F("replspace"), + P020_REPLACE_SPACE, F(P020_REPLACE_CHAR_SET), F("")); - addFormSeparatorCharInput(F("Replace newlines in event by"), F("replcrlf"), - P020_REPLACE_NEWLINE, F(P020_REPLACE_CHAR_SET), F("")); + addFormSeparatorCharInput(F("Replace newlines in event by"), F("replcrlf"), + P020_REPLACE_NEWLINE, F(P020_REPLACE_CHAR_SET), F("")); + } addFormCheckBox(F("Process events without client"), F("pignoreclient"), P020_IGNORE_CLIENT_CONNECTED); # ifndef LIMIT_BUILD_SIZE @@ -252,8 +257,10 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) } P020_LED_PIN = getFormItemInt(F("pledpin")); - P020_REPLACE_SPACE = getFormItemInt(F("replspace")); - P020_REPLACE_NEWLINE = getFormItemInt(F("replcrlf")); + if (!P020_Emulate_P044) { + P020_REPLACE_SPACE = getFormItemInt(F("replspace")); + P020_REPLACE_NEWLINE = getFormItemInt(F("replcrlf")); + } uint32_t lSettings = 0u; bitWrite(lSettings, P020_FLAG_IGNORE_CLIENT, isFormItemChecked(F("pignoreclient"))); From 5530e943418074297b7f3cf589af62a66da11b4e Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Fri, 16 Dec 2022 21:44:21 +0100 Subject: [PATCH 16/41] [P044] Update plugin documentation (based on P020) --- docs/source/Plugin/P044.rst | 39 ++++++++++++++---- .../Plugin/P044_DeviceConfiguration.png | Bin 0 -> 45499 bytes .../Plugin/P044_ReplaceCharInEventOptions.png | Bin 0 -> 49568 bytes 3 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 docs/source/Plugin/P044_DeviceConfiguration.png create mode 100644 docs/source/Plugin/P044_ReplaceCharInEventOptions.png diff --git a/docs/source/Plugin/P044.rst b/docs/source/Plugin/P044.rst index f50fd2bd7..3065fe445 100644 --- a/docs/source/Plugin/P044.rst +++ b/docs/source/Plugin/P044.rst @@ -26,23 +26,46 @@ Supported hardware |P044_usedby| +.. note:: This plugin is now merged (back) into :ref:`P020_page` where it initially was forked off from, with using some predefined settings. + +Configuration +------------- + +.. image:: P044_DeviceConfiguration.png + +* **Name** In the Name field a unique name should be entered. + +* **Enabled** When unchecked the plugin is not enabled. + Sensor ^^^^^^ See: :ref:`SerialHelper_page` +Device Settings +^^^^^^^^^^^^^^^ -**TODO**: Complete this documentation... +* **TCP Port**: The port for an external network client to read the data from, range 1..65535. The used port number must be unique within the device. -.. Commands available -.. ^^^^^^^^^^^^^^^^^^ +* **Baud Rate / Serial config**: See *Serial helper configuration*, above. -.. .. include:: P044_commands.repl +* **P1 #data event with message**: When enabled, the *P1 WiFi Gateway* Event Processing option will include the received message. **WARNING** This may easily cause memory overflow exceptions, especially when running on ESP8266 or other low-memory situations! -.. Events -.. ~~~~~~ +* **Process events without client**: By default, if no network client is connected, no serial data will be received and processed either. Enabling this option enables receiving data and generating events without a TCP client connected. + +* **RX Receive timeout (mSec)**: If parts of serial data packets are somewhat delayed, but should still be handled as a single message, then the delay to wait for the next part can be configured here. 0 disables the delay. + +* **Reset target after init**: Select a GPIO pin that should be pulled low once during initialization of the plugin, used to synchronize the external serial data source with the plugin. + +Led +^^^ + +* **Led enabled**: To enable a *data is being processed* activity led. + +* **Led pin**: The GPIO pin the Led is connected to. + +* **Led inverted**: Iverts the on/off state for the Led. -.. .. include:: P044_events.repl Change log ---------- @@ -50,6 +73,8 @@ Change log .. versionchanged:: 2.0 ... + |changed| 2022-10-08: Merge of P020 and P044 to reduce code size and combine features, as P044 was initially started as a spin-off from P020, but not evolved with the P020 features. + |added| Major overhaul for 2.0 release. diff --git a/docs/source/Plugin/P044_DeviceConfiguration.png b/docs/source/Plugin/P044_DeviceConfiguration.png new file mode 100644 index 0000000000000000000000000000000000000000..a8d83191c01706b6280129ef38e8ef4ca66c4381 GIT binary patch literal 45499 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≤1JJkCfh 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 JzjXiCe*t~f9fAM= literal 0 HcmV?d00001 diff --git a/docs/source/Plugin/P044_ReplaceCharInEventOptions.png b/docs/source/Plugin/P044_ReplaceCharInEventOptions.png new file mode 100644 index 0000000000000000000000000000000000000000..df68e230c3c866220287cf9fe846217e974232a8 GIT binary patch literal 49568 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⋘GY#@zqK>$#>Xq?B3F! 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 literal 0 HcmV?d00001 From b2358e07c71858257712c3613c81530cd9953727 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Tue, 3 Jan 2023 19:33:13 +0100 Subject: [PATCH 17/41] [Build] Adjust Custom ESP8266 as already planned to fix build failure --- tools/pio/pre_custom_esp82xx.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/pio/pre_custom_esp82xx.py b/tools/pio/pre_custom_esp82xx.py index 344e9e05e..c133be07b 100644 --- a/tools/pio/pre_custom_esp82xx.py +++ b/tools/pio/pre_custom_esp82xx.py @@ -42,10 +42,8 @@ else: # "-DUSES_P080", # Dallas iButton "-DUSES_P081", # Cron "-DUSES_P082", # GPS - "-DUSES_P085", # AcuDC24x "-DUSES_P098", # PWM motor - "-DUSES_P100", # Pulse Counter - DS2423 # "-DUSES_P087", # Serial Proxy # "-DUSES_P094", # CUL Reader # "-DUSES_P095", # TFT ILI9341 From 5e68eec0da63741522451a0b66d71bc57fd404b1 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 14 Jan 2023 11:01:44 +0100 Subject: [PATCH 18/41] [P020/P044] Adjustments to complete the merge --- src/src/PluginStructs/P020_data_struct.h | 7 +++++-- src/src/PluginStructs/P044_data_struct.h | 8 +++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/src/PluginStructs/P020_data_struct.h b/src/src/PluginStructs/P020_data_struct.h index 73be96185..eeccc3a6b 100644 --- a/src/src/PluginStructs/P020_data_struct.h +++ b/src/src/PluginStructs/P020_data_struct.h @@ -11,8 +11,11 @@ # define PLUGIN_020_DEBUG false // when true: extra logging in serial out !?!?! # endif // ifndef PLUGIN_020_DEBUG -# define P020_SERVER_PORT ExtraTaskSettings.TaskDevicePluginConfigLong[0] -# define P020_BAUDRATE ExtraTaskSettings.TaskDevicePluginConfigLong[1] +# define P020_SET_SERVER_PORT ExtraTaskSettings.TaskDevicePluginConfigLong[0] +# define P020_SET_BAUDRATE ExtraTaskSettings.TaskDevicePluginConfigLong[1] + +# define P020_GET_SERVER_PORT Cache.getTaskDevicePluginConfigLong(event->TaskIndex, 0) +# define P020_GET_BAUDRATE Cache.getTaskDevicePluginConfigLong(event->TaskIndex, 1) # define P020_REPLACE_CHAR_SET ",;:.!^|/\\" diff --git a/src/src/PluginStructs/P044_data_struct.h b/src/src/PluginStructs/P044_data_struct.h index 02eb3d179..632137e93 100644 --- a/src/src/PluginStructs/P044_data_struct.h +++ b/src/src/PluginStructs/P044_data_struct.h @@ -9,8 +9,10 @@ // #define PLUGIN_044_DEBUG // extra logging in serial out -# define P044_WIFI_SERVER_PORT ExtraTaskSettings.TaskDevicePluginConfigLong[0] -# define P044_BAUDRATE ExtraTaskSettings.TaskDevicePluginConfigLong[1] +# define P044_SET_WIFI_SERVER_PORT ExtraTaskSettings.TaskDevicePluginConfigLong[0] +# define P044_SET_BAUDRATE ExtraTaskSettings.TaskDevicePluginConfigLong[1] +# define P044_GET_WIFI_SERVER_PORT Cache.getTaskDevicePluginConfigLong(event->TaskIndex, 0) +# define P044_GET_BAUDRATE Cache.getTaskDevicePluginConfigLong(event->TaskIndex, 1) # define P044_RX_WAIT PCONFIG(0) # define P044_SERIAL_CONFIG PCONFIG(1) # define P044_RESET_TARGET_PIN CONFIG_PIN1 @@ -112,5 +114,5 @@ struct P044_Task : public PluginTaskData_base { bool _ledInverted = false; }; -#endif // ifdef USES_P044 +#endif // ifdef USES_P044_ORG #endif // ifndef PLUGINSTRUCTS_P044_DATA_STRUCT_H From 064912d7387d874fb46ca471094a93adb14d488b Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sun, 15 Jan 2023 20:05:46 +0100 Subject: [PATCH 19/41] [P020] Add extra check before sending P1 data --- src/src/PluginStructs/P020_data_struct.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/src/PluginStructs/P020_data_struct.cpp b/src/src/PluginStructs/P020_data_struct.cpp index afa4be64a..fb17a7e73 100644 --- a/src/src/PluginStructs/P020_data_struct.cpp +++ b/src/src/PluginStructs/P020_data_struct.cpp @@ -205,12 +205,11 @@ void P020_Task::handleSerialIn(struct EventStruct *event) { } if (serial_processing == P020_Events::P1WiFiGateway) { - done = handleP1Char(ser2netSerial->read()); + done = handleP1Char(static_cast(ser2netSerial->read())); } else { - addChar((char)ser2netSerial->read()); + addChar(static_cast(ser2netSerial->read())); } - if (_ledEnabled) { digitalWrite(_ledPin, _ledInverted ? 1 : 0); } @@ -227,6 +226,9 @@ void P020_Task::handleSerialIn(struct EventStruct *event) { if (serial_buffer.length() > 0) { if (ser2netClient.connected()) { // Only send out if a client is connected + if ((serial_processing == P020_Events::P1WiFiGateway) && !serial_buffer.endsWith(F("\r\n"))) { + serial_buffer += F("\r\n"); + } ser2netClient.print(serial_buffer); } From 57666f62c076a87013e96498c4fd55cee730e985 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Tue, 17 Jan 2023 23:25:14 +0100 Subject: [PATCH 20/41] [P020] Revert extra check before sending P1 data, minor improvements --- src/_P020_Ser2Net.ino | 11 ++++------- src/src/PluginStructs/P020_data_struct.cpp | 7 ++++--- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/_P020_Ser2Net.ino b/src/_P020_Ser2Net.ino index 6c7a48439..18c84671c 100644 --- a/src/_P020_Ser2Net.ino +++ b/src/_P020_Ser2Net.ino @@ -58,6 +58,7 @@ bool P020_ConvertP044Settings(struct EventStruct *event) { CONFIG_PIN2 = 1; // P044 TX pin // Former P044 defaults + P020_FLAGS = 0u; // Reset bitSet(P020_FLAGS, P020_FLAG_LED_ENABLED); // Led enabled... P020_LED_PIN = P020_STATUS_LED; // ...and connected to GPIO-12 P020_SERIAL_PROCESSING = static_cast(P020_Events::P1WiFiGateway); // Enable P1 WiFi Gateway processing @@ -115,6 +116,7 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) P020_RX_WAIT = 0; P020_REPLACE_SPACE = 0; // Force empty P020_REPLACE_NEWLINE = 0; + P020_FLAGS = 0u; // Reset bitSet(P020_FLAGS, P020_FLAG_LED_ENABLED); bitSet(P020_FLAGS, P020_FLAG_P044_MODE_SAVED); // Inital config, no conversion needed } else { @@ -254,14 +256,11 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) } else { P020_SERIAL_PROCESSING = getFormItemInt(F("pevents")); P020_RX_BUFFER = getFormItemInt(F("prx_buffer")); + P020_REPLACE_SPACE = getFormItemInt(F("replspace")); + P020_REPLACE_NEWLINE = getFormItemInt(F("replcrlf")); } P020_LED_PIN = getFormItemInt(F("pledpin")); - if (!P020_Emulate_P044) { - P020_REPLACE_SPACE = getFormItemInt(F("replspace")); - P020_REPLACE_NEWLINE = getFormItemInt(F("replcrlf")); - } - uint32_t lSettings = 0u; bitWrite(lSettings, P020_FLAG_IGNORE_CLIENT, isFormItemChecked(F("pignoreclient"))); bitWrite(lSettings, P020_FLAG_LED_ENABLED, isFormItemChecked(F("pled"))); @@ -291,8 +290,6 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) } # endif // ifdef USES_P044 - LoadTaskSettings(event->TaskIndex); - if (P020_GET_LED_ENABLED && validGpio(P020_LED_PIN)) { pinMode(P020_LED_PIN, OUTPUT); digitalWrite(P020_LED_PIN, P020_GET_LED_INVERTED ? 1 : 0); diff --git a/src/src/PluginStructs/P020_data_struct.cpp b/src/src/PluginStructs/P020_data_struct.cpp index fb17a7e73..9f47c8a10 100644 --- a/src/src/PluginStructs/P020_data_struct.cpp +++ b/src/src/PluginStructs/P020_data_struct.cpp @@ -226,9 +226,10 @@ void P020_Task::handleSerialIn(struct EventStruct *event) { if (serial_buffer.length() > 0) { if (ser2netClient.connected()) { // Only send out if a client is connected - if ((serial_processing == P020_Events::P1WiFiGateway) && !serial_buffer.endsWith(F("\r\n"))) { - serial_buffer += F("\r\n"); - } + // FIXME tonhuisman: Disable extra check for now as it reportedly doesn't work as intended + // if ((serial_processing == P020_Events::P1WiFiGateway) && !serial_buffer.endsWith(F("\r\n"))) { + // serial_buffer += F("\r\n"); + // } ser2netClient.print(serial_buffer); } From 151cf5e2c07d26fa506eb6fd48164abb628ea26d Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Tue, 21 Mar 2023 23:27:39 +0100 Subject: [PATCH 21/41] [StringConverter] Add parseHexTextString() --- src/src/Helpers/StringConverter.cpp | 41 +++++++++++++++++++++++++++++ src/src/Helpers/StringConverter.h | 2 ++ 2 files changed, 43 insertions(+) diff --git a/src/src/Helpers/StringConverter.cpp b/src/src/Helpers/StringConverter.cpp index 4a6fdbc72..d184b0883 100644 --- a/src/src/Helpers/StringConverter.cpp +++ b/src/src/Helpers/StringConverter.cpp @@ -800,6 +800,47 @@ String tolerantParseStringKeepCase(const String& string, uint8_t indexFind, char return parseStringKeepCase(string, indexFind, separator, trimResult); } +/***************************************************************************** + * handles: 0xXX,text,0xXX," more text ",0xXX starting from index 2 (1-based) + ****************************************************************************/ +String parseHexTextString(const String& argument, int index) { + String result; + + // Ignore these characters when used as hex-byte separators (0x01ab 23-cd:45 -> 0x01,0xab,0x23,0xcd,0x45) + const String skipChars = F(" -:"); + + result.reserve(argument.length()); // longer than needed, most likely + int i = index; + String arg = parseStringKeepCase(argument, i, ',', false); + + while (!arg.isEmpty()) { + if ((arg.startsWith(F("0x")) || arg.startsWith(F("0X")))) { + size_t j = 2; + + while (j < arg.length()) { + int hex = -1; + + if (validIntFromString(concat(F("0x"), arg.substring(j, j + 2)), hex) && (hex > -1) && (hex < 256)) { + result += char(hex); + } + j += 2; + int c = skipChars.indexOf(arg.substring(j, j + 1)); + + while (j < arg.length() && c > -1) { + j++; + c = skipChars.indexOf(arg.substring(j, j + 1)); + } + } + } else { + result += arg; + } + i++; + arg = parseStringKeepCase(argument, i, ',', false); + } + + return result; +} + /*********************************************************************************************\ GetTextIndexed: Get text from large PROGMEM stored string Items are separated by a '|' diff --git a/src/src/Helpers/StringConverter.h b/src/src/Helpers/StringConverter.h index 8fe05903e..8f23936cf 100644 --- a/src/src/Helpers/StringConverter.h +++ b/src/src/Helpers/StringConverter.h @@ -291,6 +291,8 @@ String tolerantParseStringKeepCase(const String& string, char separator = ',', bool trimResult = true); +String parseHexTextString(const String& argument, + int index = 2); /*********************************************************************************************\ From a0ae909850aa6e73e50dd71678089bd83d053337 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Tue, 21 Mar 2023 23:28:15 +0100 Subject: [PATCH 22/41] [P020] Add command `serialsendmix` --- src/_P020_Ser2Net.ino | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/_P020_Ser2Net.ino b/src/_P020_Ser2Net.ino index 751c371bd..362264209 100644 --- a/src/_P020_Ser2Net.ino +++ b/src/_P020_Ser2Net.ino @@ -456,7 +456,14 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) task->ser2netSerial->write(string.substring(11).c_str()); task->ser2netSerial->flush(); success = true; - } + } else + + if (equals(command, F("serialsendmix"))) { + String argument = parseHexTextString(string); + task->ser2netSerial->write(argument.c_str()); + task->ser2netSerial->flush(); + success = true; + } else if ((equals(command, F("ser2netclientsend"))) && (task->hasClientConnected())) { task->ser2netClient.print(string.substring(18)); From 8da43626d1b2c3482d7eab6f6ae603e195da6029 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Wed, 22 Mar 2023 13:51:24 +0100 Subject: [PATCH 23/41] [StringConverter] Improvements to parseHexTextString() --- src/src/Helpers/StringConverter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/src/Helpers/StringConverter.cpp b/src/src/Helpers/StringConverter.cpp index d184b0883..7bf3d7e3b 100644 --- a/src/src/Helpers/StringConverter.cpp +++ b/src/src/Helpers/StringConverter.cpp @@ -807,7 +807,7 @@ String parseHexTextString(const String& argument, int index) { String result; // Ignore these characters when used as hex-byte separators (0x01ab 23-cd:45 -> 0x01,0xab,0x23,0xcd,0x45) - const String skipChars = F(" -:"); + const String skipChars = F(" -:,.;"); result.reserve(argument.length()); // longer than needed, most likely int i = index; @@ -820,7 +820,7 @@ String parseHexTextString(const String& argument, int index) { while (j < arg.length()) { int hex = -1; - if (validIntFromString(concat(F("0x"), arg.substring(j, j + 2)), hex) && (hex > -1) && (hex < 256)) { + if (validIntFromString(concat(F("0x"), arg.substring(j, j + 2)), hex) && (hex > 0) && (hex < 256)) { result += char(hex); } j += 2; From 8ffc8da2448677353a5c7f544a08211fc523c4cc Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Thu, 23 Mar 2023 21:12:49 +0100 Subject: [PATCH 24/41] [P020] Add command documentation --- docs/source/Plugin/P020.rst | 5 ++++ docs/source/Plugin/P020_commands.repl | 37 +++++++++++++++++++++++++++ docs/source/Reference/Command.rst | 6 ++--- 3 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 docs/source/Plugin/P020_commands.repl diff --git a/docs/source/Plugin/P020.rst b/docs/source/Plugin/P020.rst index 9b35ed7e0..0ed2b5df5 100644 --- a/docs/source/Plugin/P020.rst +++ b/docs/source/Plugin/P020.rst @@ -103,6 +103,11 @@ Data Acquisition The Data Acquisition and Send to Controller settings are standard available configuration items. Send to Controller only when one or more Controllers are configured. *Single event with all values* option is not applicable for this plugin. +Commands +~~~~~~~~ + +.. include:: P020_commands.repl + Change log ---------- diff --git a/docs/source/Plugin/P020_commands.repl b/docs/source/Plugin/P020_commands.repl new file mode 100644 index 000000000..5b5208c44 --- /dev/null +++ b/docs/source/Plugin/P020_commands.repl @@ -0,0 +1,37 @@ +.. csv-table:: + :header: "Command", "Extra information" + :widths: 20, 30 + + " + ``serialsend,`` + + ````: Text that will be sent (nearly) unprocessed. Only the regular variable replacements will be applied before sending the content to the serial port. + "," + Using this command, either from rules, via http or mqtt, the text that is provided as content is completely sent to the serial port. No extra data is added, other than any (system) variables that are included, being replaced. + " + " + ``ser2netclientsend,`` + + ````: Text that will be sent (nearly) unprocessed. Only the regular variable replacements will be applied before sending the content to the serial port. + "," + This command will only send data to the serial port if the network client *is* connected. + + Using this command, either from rules, via http or mqtt, the text that is provided as content is completely sent to the serial port. No extra data is added, other than any (system) variables that are included, being replaced. + " + " + ``serialsendmix,''[,...]`` + + ````: Text and/or hex byte(s) (having 0x prefix) that will be sent (nearly) unprocessed. Only the regular variable replacements will be applied before sending the content to the serial port. + "," + + This command requires quotes to be used if spaces or commas are part of the content. + + Any ascii character can be sent, even it can not be typed in a text conent, by specifying that as a separate argument: ``serialsendmix,'text, optionally including spaces or commas',0xXX,'0xXXxx XX,xx-XX:xx'`` + + ``'text, optionally including spaces or commas'``: Any text content to be sent to the serial port. Can contain variables. Quotes are only required if space or comma separators are used. + + ``0xXX``: A single character in hexadecimal notation (range: 0x01..0xFF), that is appended to the data to send. + + ``'0xXXxx XX,xx-XX:xx'``: A sequence of hexadecimal values (range: 0x01..0xFF), that *can* be separated by a space, comma, dash, colon, semicolon or period, or are just entered adjecent. Only the first 2 characters should be ``0x`` or ``0X``, the rest is interpreted as hex bytes, and appended to the string to send. Quotes are only required if space or comma separators are used. + Using this command, either from rules, via http or mqtt, the text that is provided as content is completely sent to the serial port. No extra data is added, other than any (system) variables that are included, being replaced. + " diff --git a/docs/source/Reference/Command.rst b/docs/source/Reference/Command.rst index 66f67800e..6dc22dda2 100644 --- a/docs/source/Reference/Command.rst +++ b/docs/source/Reference/Command.rst @@ -249,10 +249,10 @@ P012 :ref:`P012_page` .. .. include:: ../Plugin/P019_commands.repl -.. P020 :ref:`P020_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +P020 :ref:`P020_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. .. include:: ../Plugin/P020_commands.repl +.. include:: ../Plugin/P020_commands.repl P021 :ref:`P021_page` From 6df4649fedb4c81e9919fdf2c1a6f150819bd94b Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 25 Mar 2023 20:13:44 +0100 Subject: [PATCH 25/41] [P020] Correct some typos in documentation --- docs/source/Plugin/P020_commands.repl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/source/Plugin/P020_commands.repl b/docs/source/Plugin/P020_commands.repl index 5b5208c44..775a69c85 100644 --- a/docs/source/Plugin/P020_commands.repl +++ b/docs/source/Plugin/P020_commands.repl @@ -12,11 +12,11 @@ " ``ser2netclientsend,`` - ````: Text that will be sent (nearly) unprocessed. Only the regular variable replacements will be applied before sending the content to the serial port. + ````: Text that will be sent (nearly) unprocessed. Only the regular variable replacements will be applied before sending the content to the network client. "," - This command will only send data to the serial port if the network client *is* connected. + This command will only send data to the network client, when there is an active connection. - Using this command, either from rules, via http or mqtt, the text that is provided as content is completely sent to the serial port. No extra data is added, other than any (system) variables that are included, being replaced. + Using this command, either from rules, via http or mqtt, the text that is provided as content is completely sent to the network client. No extra data is added, other than any (system) variables that are included, being replaced. " " ``serialsendmix,''[,...]`` @@ -26,9 +26,9 @@ This command requires quotes to be used if spaces or commas are part of the content. - Any ascii character can be sent, even it can not be typed in a text conent, by specifying that as a separate argument: ``serialsendmix,'text, optionally including spaces or commas',0xXX,'0xXXxx XX,xx-XX:xx'`` + Any data can be sent, even if it can not be typed in a text content, by specifying that as a separate argument: ``serialsendmix,'text, optionally including spaces or commas',0xXX,'0xXXxx XX,xx-XX:xx'`` - ``'text, optionally including spaces or commas'``: Any text content to be sent to the serial port. Can contain variables. Quotes are only required if space or comma separators are used. + ``'text, optionally including spaces or commas'``: Any text content to be sent to the serial port. Can contain variables. Quotes are only required if spaces or commas (separators) are used. ``0xXX``: A single character in hexadecimal notation (range: 0x01..0xFF), that is appended to the data to send. From 188f6f4c5433119fb5be6fd523f47f0edd7da91a Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 25 Mar 2023 20:41:49 +0100 Subject: [PATCH 26/41] [StringConverter] Add parseHexTextData() to handle 0x00 --- src/src/Helpers/StringConverter.cpp | 43 +++++++++++++++++++++++++++++ src/src/Helpers/StringConverter.h | 2 ++ 2 files changed, 45 insertions(+) diff --git a/src/src/Helpers/StringConverter.cpp b/src/src/Helpers/StringConverter.cpp index 7bf3d7e3b..02933ccd4 100644 --- a/src/src/Helpers/StringConverter.cpp +++ b/src/src/Helpers/StringConverter.cpp @@ -841,6 +841,49 @@ String parseHexTextString(const String& argument, int index) { return result; } +/***************************************************************************** + * handles: 0xXX,text,0xXX," more text ",0xXX starting from index 2 (1-based) + ****************************************************************************/ +std::vector parseHexTextData(const String& argument, int index) { + std::vector result; + + // Ignore these characters when used as hex-byte separators (0x01ab 23-cd:45 -> 0x01,0xab,0x23,0xcd,0x45) + const String skipChars = F(" -:,.;"); + + result.reserve(argument.length()); // longer than needed, most likely + int i = index; + String arg = parseStringKeepCase(argument, i, ',', false); + + while (!arg.isEmpty()) { + if ((arg.startsWith(F("0x")) || arg.startsWith(F("0X")))) { + size_t j = 2; + + while (j < arg.length()) { + int hex = -1; + + if (validIntFromString(concat(F("0x"), arg.substring(j, j + 2)), hex) && (hex > -1) && (hex < 256)) { + result.push_back(char(hex)); + } + j += 2; + int c = skipChars.indexOf(arg.substring(j, j + 1)); + + while (j < arg.length() && c > -1) { + j++; + c = skipChars.indexOf(arg.substring(j, j + 1)); + } + } + } else { + for (size_t s = 0; s < arg.length(); s++) { + result.push_back(arg[s]); + } + } + i++; + arg = parseStringKeepCase(argument, i, ',', false); + } + + return result; +} + /*********************************************************************************************\ GetTextIndexed: Get text from large PROGMEM stored string Items are separated by a '|' diff --git a/src/src/Helpers/StringConverter.h b/src/src/Helpers/StringConverter.h index 8f23936cf..ea4ca8a83 100644 --- a/src/src/Helpers/StringConverter.h +++ b/src/src/Helpers/StringConverter.h @@ -293,6 +293,8 @@ String tolerantParseStringKeepCase(const String& string, String parseHexTextString(const String& argument, int index = 2); +std::vector parseHexTextData(const String& argument, + int index = 2); /*********************************************************************************************\ From 05337a521fb06ff8a3ff424e7fc57d8adc5bc2dc Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 25 Mar 2023 20:43:09 +0100 Subject: [PATCH 27/41] [P020] Use parseHextTextData() to handle 0x00 in data --- src/_P020_Ser2Net.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_P020_Ser2Net.ino b/src/_P020_Ser2Net.ino index 362264209..30afcec84 100644 --- a/src/_P020_Ser2Net.ino +++ b/src/_P020_Ser2Net.ino @@ -459,8 +459,8 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) } else if (equals(command, F("serialsendmix"))) { - String argument = parseHexTextString(string); - task->ser2netSerial->write(argument.c_str()); + std::vector argument = parseHexTextData(string); + task->ser2netSerial->write(&argument[0], argument.size()); task->ser2netSerial->flush(); success = true; } else From 0c99aaad3f7e991b895221132eccced4a1daa282 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 25 Mar 2023 20:46:03 +0100 Subject: [PATCH 28/41] [P020] Update documentation for 0x00..0xFF hex data support in serialsendmix --- docs/source/Plugin/P020_commands.repl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/Plugin/P020_commands.repl b/docs/source/Plugin/P020_commands.repl index 775a69c85..9827b2a65 100644 --- a/docs/source/Plugin/P020_commands.repl +++ b/docs/source/Plugin/P020_commands.repl @@ -30,8 +30,8 @@ ``'text, optionally including spaces or commas'``: Any text content to be sent to the serial port. Can contain variables. Quotes are only required if spaces or commas (separators) are used. - ``0xXX``: A single character in hexadecimal notation (range: 0x01..0xFF), that is appended to the data to send. + ``0xXX``: A single character in hexadecimal notation (range: 0x00..0xFF), that is appended to the data to send. - ``'0xXXxx XX,xx-XX:xx'``: A sequence of hexadecimal values (range: 0x01..0xFF), that *can* be separated by a space, comma, dash, colon, semicolon or period, or are just entered adjecent. Only the first 2 characters should be ``0x`` or ``0X``, the rest is interpreted as hex bytes, and appended to the string to send. Quotes are only required if space or comma separators are used. + ``'0xXXxx XX,xx-XX:xx'``: A sequence of hexadecimal values (range: 0x00..0xFF), that *can* be separated by a space, comma, dash, colon, semicolon or period, or are just entered adjecent. Only the first 2 characters should be ``0x`` or ``0X``, the rest is interpreted as hex bytes, and appended to the string to send. Quotes are only required if space or comma separators are used. Using this command, either from rules, via http or mqtt, the text that is provided as content is completely sent to the serial port. No extra data is added, other than any (system) variables that are included, being replaced. " From 2aba1bf0f192bdd06dfc873ceed43dec15fb2fc0 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 25 Mar 2023 21:37:45 +0100 Subject: [PATCH 29/41] [P020] Update changelog --- src/_P020_Ser2Net.ino | 1 + 1 file changed, 1 insertion(+) diff --git a/src/_P020_Ser2Net.ino b/src/_P020_Ser2Net.ino index 30afcec84..8447d9791 100644 --- a/src/_P020_Ser2Net.ino +++ b/src/_P020_Ser2Net.ino @@ -8,6 +8,7 @@ /************ * Changelog: + * 2023-03-25 tonhuisman: Change serialsendmix to handle 0x00 also, by implementing parseHexTextData() * 2022-12-12 tonhuisman: Add character conversion for the received serial data, act on Space and/or Newline * 2022-10-11 tonhuisman: Add option for including the message in P1 #data event * 2022-10-09 tonhuisman: Check P044 migration on PLUGIN_INIT too, still needs a manual save (from UI or by save command) From d5d31a71c71267d626ef0ae18a1a7b12afca166a Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Mon, 22 May 2023 20:38:16 +0200 Subject: [PATCH 30/41] [P020] Unification of Ser2Net log messages --- src/_P020_Ser2Net.ino | 4 ++-- src/src/PluginStructs/P020_data_struct.cpp | 21 ++++++++++----------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/_P020_Ser2Net.ino b/src/_P020_Ser2Net.ino index 8447d9791..854d76100 100644 --- a/src/_P020_Ser2Net.ino +++ b/src/_P020_Ser2Net.ino @@ -332,7 +332,7 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log; log.reserve(100); - log = concat(F("Ser2net: TaskIndex="), static_cast(event->TaskIndex)); + log = concat(F("Ser2Net: TaskIndex="), static_cast(event->TaskIndex) + 1); log += concat(F(" port="), static_cast(CONFIG_PORT)); log += concat(F(" rxPin="), static_cast(rxPin)); log += concat(F(" txPin="), static_cast(txPin)); @@ -358,7 +358,7 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLogMove(LOG_LEVEL_DEBUG, concat(F("Ser2net : P020_RESET_TARGET_PIN : "), static_cast(P020_RESET_TARGET_PIN))); + addLogMove(LOG_LEVEL_DEBUG, concat(F("Ser2Net: P020_RESET_TARGET_PIN : "), static_cast(P020_RESET_TARGET_PIN))); } # endif // ifndef BUILD_NO_DEBUG pinMode(P020_RESET_TARGET_PIN, OUTPUT); diff --git a/src/src/PluginStructs/P020_data_struct.cpp b/src/src/PluginStructs/P020_data_struct.cpp index bd25c1508..38f852d34 100644 --- a/src/src/PluginStructs/P020_data_struct.cpp +++ b/src/src/PluginStructs/P020_data_struct.cpp @@ -55,10 +55,9 @@ void P020_Task::startServer(uint16_t portnumber) { ser2netServer->begin(); if (serverActive(ser2netServer)) { - addLog(LOG_LEVEL_INFO, String(F("Ser2Net : WiFi server started at port ")) + portnumber); + addLog(LOG_LEVEL_INFO, concat(F("Ser2Net: WiFi server started at port "), portnumber)); } else { - addLog(LOG_LEVEL_ERROR, String(F("Ser2Net : WiFi server start failed at port ")) + - portnumber + String(F(", retrying..."))); + addLog(LOG_LEVEL_ERROR, concat(F("Ser2Net: WiFi server start failed at port "), portnumber) + F(", retrying...")); } } } @@ -69,7 +68,7 @@ void P020_Task::checkServer() { ser2netServer->begin(); if (serverActive(ser2netServer)) { - addLog(LOG_LEVEL_INFO, F("Ser2net : WiFi server started")); + addLog(LOG_LEVEL_INFO, F("Ser2Net: WiFi server started")); } } } @@ -79,7 +78,7 @@ void P020_Task::stopServer() { if (ser2netClient) { ser2netClient.stop(); } clientConnected = false; ser2netServer->close(); - addLog(LOG_LEVEL_INFO, F("Ser2net : WiFi server closed")); + addLog(LOG_LEVEL_INFO, F("Ser2Net: WiFi server closed")); delete ser2netServer; ser2netServer = nullptr; } @@ -102,7 +101,7 @@ bool P020_Task::hasClientConnected() { # endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS sendConnectedEvent(true); - addLog(LOG_LEVEL_INFO, F("Ser2Net : Client connected!")); + addLog(LOG_LEVEL_INFO, F("Ser2Net: Client connected!")); } if (ser2netClient.connected()) @@ -115,7 +114,7 @@ bool P020_Task::hasClientConnected() { { clientConnected = false; sendConnectedEvent(false); - addLog(LOG_LEVEL_INFO, F("Ser2net : Client disconnected!")); + addLog(LOG_LEVEL_INFO, F("Ser2Net: Client disconnected!")); } } return clientConnected; @@ -150,7 +149,7 @@ void P020_Task::serialBegin(const ESPEasySerialPort port, int16_t rxPin, int16_t ser2netSerial->begin(baud, config); # endif // if defined(ESP8266) # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("Ser2net : Serial opened")); + addLog(LOG_LEVEL_DEBUG, F("Ser2Net: Serial opened")); # endif // ifndef BUILD_NO_DEBUG } } @@ -162,7 +161,7 @@ void P020_Task::serialEnd() { clearBuffer(); ser2netSerial = nullptr; # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("Ser2net : Serial closed")); + addLog(LOG_LEVEL_DEBUG, F("Ser2Net: Serial closed")); # endif // ifndef BUILD_NO_DEBUG } } @@ -195,7 +194,7 @@ void P020_Task::handleSerialIn(struct EventStruct *event) { if ((serial_processing != P020_Events::P1WiFiGateway) // P1 handling without this check && (serial_buffer.length() > static_cast(P020_RX_BUFFER))) { # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("Ser2Net : Error: Buffer overflow, discarded input.")); + addLog(LOG_LEVEL_DEBUG, F("Ser2Net: Error: Buffer overflow, discarded input.")); # endif // ifndef BUILD_NO_DEBUG ser2netSerial->read(); } @@ -239,7 +238,7 @@ void P020_Task::handleSerialIn(struct EventStruct *event) { ser2netClient.flush(); clearBuffer(); # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("Ser2Net : data send!")); + addLog(LOG_LEVEL_DEBUG, F("Ser2Net: data send!")); # endif // ifndef BUILD_NO_DEBUG } // done } From 20a56680967d389fe7f0e41d34bad89770b2e149 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Thu, 1 Jun 2023 21:03:58 +0200 Subject: [PATCH 31/41] [Build] Manually apply WiFi build fixes (missing includes) --- src/ESPEasy_common.h | 7 ++++++- src/src/DataStructs/EthernetEventData.cpp | 2 ++ src/src/ESPEasyCore/ESPEasyEth.cpp | 2 ++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/ESPEasy_common.h b/src/ESPEasy_common.h index cef4d30eb..e746b5e7c 100644 --- a/src/ESPEasy_common.h +++ b/src/ESPEasy_common.h @@ -30,7 +30,12 @@ #endif - +#if defined(ESP8266) + # include +#endif // if defined(ESP8266) +#if defined(ESP32) + # include +#endif // if defined(ESP32) #ifdef ESP8266 diff --git a/src/src/DataStructs/EthernetEventData.cpp b/src/src/DataStructs/EthernetEventData.cpp index 78d88de3a..01a8e25b8 100644 --- a/src/src/DataStructs/EthernetEventData.cpp +++ b/src/src/DataStructs/EthernetEventData.cpp @@ -5,6 +5,8 @@ #include "../ESPEasyCore/ESPEasy_Log.h" #include "../Helpers/Networking.h" +#include + // Bit numbers for Eth status #define ESPEASY_ETH_CONNECTED 0 #define ESPEASY_ETH_GOT_IP 1 diff --git a/src/src/ESPEasyCore/ESPEasyEth.cpp b/src/src/ESPEasyCore/ESPEasyEth.cpp index 6163aa689..e169bbd87 100644 --- a/src/src/ESPEasyCore/ESPEasyEth.cpp +++ b/src/src/ESPEasyCore/ESPEasyEth.cpp @@ -23,6 +23,8 @@ #include #endif +#include + bool ethUseStaticIP() { return Settings.ETH_IP[0] != 0 && Settings.ETH_IP[0] != 255; } From e3f8adf83e179f5b8ec6f97770752ba29bc94bbb Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 24 Jun 2023 15:22:40 +0200 Subject: [PATCH 32/41] [ESPEasySerial] Add shortName option to be used as eventname --- lib/ESPEasySerial/ESPEasySerialPort.cpp | 23 ++++++++++++----------- lib/ESPEasySerial/ESPEasySerialPort.h | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/ESPEasySerial/ESPEasySerialPort.cpp b/lib/ESPEasySerial/ESPEasySerialPort.cpp index 92aef1b30..951c50de5 100644 --- a/lib/ESPEasySerial/ESPEasySerialPort.cpp +++ b/lib/ESPEasySerial/ESPEasySerialPort.cpp @@ -1,31 +1,32 @@ #include "ESPEasySerialPort.h" -const __FlashStringHelper* ESPEasySerialPort_toString(ESPEasySerialPort port) +const __FlashStringHelper* ESPEasySerialPort_toString(ESPEasySerialPort port, bool shortName) { switch (port) { case ESPEasySerialPort::not_set: break; #if USES_I2C_SC16IS752 - case ESPEasySerialPort::sc16is752: return F("I2C Serial"); + case ESPEasySerialPort::sc16is752: return shortName ? F("seriali2c") : F("I2C Serial"); #endif // if USES_I2C_SC16IS752 #ifdef ESP8266 - case ESPEasySerialPort::serial0_swap: return F("HW Serial0 swap"); + case ESPEasySerialPort::serial0_swap: return shortName ? F("serial0swap") : F("HW Serial0 swap"); #endif // ifdef ESP8266 - case ESPEasySerialPort::serial0: return F("HW Serial0"); + case ESPEasySerialPort::serial0: return shortName ? F("serial0") : F("HW Serial0"); #if SOC_UART_NUM > 1 - case ESPEasySerialPort::serial1: return F("HW Serial1"); + case ESPEasySerialPort::serial1: return shortName ? F("serial1") : F("HW Serial1"); #endif // if SOC_UART_NUM > 1 #if SOC_UART_NUM > 2 - case ESPEasySerialPort::serial2: return F("HW Serial2"); + case ESPEasySerialPort::serial2: return shortName ? F("serial2") : F("HW Serial2"); #endif // if SOC_UART_NUM > 2 #if USES_SW_SERIAL - case ESPEasySerialPort::software: return F("SW Serial"); + case ESPEasySerialPort::software: return shortName ? F("serialsw") : F("SW Serial"); #endif // if USES_SW_SERIAL #if USES_HWCDC - case ESPEasySerialPort::usb_hw_cdc: return F("USB HWCDC"); + case ESPEasySerialPort::usb_hw_cdc: return shortName ? F("serialhwcdc") : F("USB HWCDC"); #endif // if USES_HWCDC #if USES_USBCDC - case ESPEasySerialPort::usb_cdc_0: return F("USB CDC"); -// case ESPEasySerialPort::usb_cdc_1: return F("USB CDC1"); + case ESPEasySerialPort::usb_cdc_0: return shortName ? F("serialcdc") : F("USB CDC"); + + // case ESPEasySerialPort::usb_cdc_1: return F("USB CDC1"); #endif // if USES_USBCDC case ESPEasySerialPort::MAX_SERIAL_TYPE: break; @@ -103,7 +104,7 @@ bool validSerialPort(ESPEasySerialPort port) #endif // if USES_HWCDC #if USES_USBCDC case ESPEasySerialPort::usb_cdc_0: -// case ESPEasySerialPort::usb_cdc_1: + // case ESPEasySerialPort::usb_cdc_1: #endif // if USES_USBCDC return true; diff --git a/lib/ESPEasySerial/ESPEasySerialPort.h b/lib/ESPEasySerial/ESPEasySerialPort.h index 1d66dd3a8..9d6f76759 100644 --- a/lib/ESPEasySerial/ESPEasySerialPort.h +++ b/lib/ESPEasySerial/ESPEasySerialPort.h @@ -36,7 +36,7 @@ enum class ESPEasySerialPort : uint8_t { }; -const __FlashStringHelper* ESPEasySerialPort_toString(ESPEasySerialPort port); +const __FlashStringHelper* ESPEasySerialPort_toString(ESPEasySerialPort port, bool shortName = false); bool isHWserial(ESPEasySerialPort port); From ca6669c72f1e19e23ed11a4d436c37937cd94651 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 24 Jun 2023 15:23:48 +0200 Subject: [PATCH 33/41] [Helpers] Implement ESPEasySerial shortName option to be used as eventname --- src/src/Helpers/_Plugin_Helper_serial.cpp | 4 ++-- src/src/Helpers/_Plugin_Helper_serial.h | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/src/Helpers/_Plugin_Helper_serial.cpp b/src/src/Helpers/_Plugin_Helper_serial.cpp index a49422832..4eb222617 100644 --- a/src/src/Helpers/_Plugin_Helper_serial.cpp +++ b/src/src/Helpers/_Plugin_Helper_serial.cpp @@ -15,8 +15,8 @@ #include -String serialHelper_getSerialTypeLabel(ESPEasySerialPort serType) { - return ESPEasySerialPort_toString(serType); +String serialHelper_getSerialTypeLabel(ESPEasySerialPort serType, bool shortName) { + return ESPEasySerialPort_toString(serType, shortName); } void serialHelper_log_GpioDescription(ESPEasySerialPort typeHint, int config_pin1, int config_pin2) { diff --git a/src/src/Helpers/_Plugin_Helper_serial.h b/src/src/Helpers/_Plugin_Helper_serial.h index a0efd9bac..5e7cf5474 100644 --- a/src/src/Helpers/_Plugin_Helper_serial.h +++ b/src/src/Helpers/_Plugin_Helper_serial.h @@ -8,7 +8,8 @@ struct ESPeasySerialType; -String serialHelper_getSerialTypeLabel(ESPEasySerialPort serType); +String serialHelper_getSerialTypeLabel(ESPEasySerialPort serType, + bool shortName = false); void serialHelper_log_GpioDescription(ESPEasySerialPort typeHint, int config_pin1, @@ -32,8 +33,8 @@ ESPEasySerialPort serialHelper_getSerialType(struct EventStruct *event); String serialHelper_getSerialTypeLabel(struct EventStruct *event); #ifndef DISABLE_SC16IS752_Serial -void serialHelper_addI2CuartSelectors(int address, - int channel); +void serialHelper_addI2CuartSelectors(int address, + int channel); #endif // ifndef DISABLE_SC16IS752_Serial void serialHelper_webformLoad(struct EventStruct *event); From 280ad63a3f5feb27abce9a7ddb2c7467ae1f0fd9 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 24 Jun 2023 15:27:08 +0200 Subject: [PATCH 34/41] [P020] Increase max. buffer size [P020] Add options for using Serial Port name as event and/or append the task number [P020/P044] Fix plugin initialization when USB (HW)CDC or I2C Serial is selected --- src/_P020_Ser2Net.ino | 28 +++++++++++++++- src/src/PluginStructs/P020_data_struct.cpp | 37 ++++++++++++++++------ src/src/PluginStructs/P020_data_struct.h | 8 +++++ 3 files changed, 63 insertions(+), 10 deletions(-) diff --git a/src/_P020_Ser2Net.ino b/src/_P020_Ser2Net.ino index 854d76100..131e8d0b4 100644 --- a/src/_P020_Ser2Net.ino +++ b/src/_P020_Ser2Net.ino @@ -8,6 +8,10 @@ /************ * Changelog: + * 2023-06-24 tonhuisman: Fix initialization with non-GPIO serial ports like CDC/HW-CDC + * Add option: append the task number to the event-name (Generic and RFLink event options) + * 2023-06-23 tonhuisman: Add option: use Serial Port name (serialxxx -> xxx= 0/1/2/0swap/i2c/sw/hwcdc/cdc) as event-name for Generic events + * 2023-06-02 tonhuisman: Allow buffer up to 2kB. Use ESPEasySerial buffering feature * 2023-03-25 tonhuisman: Change serialsendmix to handle 0x00 also, by implementing parseHexTextData() * 2022-12-12 tonhuisman: Add character conversion for the received serial data, act on Space and/or Newline * 2022-10-11 tonhuisman: Add option for including the message in P1 #data event @@ -204,6 +208,20 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) addFormNote(F("When enabled, passes the entire message in the event. Warning: can cause memory overflow issues!")); # endif // ifndef LIMIT_BUILD_SIZE + if (P020_Events::Generic == static_cast(P020_SERIAL_PROCESSING)) { + addFormCheckBox(F("Use Serial Port as eventname"), F("pevtname"), P020_GET_EVENT_SERIAL_ID); + # ifndef LIMIT_BUILD_SIZE + addFormNote(F("(Event processing: Generic only!)")); + # endif // ifndef LIMIT_BUILD_SIZE + } + + if (P020_Events::P1WiFiGateway != static_cast(P020_SERIAL_PROCESSING)) { + addFormCheckBox(F("Append Task Number to eventname"), F("papptask"), P020_GET_APPEND_TASK_ID); + # ifndef LIMIT_BUILD_SIZE + addFormNote(F("(Event processing: Generic and RFLink only!)")); + # endif // ifndef LIMIT_BUILD_SIZE + } + if (!P020_Emulate_P044) { // Not appropriate for P1 WiFi Gateway addFormSeparatorCharInput(F("Replace spaces in event by"), F("replspace"), P020_REPLACE_SPACE, F(P020_REPLACE_CHAR_SET), F("")); @@ -226,7 +244,7 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) addFormPinSelect(PinSelectPurpose::Generic, F("Reset target after init"), F("presetpin"), P020_RESET_TARGET_PIN); if (!P020_Emulate_P044) { - addFormNumericBox(F("RX buffer size (bytes)"), F("prx_buffer"), P020_RX_BUFFER, 256, 1024); + addFormNumericBox(F("RX buffer size (bytes)"), F("prx_buffer"), P020_RX_BUFFER, 256, 2048); # ifndef LIMIT_BUILD_SIZE addFormNote(F("Standard RX buffer 256B; higher values could be unstable; energy meters could require 1024B")); # endif // ifndef LIMIT_BUILD_SIZE @@ -268,6 +286,14 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) bitWrite(lSettings, P020_FLAG_LED_INVERTED, isFormItemChecked(F("pledinv"))); bitWrite(lSettings, P020_FLAG_P1_EVENT_DATA, isFormItemChecked(F("pp1event"))); + if (P020_Events::Generic == static_cast(P020_SERIAL_PROCESSING)) { + bitWrite(lSettings, P020_FLAG_EVENT_SERIAL_ID, isFormItemChecked(F("pevtname"))); + } + + if (P020_Events::P1WiFiGateway != static_cast(P020_SERIAL_PROCESSING)) { + bitWrite(lSettings, P020_FLAG_APPEND_TASK_ID, isFormItemChecked(F("papptask"))); + } + if (P020_Emulate_P044) { bitSet(lSettings, P020_FLAG_P044_MODE_SAVED); // Set to P044 configuration done on every save } else { diff --git a/src/src/PluginStructs/P020_data_struct.cpp b/src/src/PluginStructs/P020_data_struct.cpp index 38f852d34..56c1c9918 100644 --- a/src/src/PluginStructs/P020_data_struct.cpp +++ b/src/src/PluginStructs/P020_data_struct.cpp @@ -16,10 +16,13 @@ P020_Task::P020_Task(struct EventStruct *event) : _taskIndex(event->TaskIndex) { if (P020_GET_LED_ENABLED) { _ledPin = P020_LED_PIN; // Default pin (12) is already initialized in P020_Task } - _ledEnabled = P020_GET_LED_ENABLED == 1; - _ledInverted = P020_GET_LED_INVERTED == 1; - _space = static_cast(P020_REPLACE_SPACE); - _newline = static_cast(P020_REPLACE_NEWLINE); + _ledEnabled = P020_GET_LED_ENABLED == 1; + _ledInverted = P020_GET_LED_INVERTED == 1; + _space = static_cast(P020_REPLACE_SPACE); + _newline = static_cast(P020_REPLACE_NEWLINE); + _port = static_cast(CONFIG_PORT); + _serialId = P020_GET_EVENT_SERIAL_ID; + _appendTaskId = P020_GET_APPEND_TASK_ID; } P020_Task::~P020_Task() { @@ -139,7 +142,7 @@ void P020_Task::clearBuffer() { void P020_Task::serialBegin(const ESPEasySerialPort port, int16_t rxPin, int16_t txPin, unsigned long baud, uint8_t config) { serialEnd(); - if (rxPin >= 0) { + if (ESPEasySerialPort::not_set != port) { ser2netSerial = new (std::nothrow) ESPeasySerial(port, rxPin, txPin); if (nullptr != ser2netSerial) { @@ -253,7 +256,7 @@ void P020_Task::discardSerialIn() { // We can also use the rules engine for local control! void P020_Task::rulesEngine(const String& message) { - if (!Settings.UseRules || message.isEmpty()) { return; } + if (!Settings.UseRules || message.isEmpty() || (P020_Events::None == serial_processing)) { return; } int NewLinePos = 0; uint16_t StartPos = 0; @@ -279,7 +282,18 @@ void P020_Task::rulesEngine(const String& message) { case P020_Events::None: { break; } case P020_Events::Generic: { // Generic if (NewLinePos > StartPos) { - eventString = F("!Serial#"); + eventString = '!'; // F("!Serial"); + + if (_serialId) { + eventString += ESPEasySerialPort_toString(_port, true); + } else { + eventString += F("Serial"); + } + + if (_appendTaskId) { + eventString += (_taskIndex + 1); + } + eventString += '#'; eventString += message.substring(StartPos, NewLinePos); } break; @@ -290,11 +304,16 @@ void P020_Task::rulesEngine(const String& message) { if (message.substring(StartPos, NewLinePos) .startsWith(F("ESPEASY"))) { // Special treatment for gpio values, strip unneeded parts... StartPos += 8; // Strip "ESPEASY;" - eventString = F("RFLink#"); + eventString = F("RFLink"); } else { - eventString = F("!RFLink#"); // default event as it comes in, literal match needed in rules, using '!' + eventString = F("!RFLink"); // default event as it comes in, literal match needed in rules, using '!' } + if (_appendTaskId) { + eventString += (_taskIndex + 1); + } + eventString += '#'; + if (NewLinePos > StartPos) { eventString += message.substring(StartPos, NewLinePos); } diff --git a/src/src/PluginStructs/P020_data_struct.h b/src/src/PluginStructs/P020_data_struct.h index eeccc3a6b..995b74f32 100644 --- a/src/src/PluginStructs/P020_data_struct.h +++ b/src/src/PluginStructs/P020_data_struct.h @@ -35,12 +35,16 @@ # define P020_FLAG_LED_INVERTED 3 # define P020_FLAG_P1_EVENT_DATA 4 # define P020_FLAG_P044_MODE_SAVED 8 +# define P020_FLAG_EVENT_SERIAL_ID 9 +# define P020_FLAG_APPEND_TASK_ID 10 # define P020_IGNORE_CLIENT_CONNECTED bitRead(P020_FLAGS, P020_FLAG_IGNORE_CLIENT) # define P020_HANDLE_MULTI_LINE bitRead(P020_FLAGS, P020_FLAG_MULTI_LINE) # define P020_GET_LED_ENABLED bitRead(P020_FLAGS, P020_FLAG_LED_ENABLED) # define P020_GET_LED_INVERTED bitRead(P020_FLAGS, P020_FLAG_LED_INVERTED) # define P020_GET_P1_EVENT_DATA bitRead(P020_FLAGS, P020_FLAG_P1_EVENT_DATA) # define P020_GET_P044_MODE_SAVED bitRead(P020_FLAGS, P020_FLAG_P044_MODE_SAVED) +# define P020_GET_EVENT_SERIAL_ID bitRead(P020_FLAGS, P020_FLAG_EVENT_SERIAL_ID) +# define P020_GET_APPEND_TASK_ID bitRead(P020_FLAGS, P020_FLAG_APPEND_TASK_ID) # define P020_DEFAULT_SERVER_PORT 1234 # define P020_DEFAULT_BAUDRATE 115200 @@ -151,6 +155,10 @@ struct P020_Task : public PluginTaskData_base { ParserState state = ParserState::WAITING; char _space = 0; char _newline = 0; + bool _serialId = false; + bool _appendTaskId = false; + + ESPEasySerialPort _port; }; #endif // ifdef USES_P020 From 296873795b7b3eb3f57b3c370f8893a2187858fc Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 24 Jun 2023 15:34:04 +0200 Subject: [PATCH 35/41] [P020] Update documentation for new options --- docs/source/Plugin/P020.rst | 24 +++++++++++++++++++++++ docs/source/Plugin/P020_EventOptions.png | Bin 0 -> 30843 bytes 2 files changed, 24 insertions(+) create mode 100644 docs/source/Plugin/P020_EventOptions.png diff --git a/docs/source/Plugin/P020.rst b/docs/source/Plugin/P020.rst index 0ed2b5df5..0373e404d 100644 --- a/docs/source/Plugin/P020.rst +++ b/docs/source/Plugin/P020.rst @@ -71,6 +71,30 @@ Device Settings * **P1 #data event with message**: When enabled, the *P1 WiFi Gateway* Event Processing option will include the received message. **WARNING** This may easily cause memory overflow exceptions, especially when running on ESP8266 or other low-memory situations! +When selecting the **Event processing** options *Generic* or *RFLink*, after submitting the page will show extra options for the events generated: + +.. image:: P020_EventOptions.png + +* **Use Serial Port as eventname**: Instead of the default ``!Serial#`` event, the name of the configured serial port will be used: (**Only** available for *Generic* Event processing) + +.. spacer + +* *(Unchecked)* -> ``Serial`` +* *HW Serial0* -> ``serial0`` +* *HW Serial0 swap* -> ``serial0`` +* *HW Serial1* -> ``serial1`` +* *HW Serial2* -> ``serial2`` +* *SW Serial* -> ``serialsw`` +* *I2C Serial* -> ``seriali2c`` +* *USB HWCDC* -> ``serialhwcdc`` +* *USB CDC* -> ``serialcdc`` + +.. spacer + +* **Append Task Number to eventname**: Will append the task number to the event name, f.e. ``Serial8`` or ``RFLink12`` when task 8 or 12 is in use for this plugin. Can be combined with **Use Serial Port as eventname** if that is option is shown, resulting f.e. in ``serial0swap6`` etc. (Only available for *Generic* and *RFLink* Event processing) + +.. spacer + * **Replace spaces in event by**: Here a single character can be selected to replace all spaces during receiving the data. * **Replace newlines in event by**: Here a single character can be selected to replace all newlines during receiving the data. When enabled, all linefeeds are replaced, and all carriage returns (if any) are discarded. diff --git a/docs/source/Plugin/P020_EventOptions.png b/docs/source/Plugin/P020_EventOptions.png new file mode 100644 index 0000000000000000000000000000000000000000..5c5bb82a143110a50cd6bb7b972b10772677ab1a GIT binary patch literal 30843 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 literal 0 HcmV?d00001 From eb12171385ba0aff4a7a9241d2a05466250372f9 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 12 Aug 2023 22:16:35 +0200 Subject: [PATCH 36/41] [P020] Fix receiving P1 data --- src/_P020_Ser2Net.ino | 1 + src/src/PluginStructs/P020_data_struct.cpp | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/_P020_Ser2Net.ino b/src/_P020_Ser2Net.ino index 131e8d0b4..2f50f8cf5 100644 --- a/src/_P020_Ser2Net.ino +++ b/src/_P020_Ser2Net.ino @@ -8,6 +8,7 @@ /************ * Changelog: + * 2023-08-12 tonhuisman: Strip off occasional 8th bit from received data, to avoid unexpected failures on P1 data reception * 2023-06-24 tonhuisman: Fix initialization with non-GPIO serial ports like CDC/HW-CDC * Add option: append the task number to the event-name (Generic and RFLink event options) * 2023-06-23 tonhuisman: Add option: use Serial Port name (serialxxx -> xxx= 0/1/2/0swap/i2c/sw/hwcdc/cdc) as event-name for Generic events diff --git a/src/src/PluginStructs/P020_data_struct.cpp b/src/src/PluginStructs/P020_data_struct.cpp index a1fdc2786..6066183b5 100644 --- a/src/src/PluginStructs/P020_data_struct.cpp +++ b/src/src/PluginStructs/P020_data_struct.cpp @@ -323,7 +323,9 @@ void P020_Task::rulesEngine(const String& message) { if (_P1EventData) { eventString += '='; - eventString += message; // Include entire message, may cause memory overflow! + eventString += message; // Include entire message, may cause memory overflow! + eventString.replace(F("\n"), F(",")); // Make it a single line, comma-separated, as much as possible + eventString.replace(F("\r"), F("")); // We don't need no st*n carriage returns :) } break; } // switch @@ -467,6 +469,7 @@ bool P020_Task::handleP1Char(char ch) { # endif // ifndef BUILD_NO_DEBUG state = ParserState::WAITING; // reset } + ch &= 0x7F; // Strip off occasional 8th bit for now bool done = false; bool invalid = false; @@ -500,6 +503,7 @@ bool P020_Task::handleP1Char(char ch) { state = ParserState::WAITING; // reset return handleP1Char(ch); } else { + addLog(LOG_LEVEL_ERROR, strformat(F("P1 : Receiving unknown: %d,'%c'"), ch, ch)); invalid = true; } break; From 2e498bed2d05a17f562d90fae03b247922c7b48e Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Thu, 17 Aug 2023 11:53:12 +0200 Subject: [PATCH 37/41] [P020] Fix merge conflict --- src/_P020_Ser2Net.ino | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/_P020_Ser2Net.ino b/src/_P020_Ser2Net.ino index ebbdf4499..cd3853d5b 100644 --- a/src/_P020_Ser2Net.ino +++ b/src/_P020_Ser2Net.ino @@ -480,21 +480,22 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) if (nullptr != task) { String command = parseString(string, 1); - if (equals(command, F("serialsend"))) { - task->ser2netSerial->write(string.substring(11).c_str()); - task->ser2netSerial->flush(); - success = true; - } else if (equals(command, F("serialsendmix"))) { - std::vector argument = parseHexTextData(string); - task->ser2netSerial->write(&argument[0], argument.size()); - task->ser2netSerial->flush(); - success = true; - } else if ((equals(command, F("ser2netclientsend"))) && (task->hasClientConnected())) { - task->ser2netClient.print(string.substring(18)); - task->ser2netClient.flush(); - success = true; + if (equals(command, F("serialsend"))) { + task->ser2netSerial->write(string.substring(11).c_str()); + task->ser2netSerial->flush(); + success = true; + } else if (equals(command, F("serialsendmix"))) { + std::vector argument = parseHexTextData(string); + task->ser2netSerial->write(&argument[0], argument.size()); + task->ser2netSerial->flush(); + success = true; + } else if ((equals(command, F("ser2netclientsend"))) && (task->hasClientConnected())) { + task->ser2netClient.print(string.substring(18)); + task->ser2netClient.flush(); + success = true; + } + break; } - break; } } return success; From 9d0dd105fde8d299ccb04fc1f2fc24c74d65d31d Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Thu, 17 Aug 2023 16:09:29 +0200 Subject: [PATCH 38/41] [P020] Allow some extra time-out while receiving P1 data, add missing CR/LF --- src/_P020_Ser2Net.ino | 2 + src/src/PluginStructs/P020_data_struct.cpp | 53 ++++++++++++++-------- src/src/PluginStructs/P020_data_struct.h | 2 +- 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/src/_P020_Ser2Net.ino b/src/_P020_Ser2Net.ino index cd3853d5b..75a51859a 100644 --- a/src/_P020_Ser2Net.ino +++ b/src/_P020_Ser2Net.ino @@ -8,6 +8,8 @@ /************ * Changelog: + * 2023-08-17 tonhuisman: P1 data: Allow some extra reading timeout between the data and the checksum, as some meters need more time to + * calculate the CRC. Add CR/LF before sending P1 data. * 2023-08-12 tonhuisman: Strip off occasional 8th bit from received data, to avoid unexpected failures on P1 data reception * 2023-06-24 tonhuisman: Fix initialization with non-GPIO serial ports like CDC/HW-CDC * Add option: append the task number to the event-name (Generic and RFLink event options) diff --git a/src/src/PluginStructs/P020_data_struct.cpp b/src/src/PluginStructs/P020_data_struct.cpp index 6066183b5..43801bea6 100644 --- a/src/src/PluginStructs/P020_data_struct.cpp +++ b/src/src/PluginStructs/P020_data_struct.cpp @@ -188,9 +188,11 @@ void P020_Task::handleClientIn(struct EventStruct *event) { void P020_Task::handleSerialIn(struct EventStruct *event) { if (nullptr == ser2netSerial) { return; } - int RXWait = P020_RX_WAIT; - int timeOut = RXWait; - bool done = false; + int RXWait = P020_RX_WAIT; + int timeOut = RXWait; + int maxExtend = 5; + bool done = false; + char ch; do { if (ser2netSerial->available()) { @@ -206,10 +208,12 @@ void P020_Task::handleSerialIn(struct EventStruct *event) { digitalWrite(_ledPin, _ledInverted ? 0 : 1); } + ch = static_cast(ser2netSerial->read()); + if (serial_processing == P020_Events::P1WiFiGateway) { - done = handleP1Char(static_cast(ser2netSerial->read())); + done = handleP1Char(ch); } else { - addChar(static_cast(ser2netSerial->read())); + addChar(ch); } if (_ledEnabled) { @@ -217,10 +221,22 @@ void P020_Task::handleSerialIn(struct EventStruct *event) { } } - if (done) { break; } + if (done) { + break; + } timeOut = RXWait; // if serial received, reset timeout counter } else { - if (timeOut <= 0) { break; } + if (timeOut <= 0) { + if ((RXWait > 0) && (serial_processing == P020_Events::P1WiFiGateway) && + ((_state == ParserState::READING) || + (_state == ParserState::CHECKSUM)) && + (maxExtend > 0)) { + timeOut = RXWait; + maxExtend--; + } else { + break; + } + } delay(1); --timeOut; } @@ -228,10 +244,9 @@ void P020_Task::handleSerialIn(struct EventStruct *event) { if (serial_buffer.length() > 0) { if (ser2netClient.connected()) { // Only send out if a client is connected - // FIXME tonhuisman: Disable extra check for now as it reportedly doesn't work as intended - // if ((serial_processing == P020_Events::P1WiFiGateway) && !serial_buffer.endsWith(F("\r\n"))) { - // serial_buffer += F("\r\n"); - // } + if ((serial_processing == P020_Events::P1WiFiGateway) && !serial_buffer.endsWith(F("\r\n"))) { + serial_buffer += F("\r\n"); + } ser2netClient.print(serial_buffer); } @@ -467,20 +482,20 @@ bool P020_Task::handleP1Char(char ch) { # ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_DEBUG, F("P1 : Error: Buffer overflow, discarded input.")); # endif // ifndef BUILD_NO_DEBUG - state = ParserState::WAITING; // reset + _state = ParserState::WAITING; // reset } - ch &= 0x7F; // Strip off occasional 8th bit for now + ch &= 0x7F; // Strip off occasional 8th bit for now bool done = false; bool invalid = false; - switch (state) { + switch (_state) { case ParserState::WAITING: if (ch == P020_DATAGRAM_START_CHAR) { clearBuffer(); addChar(ch); - state = ParserState::READING; + _state = ParserState::READING; } // else ignore data break; case ParserState::READING: @@ -492,7 +507,7 @@ bool P020_Task::handleP1Char(char ch) { if (_CRCcheck) { checkI = 0; - state = ParserState::CHECKSUM; + _state = ParserState::CHECKSUM; } else { done = true; } @@ -500,7 +515,7 @@ bool P020_Task::handleP1Char(char ch) { # ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_DEBUG, F("P1 : Error: Start detected, discarded input.")); # endif // ifndef BUILD_NO_DEBUG - state = ParserState::WAITING; // reset + _state = ParserState::WAITING; // reset return handleP1Char(ch); } else { addLog(LOG_LEVEL_ERROR, strformat(F("P1 : Receiving unknown: %d,'%c'"), ch, ch)); @@ -533,7 +548,7 @@ bool P020_Task::handleP1Char(char ch) { serialPrint(String(ch)); serialPrintln("<"); # endif // if PLUGIN_020_DEBUG - state = ParserState::WAITING; // reset + _state = ParserState::WAITING; // reset } if (done) { @@ -553,7 +568,7 @@ bool P020_Task::handleP1Char(char ch) { addLog(LOG_LEVEL_DEBUG, F("P1 : Error: Invalid datagram, dropped data")); # endif // ifndef BUILD_NO_DEBUG } - state = ParserState::WAITING; // prepare for next one + _state = ParserState::WAITING; // prepare for next one } return done; diff --git a/src/src/PluginStructs/P020_data_struct.h b/src/src/PluginStructs/P020_data_struct.h index 995b74f32..8714ca43c 100644 --- a/src/src/PluginStructs/P020_data_struct.h +++ b/src/src/PluginStructs/P020_data_struct.h @@ -152,7 +152,7 @@ struct P020_Task : public PluginTaskData_base { bool _CRCcheck = false; bool _P1EventData = false; size_t _maxDataGramSize = P020_DATAGRAM_MAX_SIZE; - ParserState state = ParserState::WAITING; + ParserState _state = ParserState::WAITING; char _space = 0; char _newline = 0; bool _serialId = false; From 5fb0975fa219fc4dc0aa6aa2c5f836fb267ba5d2 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 26 Aug 2023 21:23:49 +0200 Subject: [PATCH 39/41] [P044/P020] Improved defaults for P1 WiFigateway --- src/_P020_Ser2Net.ino | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/_P020_Ser2Net.ino b/src/_P020_Ser2Net.ino index 75a51859a..c035e231d 100644 --- a/src/_P020_Ser2Net.ino +++ b/src/_P020_Ser2Net.ino @@ -8,6 +8,7 @@ /************ * Changelog: + * 2023-08-26 tonhuisman: P044 mode: Set RX time-out default to 50 msec for better receive pace of P1 data * 2023-08-17 tonhuisman: P1 data: Allow some extra reading timeout between the data and the checksum, as some meters need more time to * calculate the CRC. Add CR/LF before sending P1 data. * 2023-08-12 tonhuisman: Strip off occasional 8th bit from received data, to avoid unexpected failures on P1 data reception @@ -121,7 +122,7 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) P020_RESET_TARGET_PIN = P020_DEFAULT_RESET_TARGET_PIN; P020_SERIAL_PROCESSING = static_cast(P020_Events::P1WiFiGateway); // Enable P1 WiFi Gateway processing (only) P020_LED_PIN = P020_STATUS_LED; - P020_RX_WAIT = 0; + P020_RX_WAIT = 50; // 50 msec for proper P1 packet receive mode P020_REPLACE_SPACE = 0; // Force empty P020_REPLACE_NEWLINE = 0; P020_FLAGS = 0u; // Reset From 40762e4fabf4f2276e9dd60aacbda27bdcaca987 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 2 Dec 2023 13:23:43 +0100 Subject: [PATCH 40/41] [P020][P044] Minor improvements, update with latest mega changes --- src/_P020_Ser2Net.ino | 25 +++++++++++----------- src/src/PluginStructs/P020_data_struct.cpp | 4 ++-- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/_P020_Ser2Net.ino b/src/_P020_Ser2Net.ino index c035e231d..6328605fe 100644 --- a/src/_P020_Ser2Net.ino +++ b/src/_P020_Ser2Net.ino @@ -203,8 +203,9 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) static_cast(P020_Events::RFLink), static_cast(P020_Events::P1WiFiGateway), }; + constexpr int optionCount = NR_ELEMENTS(optionValues); addFormSelector(F("Event processing"), F("pevents"), - sizeof(optionValues) / sizeof(int), options, optionValues, + optionCount, options, optionValues, P020_SERIAL_PROCESSING); } addFormCheckBox(F("P1 #data event with message"), F("pp1event"), P020_GET_P1_EVENT_DATA); @@ -245,7 +246,7 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) } { addFormNumericBox(F("RX Receive Timeout (mSec)"), F("prxwait"), P020_RX_WAIT, 0, 200); - addFormPinSelect(PinSelectPurpose::Generic, F("Reset target after init"), F("presetpin"), P020_RESET_TARGET_PIN); + addFormPinSelect(PinSelectPurpose::Generic_output, F("Reset target after init"), F("presetpin"), P020_RESET_TARGET_PIN); if (!P020_Emulate_P044) { addFormNumericBox(F("RX buffer size (bytes)"), F("prx_buffer"), P020_RX_BUFFER, 256, 2048); @@ -258,7 +259,7 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) addFormSubHeader(F("Led")); addFormCheckBox(F("Led enabled"), F("pled"), P020_GET_LED_ENABLED); - addFormPinSelect(PinSelectPurpose::Generic, F("Led pin"), F("pledpin"), P020_LED_PIN); + addFormPinSelect(PinSelectPurpose::Generic_output, F("Led pin"), F("pledpin"), P020_LED_PIN); addFormCheckBox(F("Led inverted"), F("pledinv"), P020_GET_LED_INVERTED == 1); } @@ -360,16 +361,14 @@ boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) # ifndef LIMIT_BUILD_SIZE if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - log.reserve(100); - log = concat(F("Ser2Net: TaskIndex="), static_cast(event->TaskIndex) + 1); - log += concat(F(" port="), static_cast(CONFIG_PORT)); - log += concat(F(" rxPin="), static_cast(rxPin)); - log += concat(F(" txPin="), static_cast(txPin)); - log += concat(F(" BAUDRATE="), static_cast(P020_GET_BAUDRATE)); - log += concat(F(" SERVER_PORT="), static_cast(P020_GET_SERVER_PORT)); - log += concat(F(" SERIAL_PROCESSING="), static_cast(P020_SERIAL_PROCESSING)); - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, strformat(F("Ser2Net: TaskIndex=%d port=%d rxPin=%d txPin=%d BAUDRATE=%d SERVER_PORT=%d SERIAL_PROCESSING=%d"), + event->TaskIndex + 1, + CONFIG_PORT, + rxPin, + txPin, + P020_GET_BAUDRATE, + P020_GET_SERVER_PORT, + P020_SERIAL_PROCESSING)); } # endif // ifndef LIMIT_BUILD_SIZE diff --git a/src/src/PluginStructs/P020_data_struct.cpp b/src/src/PluginStructs/P020_data_struct.cpp index 43801bea6..73cd25c7e 100644 --- a/src/src/PluginStructs/P020_data_struct.cpp +++ b/src/src/PluginStructs/P020_data_struct.cpp @@ -58,9 +58,9 @@ void P020_Task::startServer(uint16_t portnumber) { ser2netServer->begin(); if (serverActive(ser2netServer)) { - addLog(LOG_LEVEL_INFO, concat(F("Ser2Net: WiFi server started at port "), portnumber)); + addLog(LOG_LEVEL_INFO, strformat(F("Ser2Net: WiFi server started at port %d"), portnumber)); } else { - addLog(LOG_LEVEL_ERROR, concat(F("Ser2Net: WiFi server start failed at port "), portnumber) + F(", retrying...")); + addLog(LOG_LEVEL_ERROR, strformat(F("Ser2Net: WiFi server start FAILED at port %d, retrying..."), portnumber)); } } } From d5606e3800f2e430b7baaf54eefae9cf851e3fbd Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Thu, 28 Dec 2023 15:12:17 +0100 Subject: [PATCH 41/41] [P020] Replace WiFiServer->available() by accept() (for IDF >= 5) --- src/src/PluginStructs/P020_data_struct.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/src/PluginStructs/P020_data_struct.cpp b/src/src/PluginStructs/P020_data_struct.cpp index 73cd25c7e..7edc227a9 100644 --- a/src/src/PluginStructs/P020_data_struct.cpp +++ b/src/src/PluginStructs/P020_data_struct.cpp @@ -91,7 +91,11 @@ bool P020_Task::hasClientConnected() { if ((nullptr != ser2netServer) && ser2netServer->hasClient()) { if (ser2netClient) { ser2netClient.stop(); } + #if ESP_IDF_VERSION_MAJOR >= 5 + ser2netClient = ser2netServer->accept(); + #else ser2netClient = ser2netServer->available(); + #endif # ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS