frame handler with string and map

This commit is contained in:
helgeerbe 2022-08-20 17:06:56 +02:00
parent 7140574c37
commit 48e5b567cb
4 changed files with 89 additions and 165 deletions

View File

@ -47,15 +47,10 @@ VeDirectFrameHandler VeDirect;
VeDirectFrameHandler::VeDirectFrameHandler() : VeDirectFrameHandler::VeDirectFrameHandler() :
//mStop(false), // don't know what Victron uses this for, not using //mStop(false), // don't know what Victron uses this for, not using
veName(), _state(IDLE),
veValue(), _checksum(0),
frameIndex(0), _name(""),
veEnd(0), _value(""),
mState(IDLE),
mChecksum(0),
idx(0),
tempName(),
tempValue(),
_pollInterval(5), _pollInterval(5),
_lastPoll(0) _lastPoll(0)
{ {
@ -74,8 +69,6 @@ void VeDirectFrameHandler::setPollInterval(unsigned long interval)
void VeDirectFrameHandler::loop() void VeDirectFrameHandler::loop()
{ {
polltime = _pollInterval;
if ((millis() - getLastUpdate()) < _pollInterval * 1000) { if ((millis() - getLastUpdate()) < _pollInterval * 1000) {
return; return;
} }
@ -87,26 +80,26 @@ void VeDirectFrameHandler::loop()
/* /*
* rxData * rxData
* This function is called by the application which passes a byte of serial data * This function is called by loop() which passes a byte of serial data
* It is unchanged from Victron's example code * Based on Victron's example code. But using String and Map instead of pointer and arrays
*/ */
void VeDirectFrameHandler::rxData(uint8_t inbyte) void VeDirectFrameHandler::rxData(uint8_t inbyte)
{ {
//if (mStop) return; //if (mStop) return;
if ( (inbyte == ':') && (mState != CHECKSUM) ) { if ( (inbyte == ':') && (_state != CHECKSUM) ) {
mState = RECORD_HEX; _state = RECORD_HEX;
} }
if (mState != RECORD_HEX) { if (_state != RECORD_HEX) {
mChecksum += inbyte; _checksum += inbyte;
} }
inbyte = toupper(inbyte); inbyte = toupper(inbyte);
switch(mState) { switch(_state) {
case IDLE: case IDLE:
/* wait for \n of the start of an record */ /* wait for \n of the start of an record */
switch(inbyte) { switch(inbyte) {
case '\n': case '\n':
mState = RECORD_BEGIN; _state = RECORD_BEGIN;
break; break;
case '\r': /* Skip */ case '\r': /* Skip */
default: default:
@ -114,29 +107,26 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte)
} }
break; break;
case RECORD_BEGIN: case RECORD_BEGIN:
idx = 0; _name = (char) inbyte;
mName[idx++] = inbyte; _state = RECORD_NAME;
mState = RECORD_NAME;
break; break;
case RECORD_NAME: case RECORD_NAME:
// The record name is being received, terminated by a \t // The record name is being received, terminated by a \t
switch(inbyte) { switch(inbyte) {
case '\t': case '\t':
// the Checksum record indicates a EOR // the Checksum record indicates a EOR
if ( idx < sizeof(mName) ) { if (_name.equals(checksumTagName)) {
mName[idx] = 0; /* Zero terminate */ _state = CHECKSUM;
if (strcmp(mName, checksumTagName) == 0) {
mState = CHECKSUM;
break; break;
} }
} _state = RECORD_VALUE;
idx = 0; /* Reset value pointer */ _value = "";
mState = RECORD_VALUE; break;
case '#': /* Ignore # from serial number*/
break; break;
default: default:
// add byte to name, but do no overflow // add byte to name, but do no overflow
if ( idx < sizeof(mName) ) _name += (char) inbyte;
mName[idx++] = inbyte;
break; break;
} }
break; break;
@ -144,51 +134,36 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte)
// The record value is being received. The \r indicates a new record. // The record value is being received. The \r indicates a new record.
switch(inbyte) { switch(inbyte) {
case '\n': case '\n':
// forward record, only if it could be stored completely _tmpMap[_name] = _value;
if ( idx < sizeof(mValue) ) { _state = RECORD_BEGIN;
mValue[idx] = 0; // make zero ended
textRxEvent(mName, mValue);
}
mState = RECORD_BEGIN;
break; break;
case '\r': /* Skip */ case '\r': /* Skip */
break; break;
default: default:
// add byte to value, but do no overflow // add byte to value, but do no overflow
if ( idx < sizeof(mValue) ) _value += (char) inbyte;
mValue[idx++] = inbyte;
break; break;
} }
break; break;
case CHECKSUM: case CHECKSUM:
{ {
bool valid = mChecksum == 0; bool valid = _checksum == 0;
if (!valid) if (!valid)
logE(MODULE,"[CHECKSUM] Invalid frame"); logE(MODULE,"[CHECKSUM] Invalid frame");
mChecksum = 0; _checksum = 0;
mState = IDLE; _state = IDLE;
frameEndEvent(valid); frameEndEvent(valid);
break; break;
} }
case RECORD_HEX: case RECORD_HEX:
if (hexRxEvent(inbyte)) { if (hexRxEvent(inbyte)) {
mChecksum = 0; _checksum = 0;
mState = IDLE; _state = IDLE;
} }
break; break;
} }
} }
/*
* textRxEvent
* This function is called every time a new name/value is successfully parsed. It writes the values to the temporary buffer.
*/
void VeDirectFrameHandler::textRxEvent(char * mName, char * mValue) {
strcpy(tempName[frameIndex], mName); // copy name to temporary buffer
strcpy(tempValue[frameIndex], mValue); // copy value to temporary buffer
frameIndex++;
}
/* /*
* frameEndEvent * frameEndEvent
* This function is called at the end of the received frame. If the checksum is valid, the temp buffer is read line by line. * This function is called at the end of the received frame. If the checksum is valid, the temp buffer is read line by line.
@ -197,27 +172,9 @@ void VeDirectFrameHandler::textRxEvent(char * mName, char * mValue) {
*/ */
void VeDirectFrameHandler::frameEndEvent(bool valid) { void VeDirectFrameHandler::frameEndEvent(bool valid) {
if ( valid ) { if ( valid ) {
for ( int i = 0; i < frameIndex; i++ ) { // read each name already in the temp buffer veMap = _tmpMap;
bool nameExists = false;
for ( int j = 0; j <= veEnd; j++ ) { // compare to existing names in the public buffer
if ( strcmp(tempName[i], veName[j]) == 0 ) {
strcpy(veValue[j], tempValue[i]); // overwrite tempValue in the public buffer
nameExists = true;
break;
}
}
if ( !nameExists ) {
strcpy(veName[veEnd], tempName[i]); // write new Name to public buffer
strcpy(veValue[veEnd], tempValue[i]); // write new Value to public buffer
veEnd++; // increment end of public buffer
if ( veEnd >= buffLen ) { // stop any buffer overrun
veEnd = buffLen - 1;
}
}
}
setLastUpdate(); setLastUpdate();
} }
frameIndex = 0; // reset frame
} }
/* /*
@ -233,8 +190,8 @@ void VeDirectFrameHandler::logE(const char * module, const char * error) {
} }
/* /*
* getLastUpdate * hexRxEvent
* This function returns the timestamp of the last succesful read of a ve.direct frame. * This function included for continuity and possible future use.
*/ */
bool VeDirectFrameHandler::hexRxEvent(uint8_t inbyte) { bool VeDirectFrameHandler::hexRxEvent(uint8_t inbyte) {
return true; // stubbed out for future return true; // stubbed out for future

View File

@ -5,23 +5,21 @@
* *
* 2020.05.05 - 0.2 - initial release * 2020.05.05 - 0.2 - initial release
* 2021.02.23 - 0.3 - change frameLen to 22 per VE.Direct Protocol version 3.30 * 2021.02.23 - 0.3 - change frameLen to 22 per VE.Direct Protocol version 3.30
* 2022.08.20 - 0.4 - changes for OpenDTU
* *
*/ */
#pragma once #pragma once
#include <Arduino.h> #include <Arduino.h>
#include <map>
const byte frameLen = 22; // VE.Direct Protocol: max frame size is 18
const byte nameLen = 9; // VE.Direct Protocol: max name size is 9 including /0
const byte valueLen = 33; // VE.Direct Protocol: max value size is 33 including /0
const byte buffLen = 40; // Maximum number of lines possible from the device. Current protocol shows this to be the BMV700 at 33 lines.
#ifndef VICTRON_PIN_TX #ifndef VICTRON_PIN_TX
#define VICTRON_PIN_TX 21 #define VICTRON_PIN_TX 21 // HardwareSerial TX Pin
#endif #endif
#ifndef VICTRON_PIN_RX #ifndef VICTRON_PIN_RX
#define VICTRON_PIN_RX 22 #define VICTRON_PIN_RX 22 // HardwareSerial RX Pin
#endif #endif
@ -31,25 +29,25 @@ class VeDirectFrameHandler {
public: public:
VeDirectFrameHandler(); VeDirectFrameHandler();
void init(); void init(); // initialize HardewareSerial
void setPollInterval(unsigned long interval); void setPollInterval(unsigned long interval); // set poll intervall in seconds
void loop(); void loop(); // main loop to read ve.direct data
unsigned long getLastUpdate(); unsigned long getLastUpdate(); // timestamp of last successful frame read
void setLastUpdate(); String getPidAsString(const char* pid); // product id as string
String getPidAsString(const char* pid); String getCsAsString(const char* cs); // current state as string
String getCsAsString(const char* pid); String getErrAsString(const char* err); // errer state as string
String getErrAsString(const char* err); String getOrAsString(const char* offReason); // off reason as string
String getOrAsString(const char* offReason); String getMpptAsString(const char* mppt); // state of mppt as string
String getMpptAsString(const char* mppt);
char veName[buffLen][nameLen] = { }; // public buffer for received names std::map<String, String> veMap; // public map for received name and value pairs
char veValue[buffLen][valueLen] = { }; // public buffer for received values
int frameIndex; // which line of the frame are we on
int veEnd; // current size (end) of the public buffer
unsigned long polltime=0;
private: private:
void setLastUpdate(); // set timestampt after successful frame read
void rxData(uint8_t inbyte); // byte of serial data
void frameEndEvent(bool); // copy temp map to public map
void logE(const char *, const char *);
bool hexRxEvent(uint8_t);
//bool mStop; // not sure what Victron uses this for, not using //bool mStop; // not sure what Victron uses this for, not using
enum States { // state machine enum States { // state machine
@ -61,22 +59,11 @@ private:
RECORD_HEX RECORD_HEX
}; };
int mState; // current state int _state; // current state
uint8_t _checksum; // checksum value
uint8_t mChecksum; // checksum value String _name; // buffer for the field name
String _value; // buffer for the field value
int idx; // index to the private buffer we're writing to, name or value std::map<String, String> _tmpMap; // private map for received name and value pairs
char mName[9]; // buffer for the field name
char mValue[33]; // buffer for the field value
char tempName[frameLen][nameLen]; // private buffer for received names
char tempValue[frameLen][valueLen]; // private buffer for received values
void rxData(uint8_t inbyte); // byte of serial data to be passed by the application
void textRxEvent(char *, char *);
void frameEndEvent(bool);
void logE(const char *, const char *);
bool hexRxEvent(uint8_t);
unsigned long _pollInterval; unsigned long _pollInterval;
unsigned long _lastPoll; unsigned long _lastPoll;
}; };

View File

@ -29,9 +29,9 @@ void MqttVedirectPublishingClass::loop()
bool bChanged; bool bChanged;
String topic = ""; String topic = "";
for ( int i = 0; i < VeDirect.veEnd; i++ ) { for (auto it = VeDirect.veMap.begin(); it != VeDirect.veMap.end(); ++it) {
key = VeDirect.veName[i]; key = it->first;
value = VeDirect.veValue[i]; value = it->second;
// Add new key, value pairs to map and update changed values. // Add new key, value pairs to map and update changed values.
// Mark changed values // Mark changed values
@ -54,7 +54,6 @@ void MqttVedirectPublishingClass::loop()
if (!config.Vedirect_UpdatesOnly || (bChanged && config.Vedirect_UpdatesOnly)) { if (!config.Vedirect_UpdatesOnly || (bChanged && config.Vedirect_UpdatesOnly)) {
topic = "victron/"; topic = "victron/";
topic.concat(key); topic.concat(key);
topic.replace("#",""); // # is a no go in mqtt topic
MqttSettings.publish(topic.c_str(), value.c_str()); MqttSettings.publish(topic.c_str(), value.c_str());
} }
} }

View File

@ -67,52 +67,33 @@ void WebApiWsVedirectLiveClass::generateJsonResponse(JsonVariant& root)
{ {
root[F("data_age")] = (millis() - VeDirect.getLastUpdate() ) / 1000; root[F("data_age")] = (millis() - VeDirect.getLastUpdate() ) / 1000;
root[F("age_critical")] = ((millis() - VeDirect.getLastUpdate()) / 1000) > Configuration.get().Vedirect_PollInterval * 5; root[F("age_critical")] = ((millis() - VeDirect.getLastUpdate()) / 1000) > Configuration.get().Vedirect_PollInterval * 5;
root[F("PID")] = VeDirect.getPidAsString(VeDirect.veMap["PID"].c_str());
for ( int i = 0; i < VeDirect.veEnd; i++ ) { root[F("SER")] = VeDirect.veMap["SER"];
if(strcmp(VeDirect.veName[i], "PID") == 0) { root[F("FW")] = VeDirect.veMap["FW"];
root[F(VeDirect.veName[i])] = VeDirect.getPidAsString(VeDirect.veValue[i]); root[F("CS")] = VeDirect.getCsAsString(VeDirect.veMap["CS"].c_str());
} root[F("ERR")] = VeDirect.getErrAsString(VeDirect.veMap["ERR"].c_str());
else if(strcmp(VeDirect.veName[i], "SER#") == 0) { root[F("OR")] = VeDirect.getOrAsString(VeDirect.veMap["OR"].c_str());
root[F("SER")] = VeDirect.veValue[i]; root[F("MPPT")] = VeDirect.getMpptAsString(VeDirect.veMap["MPPT"].c_str());
} root[F("VPV")]["v"] = round(VeDirect.veMap["VPV"].toDouble() / 10.0) / 100.0;
else if(strcmp(VeDirect.veName[i], "CS") == 0) { root[F("VPV")]["u"] = "V";
root[F(VeDirect.veName[i])] = VeDirect.getCsAsString(VeDirect.veValue[i]); root[F("V")]["v"] = round(VeDirect.veMap["V"].toDouble() / 10.0) / 100.0;
} root[F("V")]["u"] = "V";
else if(strcmp(VeDirect.veName[i], "ERR") == 0) { root[F("I")]["v"] = round(VeDirect.veMap["I"].toDouble() / 10.0) / 100.0;
root[F(VeDirect.veName[i])] = VeDirect.getErrAsString(VeDirect.veValue[i]); root[F("I")]["u"] = "A";
} root[F("PPV")]["v"] = VeDirect.veMap["PPV"].toInt();
else if(strcmp(VeDirect.veName[i], "OR") == 0) { root[F("PPV")]["u"] = "W";
root[F(VeDirect.veName[i])] = VeDirect.getOrAsString(VeDirect.veValue[i]); root[F("H21")]["v"] = VeDirect.veMap["H21"].toInt();
} root[F("H21")]["u"] = "W";
else if(strcmp(VeDirect.veName[i], "MPPT") == 0) { root[F("H23")]["v"] = VeDirect.veMap["H23"].toInt();
root[F(VeDirect.veName[i])] = VeDirect.getMpptAsString(VeDirect.veValue[i]); root[F("H23")]["u"] = "W";
} root[F("H19")]["v"] = VeDirect.veMap["H19"].toDouble() / 100.0;
else if((strcmp(VeDirect.veName[i], "V") == 0) || (strcmp(VeDirect.veName[i], "VPV") == 0)) { root[F("H19")]["u"] = "kWh";
root[F(VeDirect.veName[i])]["v"] = round(std::stod(VeDirect.veValue[i]) / 10.0) / 100.0; root[F("H20")]["v"] = VeDirect.veMap["H20"].toDouble() / 100.0;
root[F(VeDirect.veName[i])]["u"] = "V"; root[F("H20")]["u"] = "kWh";
} root[F("H22")]["v"] = VeDirect.veMap["H22"].toDouble() / 100.0;
else if(strcmp(VeDirect.veName[i], "I") == 0) { root[F("H22")]["u"] = "kWh";
root[F(VeDirect.veName[i])]["v"] = round(std::stod(VeDirect.veValue[i]) / 10.0) / 100.0; root[F("HSDS")]["v"] = VeDirect.veMap["HSDS"].toInt();
root[F(VeDirect.veName[i])]["u"] = "A"; root[F("HSDS")]["u"] = "Days";
}
else if((strcmp(VeDirect.veName[i], "PPV") == 0) || (strcmp(VeDirect.veName[i], "H21") == 0) || (strcmp(VeDirect.veName[i], "H23") == 0)){
root[F(VeDirect.veName[i])]["v"] = std::stoi(VeDirect.veValue[i]);
root[F(VeDirect.veName[i])]["u"] = "W";
}
else if((strcmp(VeDirect.veName[i], "H19") == 0) || (strcmp(VeDirect.veName[i], "H20") == 0) || (strcmp(VeDirect.veName[i], "H22") == 0)){
root[F(VeDirect.veName[i])]["v"] = std::stod(VeDirect.veValue[i]) / 100.0;
root[F(VeDirect.veName[i])]["u"] = "kWh";
}
else if(strcmp(VeDirect.veName[i], "HSDS") == 0){
root[F(VeDirect.veName[i])]["v"] = std::stoi(VeDirect.veValue[i]);
root[F(VeDirect.veName[i])]["u"] = "Days";
}
else {
root[F(VeDirect.veName[i])] = VeDirect.veValue[i];
}
}
root[F("polltime")] = VeDirect.polltime;
root[F("VE.end")] = VeDirect.veEnd;
if (VeDirect.getLastUpdate() > _newestVedirectTimestamp) { if (VeDirect.getLastUpdate() > _newestVedirectTimestamp) {
_newestVedirectTimestamp = VeDirect.getLastUpdate(); _newestVedirectTimestamp = VeDirect.getLastUpdate();
} }