mirror of
https://github.com/letscontrolit/ESPEasy.git
synced 2026-07-28 04:07:47 +00:00
456 lines
15 KiB
Arduino
456 lines
15 KiB
Arduino
#include "_Plugin_Helper.h"
|
|
#ifdef USES_P087
|
|
|
|
// #######################################################################################################
|
|
// #################### Plugin 087 Serial Proxy ##########################################################
|
|
// #######################################################################################################
|
|
//
|
|
// Interact with a device connected to serial
|
|
// Allows to redirect data to a controller
|
|
//
|
|
|
|
/**
|
|
* Changelog:
|
|
* 2025-12-22 tonhuisman: Add support for receiving binary data, fixed length receive and sending events with hex data
|
|
* Moved most #define variables to P087_data_struct.h
|
|
* 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery (not supported for Serial Proxy)
|
|
* 2024-02-27 tonhuisman: Always process the regular expression like 'Global Match' to enable retrieving the available values
|
|
* 2024-02-26 tonhuisman: Apply log-string and other code optimizations
|
|
* 2024-02-25 tonhuisman: Add command serialproxy_test,<testdata> to test as if serial data was received
|
|
* Add Get Config Value support for retrieving the last regex-parsed data:
|
|
* - By group: [<taskname>#group,<groupnr>] (groupnr is 0-base!)
|
|
* - By name: [<taskname>#next,<data>] if the <data> is found, the next group-data is returned
|
|
* 2023-03-25 tonhuisman: Change serialproxy_writemix to handle 0x00 also, by implementing parseHexTextData()
|
|
* 2023-03-22 tonhuisman: Add command serialproxy_writemix to handle mixed hex characters and text to send
|
|
* using parseHexTextString()
|
|
* Format source using Uncrustify
|
|
* 2022-07-08 tonhuisman: Allow baudrate lowest value to 300 (from 2400)
|
|
* Don't trim off pre/post white-space from string to send
|
|
* 2022-07-07 tonhuisman: Add selection for serial protocol configuration (databits, parity, nr. of stopbits)
|
|
* 2022-07 First recorded changelog
|
|
**/
|
|
|
|
|
|
# include "src/PluginStructs/P087_data_struct.h"
|
|
|
|
# include <Regexp.h>
|
|
|
|
# define PLUGIN_087
|
|
# define PLUGIN_ID_087 87
|
|
# define PLUGIN_NAME_087 "Communication - Serial Proxy"
|
|
|
|
|
|
// Plugin settings:
|
|
// Validate:
|
|
// - [0..9]
|
|
// - "+", "-", "."
|
|
// - [A..Z]
|
|
// - [a..z]
|
|
// - ASCII 32 - 217
|
|
// Sentence start: char
|
|
// Sentence end: CR/CRLF/LF/char
|
|
// Max length sentence: 1k max
|
|
// Interpret as:
|
|
// - Float
|
|
// - int
|
|
// - String
|
|
// Init string (incl parsing CRLF like characters)
|
|
// Timeout between sentences.
|
|
|
|
|
|
boolean Plugin_087(uint8_t function, struct EventStruct *event, String& string) {
|
|
boolean success = false;
|
|
|
|
switch (function) {
|
|
case PLUGIN_DEVICE_ADD: {
|
|
auto& dev = Device[++deviceCount];
|
|
dev.Number = PLUGIN_ID_087;
|
|
dev.Type = DEVICE_TYPE_SERIAL;
|
|
dev.VType = Sensor_VType::SENSOR_TYPE_STRING;
|
|
dev.ValueCount = 1;
|
|
dev.SendDataOption = true;
|
|
dev.TimerOption = true;
|
|
|
|
// FIXME TD-er: Not sure if access to any existing task data is needed when saving
|
|
dev.ExitTaskBeforeSave = false;
|
|
break;
|
|
}
|
|
|
|
case PLUGIN_GET_DEVICENAME: {
|
|
string = F(PLUGIN_NAME_087);
|
|
break;
|
|
}
|
|
|
|
case PLUGIN_GET_DEVICEVALUENAMES: {
|
|
for (uint8_t i = 0; i < VARS_PER_TASK; ++i) {
|
|
if (i < P087_NR_OUTPUT_VALUES) {
|
|
const uint8_t pconfigIndex = i + P087_QUERY1_CONFIG_POS;
|
|
uint8_t choice = PCONFIG(pconfigIndex);
|
|
ExtraTaskSettings.setTaskDeviceValueName(i, Plugin_087_valuename(choice, false));
|
|
} else {
|
|
ExtraTaskSettings.clearTaskDeviceValueName(i);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
# if FEATURE_MQTT_DISCOVER
|
|
case PLUGIN_GET_DISCOVERY_VTYPES:
|
|
{
|
|
event->Par1 = static_cast<int>(Sensor_VType::SENSOR_TYPE_NONE); // Not yet supported
|
|
success = true;
|
|
break;
|
|
}
|
|
# endif // if FEATURE_MQTT_DISCOVER
|
|
|
|
case PLUGIN_GET_DEVICEGPIONAMES: {
|
|
serialHelper_getGpioNames(event, false, true); // TX optional
|
|
break;
|
|
}
|
|
|
|
case PLUGIN_WEBFORM_SHOW_VALUES:
|
|
{
|
|
P087_data_struct *P087_data =
|
|
static_cast<P087_data_struct *>(getPluginTaskData(event->TaskIndex));
|
|
|
|
if ((nullptr != P087_data) && P087_data->isInitialized()) {
|
|
uint32_t success, error, length_last;
|
|
P087_data->getSentencesReceived(success, error, length_last);
|
|
uint8_t varNr = VARS_PER_TASK;
|
|
pluginWebformShowValue(event->TaskIndex, varNr++, F("Success"), String(success));
|
|
pluginWebformShowValue(event->TaskIndex, varNr++, F("Error"), String(error));
|
|
pluginWebformShowValue(event->TaskIndex, varNr++, F("Length Last"), String(length_last), true);
|
|
|
|
// success = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case PLUGIN_SET_DEFAULTS:
|
|
{
|
|
P087_BAUDRATE = P087_DEFAULT_BAUDRATE;
|
|
|
|
success = true;
|
|
break;
|
|
}
|
|
|
|
case PLUGIN_WEBFORM_SHOW_CONFIG:
|
|
{
|
|
string += serialHelper_getSerialTypeLabel(event);
|
|
success = true;
|
|
break;
|
|
}
|
|
|
|
case PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS:
|
|
{
|
|
addFormNumericBox(F("Baudrate"), P087_BAUDRATE_LABEL, P087_BAUDRATE, 300, 115200);
|
|
addUnit(F("baud"));
|
|
const uint8_t serialConfChoice = serialHelper_convertOldSerialConfig(P087_SERIAL_CONFIG);
|
|
serialHelper_serialconfig_webformLoad(event, serialConfChoice);
|
|
break;
|
|
}
|
|
|
|
case PLUGIN_WEBFORM_LOAD: {
|
|
addFormSubHeader(F("Filtering"));
|
|
P087_html_show_matchForms(event);
|
|
|
|
addFormSubHeader(F("Statistics"));
|
|
P087_html_show_stats(event);
|
|
|
|
# ifndef LIMIT_BUILD_SIZE
|
|
addFormSubHeader(F("Data options"));
|
|
addFormCheckBox(F("Receive binary data"), P087_READ_BIN_LABEL, P087_CONFIG_GET_READ_BIN);
|
|
addFormNumericBox(F("Fixed length input data"), P087_FIXED_LENGTH_LABEL, P087_CONFIG_GET_FIXED_LENGTH, 0, 255);
|
|
addUnit(F("0 = off, max. 255"));
|
|
addFormCheckBox(F("Event with hex. data (no prefix)"), P087_EVENT_HEX_LABEL, P087_CONFIG_GET_EVENT_HEX);
|
|
# endif // ifndef LIMIT_BUILD_SIZE
|
|
|
|
success = true;
|
|
break;
|
|
}
|
|
|
|
case PLUGIN_WEBFORM_SAVE: {
|
|
P087_BAUDRATE = getFormItemInt(P087_BAUDRATE_LABEL);
|
|
P087_SERIAL_CONFIG = serialHelper_serialconfig_webformSave();
|
|
# ifndef LIMIT_BUILD_SIZE
|
|
P087_CONFIG_SET_READ_BIN(isFormItemChecked(P087_READ_BIN_LABEL));
|
|
P087_CONFIG_SET_FIXED_LENGTH(getFormItemInt(P087_FIXED_LENGTH_LABEL));
|
|
P087_CONFIG_SET_EVENT_HEX(isFormItemChecked(P087_EVENT_HEX_LABEL));
|
|
# endif // ifndef LIMIT_BUILD_SIZE
|
|
|
|
P087_data_struct *P087_data =
|
|
static_cast<P087_data_struct *>(getPluginTaskData(event->TaskIndex));
|
|
|
|
if (nullptr != P087_data) {
|
|
for (uint8_t varNr = 0; varNr < P87_Nlines; ++varNr)
|
|
{
|
|
P087_data->setLine(varNr, webArg(getPluginCustomArgName(varNr)));
|
|
}
|
|
|
|
addHtmlError(SaveCustomTaskSettings(event->TaskIndex, P087_data->_lines, P87_Nlines, 0));
|
|
success = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PLUGIN_INIT: {
|
|
const int16_t serial_rx = CONFIG_PIN1;
|
|
const int16_t serial_tx = CONFIG_PIN2;
|
|
const ESPEasySerialPort port = static_cast<ESPEasySerialPort>(CONFIG_PORT);
|
|
initPluginTaskData(event->TaskIndex, new (std::nothrow) P087_data_struct());
|
|
P087_data_struct *P087_data =
|
|
static_cast<P087_data_struct *>(getPluginTaskData(event->TaskIndex));
|
|
|
|
if (nullptr == P087_data) {
|
|
return success;
|
|
}
|
|
|
|
if (P087_data->init(port, serial_rx, serial_tx, P087_BAUDRATE, static_cast<uint8_t>(P087_SERIAL_CONFIG))) {
|
|
LoadCustomTaskSettings(event->TaskIndex, P087_data->_lines, P87_Nlines, 0);
|
|
P087_data->post_init();
|
|
# ifndef LIMIT_BUILD_SIZE
|
|
P087_data->setHandleBinary(P087_CONFIG_GET_READ_BIN);
|
|
P087_data->setEventAsHex(P087_CONFIG_GET_EVENT_HEX);
|
|
|
|
if (P087_CONFIG_GET_FIXED_LENGTH > 0) {
|
|
P087_data->setMaxLength(P087_CONFIG_GET_FIXED_LENGTH);
|
|
}
|
|
P087_data->setFixedLength(P087_CONFIG_GET_FIXED_LENGTH);
|
|
# endif // ifndef LIMIT_BUILD_SIZE
|
|
success = true;
|
|
serialHelper_log_GpioDescription(port, serial_rx, serial_tx);
|
|
} else {
|
|
clearPluginTaskData(event->TaskIndex);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case PLUGIN_FIFTY_PER_SECOND: {
|
|
P087_data_struct *P087_data =
|
|
static_cast<P087_data_struct *>(getPluginTaskData(event->TaskIndex));
|
|
|
|
if ((nullptr != P087_data) && P087_data->loop()) {
|
|
Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 10);
|
|
delay(0); // Processing a full sentence may take a while, run some
|
|
// background tasks.
|
|
}
|
|
success = true;
|
|
break;
|
|
}
|
|
|
|
case PLUGIN_READ: {
|
|
P087_data_struct *P087_data =
|
|
static_cast<P087_data_struct *>(getPluginTaskData(event->TaskIndex));
|
|
|
|
if ((nullptr != P087_data) && P087_data->getSentence(event->String2)) {
|
|
if (Plugin_087_match_all(event->TaskIndex, event->String2)) {
|
|
// sendData(event);
|
|
# ifndef LIMIT_BUILD_SIZE
|
|
|
|
if (P087_data->isEventAsHex() && !event->String2.isEmpty()) { // Convert to hex without prefix
|
|
event->String2 = formatToHex_array(reinterpret_cast<const uint8_t *>(&event->String2[0]), event->String2.length());
|
|
}
|
|
# endif // ifndef LIMIT_BUILD_SIZE
|
|
# ifndef BUILD_NO_DEBUG
|
|
addLog(LOG_LEVEL_DEBUG, event->String2);
|
|
# endif // ifndef BUILD_NO_DEBUG
|
|
success = true;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PLUGIN_WRITE: {
|
|
P087_data_struct *P087_data =
|
|
static_cast<P087_data_struct *>(getPluginTaskData(event->TaskIndex));
|
|
|
|
if (nullptr != P087_data) {
|
|
const String cmd = parseString(string, 1);
|
|
|
|
if (equals(cmd, F("serialproxy_write"))) {
|
|
String param1 = parseStringKeepCaseNoTrim(string, 2); // Don't trim off white-space
|
|
parseSystemVariables(param1, false); // FIXME tonhuisman: Doesn't seem to be needed?
|
|
P087_data->sendString(param1);
|
|
addLogMove(LOG_LEVEL_INFO, param1); // FIXME tonhuisman: Should we always want to write to the log?
|
|
success = true;
|
|
} else
|
|
if (equals(cmd, F("serialproxy_writemix"))) {
|
|
std::vector<uint8_t> param1 = parseHexTextData(string);
|
|
|
|
if (param1.size()) {
|
|
P087_data->sendData(¶m1[0], param1.size());
|
|
}
|
|
success = true;
|
|
} else
|
|
if (equals(cmd, F("serialproxy_test"))) { // Test-parse data as if received via serial
|
|
const String param1 = parseStringKeepCaseNoTrim(string, 2);
|
|
|
|
if (!param1.isEmpty()) {
|
|
P087_data->setLastSentence(param1);
|
|
Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 10);
|
|
delay(0); // Processing a full sentence may take a while, run some background tasks.
|
|
}
|
|
success = true;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PLUGIN_GET_CONFIG_VALUE:
|
|
{
|
|
P087_data_struct *P087_data =
|
|
static_cast<P087_data_struct *>(getPluginTaskData(event->TaskIndex));
|
|
|
|
if (nullptr != P087_data) {
|
|
success = P087_data->plugin_get_config_value(event, string);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return success;
|
|
}
|
|
|
|
bool Plugin_087_match_all(taskIndex_t taskIndex, String& received)
|
|
{
|
|
P087_data_struct *P087_data =
|
|
static_cast<P087_data_struct *>(getPluginTaskData(taskIndex));
|
|
|
|
if ((nullptr == P087_data)) {
|
|
return false;
|
|
}
|
|
|
|
|
|
if (P087_data->disableFilterWindowActive()) {
|
|
addLog(LOG_LEVEL_INFO, F("Serial Proxy: Disable Filter Window active"));
|
|
return true;
|
|
}
|
|
|
|
const bool res = P087_data->matchRegexp(received);
|
|
|
|
if (P087_data->invertMatch()) {
|
|
addLog(LOG_LEVEL_INFO, F("Serial Proxy: invert filter"));
|
|
return !res;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
String Plugin_087_valuename(uint8_t value_nr, bool displayString) {
|
|
switch (value_nr) {
|
|
case P087_QUERY_VALUE: return displayString ? F("Value") : F("v");
|
|
}
|
|
return EMPTY_STRING;
|
|
}
|
|
|
|
void P087_html_show_matchForms(struct EventStruct *event) {
|
|
P087_data_struct *P087_data =
|
|
static_cast<P087_data_struct *>(getPluginTaskData(event->TaskIndex));
|
|
|
|
if ((nullptr != P087_data)) {
|
|
addFormTextBox(F("RegEx"), getPluginCustomArgName(P087_REGEX_POS), P087_data->getRegEx(), P87_Nchars);
|
|
addFormNote(F("Captures are specified using round brackets."));
|
|
|
|
addFormNumericBox(F("Nr Chars use in regex"), getPluginCustomArgName(P087_NR_CHAR_USE_POS), P087_data->getRegExpMatchLength(), 0, 1024);
|
|
addFormNote(F("0 = Use all of the received string."));
|
|
|
|
addFormNumericBox(F("Filter Off Window after send"),
|
|
getPluginCustomArgName(P087_FILTER_OFF_WINDOW_POS),
|
|
P087_data->getFilterOffWindowTime(),
|
|
0,
|
|
60000);
|
|
addUnit(F("msec"));
|
|
addFormNote(F("0 = Do not turn off filter after sending to the connected device."));
|
|
|
|
{
|
|
const __FlashStringHelper *options[P087_Match_Type_NR_ELEMENTS];
|
|
int optionValues[P087_Match_Type_NR_ELEMENTS];
|
|
|
|
for (int i = 0; i < P087_Match_Type_NR_ELEMENTS; ++i) {
|
|
P087_Match_Type matchType = static_cast<P087_Match_Type>(i);
|
|
options[i] = P087_data_struct::MatchType_toString(matchType);
|
|
optionValues[i] = matchType;
|
|
}
|
|
P087_Match_Type choice = P087_data->getMatchType();
|
|
const FormSelectorOptions selector(
|
|
P087_Match_Type_NR_ELEMENTS,
|
|
options,
|
|
optionValues);
|
|
selector.addFormSelector(
|
|
F("Match Type"),
|
|
getPluginCustomArgName(P087_MATCH_TYPE_POS),
|
|
choice);
|
|
addFormNote(F("Capture filter can only be used on Global Match"));
|
|
}
|
|
|
|
|
|
uint8_t lineNr = 0;
|
|
uint8_t capture = 0;
|
|
P087_Filter_Comp comparator = P087_Filter_Comp::Equal;
|
|
String filter;
|
|
|
|
for (uint8_t varNr = P087_FIRST_FILTER_POS; varNr < P87_Nlines; ++varNr)
|
|
{
|
|
const String id = getPluginCustomArgName(varNr);
|
|
|
|
switch (varNr % 3) {
|
|
case 0:
|
|
{
|
|
// Label + first parameter
|
|
filter = P087_data->getFilter(lineNr, capture, comparator);
|
|
++lineNr;
|
|
addRowLabel_tr_id(concat(F("Capture Filter "), lineNr), id);
|
|
|
|
addNumericBox(id, capture, -1, P87_MAX_CAPTURE_INDEX);
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
// Comparator
|
|
const __FlashStringHelper *options[2];
|
|
options[P087_Filter_Comp::Equal] = F("==");
|
|
options[P087_Filter_Comp::NotEqual] = F("!=");
|
|
const int optionValues[] = { P087_Filter_Comp::Equal, P087_Filter_Comp::NotEqual };
|
|
FormSelectorOptions selector(2, options, optionValues);
|
|
selector.clearClassName();
|
|
selector.addSelector(id, static_cast<int>(comparator));
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
// Compare with
|
|
addTextBox(id, filter, 32, F(""));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void P087_html_show_stats(struct EventStruct *event) {
|
|
P087_data_struct *P087_data =
|
|
static_cast<P087_data_struct *>(getPluginTaskData(event->TaskIndex));
|
|
|
|
if ((nullptr == P087_data) || !P087_data->isInitialized()) {
|
|
return;
|
|
}
|
|
{
|
|
addRowLabel(F("Current Sentence"));
|
|
String sentencePart;
|
|
P087_data->getSentence(sentencePart);
|
|
addHtml(sentencePart);
|
|
}
|
|
|
|
{
|
|
addRowLabel(F("Sentences (pass/fail)"));
|
|
uint32_t success, error, length_last;
|
|
P087_data->getSentencesReceived(success, error, length_last);
|
|
addHtml(strformat(F("%d/%d"), success, error));
|
|
addRowLabel(F("Length Last Sentence"));
|
|
addHtmlInt(length_last);
|
|
}
|
|
}
|
|
|
|
#endif // USES_P087
|