diff --git a/_P200_VEDirect.ino b/_P200_VEDirect.ino new file mode 100644 index 0000000..c7b51ec --- /dev/null +++ b/_P200_VEDirect.ino @@ -0,0 +1,152 @@ +#include "_Plugin_Helper.h" +#ifdef USES_P200 + +// ####################################################################################################### +// ####################### Plugin 200 VE.Direct ########################################################## +// ########################## by timokovanen ############################################################# +// ####################################################################################################### +// Based TD-er P087 Serial Proxy +// +// Output (string): +// - JSON (checksum validation) +// - CSV (checksum validation) +// - RAW BASE64 (checksum included, no validation) + +#include "src/PluginStructs/P200_data_struct.h" + +#include + +#define PLUGIN_200 +#define PLUGIN_ID_200 200 +#define PLUGIN_NAME_200 "Communication - VE.Direct [TESTING]" +#define PLUGIN_VALUENAME1_200 "vedirect" + +#define P200_BAUDRATE 19200 + +boolean Plugin_200(uint8_t function, struct EventStruct *event, String& string) { + boolean success = false; + + switch (function) { + case PLUGIN_DEVICE_ADD: { + Device[++deviceCount].Number = PLUGIN_ID_200; + Device[deviceCount].Type = DEVICE_TYPE_SERIAL; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_STRING; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = false; + Device[deviceCount].GlobalSyncOption = false; + Device[deviceCount].ExitTaskBeforeSave = false; + break; + } + + case PLUGIN_GET_DEVICENAME: { + string = F(PLUGIN_NAME_200); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_200)); + break; + } + + case PLUGIN_GET_DEVICEGPIONAMES: { + serialHelper_getGpioNames(event, false, true); // TX optional + break; + } + + case PLUGIN_WEBFORM_SHOW_VALUES: + { + P200_data_struct *P200_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P200_data) && P200_data->isInitialized()) { + uint32_t success, error, length_last; + P200_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_WEBFORM_SHOW_CONFIG: + { + string += serialHelper_getSerialTypeLabel(event); + success = true; + break; + } + + case PLUGIN_WEBFORM_LOAD: { + addFormSubHeader(F("Output")); + + const __FlashStringHelper * options[3] = { F("JSON"), F("CSV"), F("RAW (BASE64 encoded, no checksum validation)") }; + int optionValues[3] = { P200_OUTPUT_JSON, P200_OUTPUT_CSV, P200_OUTPUT_RAW }; + addFormSelector(F("Format"), F("p200_output"), 3, options, optionValues, PCONFIG(0)); + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: { + PCONFIG(0) = getFormItemInt(F("p200_output")); + 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(CONFIG_PORT); + initPluginTaskData(event->TaskIndex, new (std::nothrow) P200_data_struct()); + P200_data_struct *P200_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr == P200_data) { + return success; + } + + P200_data->output_type = PCONFIG(0); + + if (P200_data->init(port, serial_rx, serial_tx, P200_BAUDRATE)) { + success = true; + serialHelper_log_GpioDescription(port, serial_rx, serial_tx); + } else { + clearPluginTaskData(event->TaskIndex); + } + break; + } + + case PLUGIN_FIFTY_PER_SECOND: { + P200_data_struct *P200_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P200_data) && P200_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: { + P200_data_struct *P200_data = + static_cast(getPluginTaskData(event->TaskIndex)); + if ((nullptr != P200_data) && P200_data->getSentence(event->String2)) { + success = true; + } + break; + } + + } + return success; +} + +#endif // USES_P200 diff --git a/src/PluginStructs/P200_data_struct.cpp b/src/PluginStructs/P200_data_struct.cpp new file mode 100644 index 0000000..af2d124 --- /dev/null +++ b/src/PluginStructs/P200_data_struct.cpp @@ -0,0 +1,185 @@ +#include "../PluginStructs/P200_data_struct.h" + + +// Needed also here for PlatformIO's library finder as the .h file +// is in a directory which is excluded in the src_filter +#include +#include + +#ifdef USES_P200 + + +P200_data_struct::P200_data_struct() : easySerial(nullptr) {} + +P200_data_struct::~P200_data_struct() { + reset(); +} + +void P200_data_struct::reset() { + if (easySerial != nullptr) { + delete easySerial; + easySerial = nullptr; + } +} + +bool P200_data_struct::init(ESPEasySerialPort port, const int16_t serial_rx, const int16_t serial_tx, unsigned long baudrate) { + if ((serial_rx < 0) && (serial_tx < 0)) { + return false; + } + reset(); + easySerial = new (std::nothrow) ESPeasySerial(port, serial_rx, serial_tx); + + if (isInitialized()) { + easySerial->begin(baudrate); + return true; + } + return false; +} + +bool P200_data_struct::isInitialized() const { + return easySerial != nullptr; +} + +bool P200_data_struct::loop() { + if (!isInitialized()) { + return false; + } + bool fullSentenceReceived = false; + + if (easySerial != nullptr) { + int available = easySerial->available(); + + while (available > 0 && !fullSentenceReceived) { + char c = easySerial->read(); + --available; + + if (available == 0) { + available = easySerial->available(); + if (available == 0) { resetSerialTimeout(); } + delay(0); + } + + switch (sentence_part.length()) { + case 0: + if (c != '\r') break; // wait for '\r' + default: + sentence_part += c; + sentence_checksum -= c; + break; + } + + if (max_length_reached()) { fullSentenceReceived = true; } + } + } + + if (serialTimeout() || fullSentenceReceived) { + const size_t length = sentence_part.length(); + bool valid = length > 0; + + fullSentenceReceived = false; + + if (valid) { + // message or full buffer + + + if (output_type == P200_OUTPUT_RAW) { + length_last_received = sentence_part.length(); + last_sentence = base64::encode(sentence_part); + fullSentenceReceived = true; + sentence_part = ""; + ++sentences_received; + } else { + // Cheksum + sentence_checksum = sentence_checksum & 0xff; + + if (sentence_checksum != 0) { + sentence_checksum = 0; + sentence_part = ""; + ++sentences_received_error; + } else { + // JSON & CSV + String field; + uint16_t field_start = 2; + uint16_t field_end = sentence_part.indexOf('\r', field_start); + uint16_t field_separator = 0; + if (output_type == P200_OUTPUT_JSON) { last_sentence = '{'; } + + while (field_end <= length) { + // this will drop last field (checksum) + if (last_sentence.length() >= 2) { last_sentence += ','; } + field = sentence_part.substring(field_start, field_end); // + + field_separator = field.indexOf('\t'); + + switch (output_type) { + case P200_OUTPUT_JSON: + last_sentence += to_json_object_value(field.substring(0, field_separator), field.substring(field_separator + 1), false); + break; + case P200_OUTPUT_CSV: + last_sentence += field.substring(0, field_separator) + ':' + field.substring(field_separator + 1); + break; + default: + break; + } + + field_start = field_end + 2; + field_end = sentence_part.indexOf('\r', field_start); + delay(0); + } + + if (output_type == P200_OUTPUT_JSON) { last_sentence += '}'; } + length_last_received = sentence_part.length(); + fullSentenceReceived = true; + sentence_part = ""; + ++sentences_received; + } + } + } + } + + return fullSentenceReceived; +} + +bool P200_data_struct::getSentence(String& string) { + string = last_sentence; + if (string.isEmpty()) { + return false; + } + last_sentence = ""; + return true; +} + +void P200_data_struct::getSentencesReceived(uint32_t& succes, uint32_t& error, uint32_t& length_last) const { + succes = sentences_received; + error = sentences_received_error; + length_last = length_last_received; +} + +void P200_data_struct::setMaxLength(uint16_t maxlenght) { + max_length = maxlenght; +} + +void P200_data_struct::resetSerialTimeout() { + serial_timeout = millis() + P200_SERIAL_TIMEOUT; +} + +bool P200_data_struct::serialTimeout() const { + if (timeOutReached(serial_timeout)) { + return true; + } + return false; +} + +bool P200_data_struct::max_length_reached() const { + if (max_length == 0) { return false; } + return sentence_part.length() >= max_length; +} + +bool P200_data_struct::isNumber(const String& str) { + for (char const &c : str) { + if (std::isdigit(c) == 0) return false; + } + return true; +} + +#endif // USES_P200 diff --git a/src/PluginStructs/P200_data_struct.h b/src/PluginStructs/P200_data_struct.h new file mode 100644 index 0000000..5c6a16c --- /dev/null +++ b/src/PluginStructs/P200_data_struct.h @@ -0,0 +1,70 @@ +#ifndef PLUGINSTRUCTS_P200_DATA_STRUCT_H +#define PLUGINSTRUCTS_P200_DATA_STRUCT_H + +#include "../../_Plugin_Helper.h" +#include "../Helpers/StringConverter.h" + +#ifdef USES_P200 + +#include +#include + +# define P200_SERIAL_TIMEOUT 2 + +#define P200_OUTPUT_JSON 0 +#define P200_OUTPUT_CSV 1 +#define P200_OUTPUT_RAW 2 + +struct P200_data_struct : public PluginTaskData_base { +public: + + P200_data_struct(); + + ~P200_data_struct(); + + void reset(); + + bool init(ESPEasySerialPort port, + const int16_t serial_rx, + const int16_t serial_tx, + unsigned long baudrate); + + bool isInitialized() const; + + bool loop(); + + bool getSentence(String& string); + + void getSentencesReceived(uint32_t& succes, + uint32_t& error, + uint32_t& length_last) const; + + void setMaxLength(uint16_t maxlenght); + + void resetSerialTimeout(); + + uint8_t output_type = 0; + +private: + + bool max_length_reached() const; + + bool serialTimeout() const; + + bool isNumber(const String& str); + + ESPeasySerial *easySerial = nullptr; + String sentence_part; + String last_sentence; + uint16_t max_length = 550; + uint32_t sentences_received = 0; + uint32_t sentences_received_error = 0; + uint32_t length_last_received = 0; + unsigned long serial_timeout = 0; + int8_t sentence_checksum = 0; + +}; + +#endif // USES_P200 + +#endif // PLUGINSTRUCTS_P200_DATA_STRUCT_H