Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

P200_VE.Direct plugin #181

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions _P200_VEDirect.ino
Original file line number Diff line number Diff line change
@@ -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 <Regexp.h>

#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<P200_data_struct *>(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<ESPEasySerialPort>(CONFIG_PORT);
initPluginTaskData(event->TaskIndex, new (std::nothrow) P200_data_struct());
P200_data_struct *P200_data =
static_cast<P200_data_struct *>(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<P200_data_struct *>(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<P200_data_struct *>(getPluginTaskData(event->TaskIndex));
if ((nullptr != P200_data) && P200_data->getSentence(event->String2)) {
success = true;
}
break;
}

}
return success;
}

#endif // USES_P200
185 changes: 185 additions & 0 deletions src/PluginStructs/P200_data_struct.cpp
Original file line number Diff line number Diff line change
@@ -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 <ESPeasySerial.h>
#include <base64.h>

#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-Labe><Tab><Field-Value>

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
Loading