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

View File

@ -5,23 +5,21 @@
*
* 2020.05.05 - 0.2 - initial release
* 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
#include <Arduino.h>
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.
#include <map>
#ifndef VICTRON_PIN_TX
#define VICTRON_PIN_TX 21
#define VICTRON_PIN_TX 21 // HardwareSerial TX Pin
#endif
#ifndef VICTRON_PIN_RX
#define VICTRON_PIN_RX 22
#define VICTRON_PIN_RX 22 // HardwareSerial RX Pin
#endif
@ -31,25 +29,25 @@ class VeDirectFrameHandler {
public:
VeDirectFrameHandler();
void init();
void setPollInterval(unsigned long interval);
void loop();
unsigned long getLastUpdate();
void setLastUpdate();
String getPidAsString(const char* pid);
String getCsAsString(const char* pid);
String getErrAsString(const char* err);
String getOrAsString(const char* offReason);
String getMpptAsString(const char* mppt);
void init(); // initialize HardewareSerial
void setPollInterval(unsigned long interval); // set poll intervall in seconds
void loop(); // main loop to read ve.direct data
unsigned long getLastUpdate(); // timestamp of last successful frame read
String getPidAsString(const char* pid); // product id as string
String getCsAsString(const char* cs); // current state as string
String getErrAsString(const char* err); // errer state as string
String getOrAsString(const char* offReason); // off reason as string
String getMpptAsString(const char* mppt); // state of mppt as string
char veName[buffLen][nameLen] = { }; // public buffer for received names
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;
std::map<String, String> veMap; // public map for received name and value pairs
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
enum States { // state machine
@ -61,22 +59,11 @@ private:
RECORD_HEX
};
int mState; // current state
uint8_t mChecksum; // checksum value
int idx; // index to the private buffer we're writing to, name or value
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);
int _state; // current state
uint8_t _checksum; // checksum value
String _name; // buffer for the field name
String _value; // buffer for the field value
std::map<String, String> _tmpMap; // private map for received name and value pairs
unsigned long _pollInterval;
unsigned long _lastPoll;
};

View File

@ -29,9 +29,9 @@ void MqttVedirectPublishingClass::loop()
bool bChanged;
String topic = "";
for ( int i = 0; i < VeDirect.veEnd; i++ ) {
key = VeDirect.veName[i];
value = VeDirect.veValue[i];
for (auto it = VeDirect.veMap.begin(); it != VeDirect.veMap.end(); ++it) {
key = it->first;
value = it->second;
// Add new key, value pairs to map and update changed values.
// Mark changed values
@ -54,7 +54,6 @@ void MqttVedirectPublishingClass::loop()
if (!config.Vedirect_UpdatesOnly || (bChanged && config.Vedirect_UpdatesOnly)) {
topic = "victron/";
topic.concat(key);
topic.replace("#",""); // # is a no go in mqtt topic
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("age_critical")] = ((millis() - VeDirect.getLastUpdate()) / 1000) > Configuration.get().Vedirect_PollInterval * 5;
for ( int i = 0; i < VeDirect.veEnd; i++ ) {
if(strcmp(VeDirect.veName[i], "PID") == 0) {
root[F(VeDirect.veName[i])] = VeDirect.getPidAsString(VeDirect.veValue[i]);
}
else if(strcmp(VeDirect.veName[i], "SER#") == 0) {
root[F("SER")] = VeDirect.veValue[i];
}
else if(strcmp(VeDirect.veName[i], "CS") == 0) {
root[F(VeDirect.veName[i])] = VeDirect.getCsAsString(VeDirect.veValue[i]);
}
else if(strcmp(VeDirect.veName[i], "ERR") == 0) {
root[F(VeDirect.veName[i])] = VeDirect.getErrAsString(VeDirect.veValue[i]);
}
else if(strcmp(VeDirect.veName[i], "OR") == 0) {
root[F(VeDirect.veName[i])] = VeDirect.getOrAsString(VeDirect.veValue[i]);
}
else if(strcmp(VeDirect.veName[i], "MPPT") == 0) {
root[F(VeDirect.veName[i])] = VeDirect.getMpptAsString(VeDirect.veValue[i]);
}
else if((strcmp(VeDirect.veName[i], "V") == 0) || (strcmp(VeDirect.veName[i], "VPV") == 0)) {
root[F(VeDirect.veName[i])]["v"] = round(std::stod(VeDirect.veValue[i]) / 10.0) / 100.0;
root[F(VeDirect.veName[i])]["u"] = "V";
}
else if(strcmp(VeDirect.veName[i], "I") == 0) {
root[F(VeDirect.veName[i])]["v"] = round(std::stod(VeDirect.veValue[i]) / 10.0) / 100.0;
root[F(VeDirect.veName[i])]["u"] = "A";
}
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;
root[F("PID")] = VeDirect.getPidAsString(VeDirect.veMap["PID"].c_str());
root[F("SER")] = VeDirect.veMap["SER"];
root[F("FW")] = VeDirect.veMap["FW"];
root[F("CS")] = VeDirect.getCsAsString(VeDirect.veMap["CS"].c_str());
root[F("ERR")] = VeDirect.getErrAsString(VeDirect.veMap["ERR"].c_str());
root[F("OR")] = VeDirect.getOrAsString(VeDirect.veMap["OR"].c_str());
root[F("MPPT")] = VeDirect.getMpptAsString(VeDirect.veMap["MPPT"].c_str());
root[F("VPV")]["v"] = round(VeDirect.veMap["VPV"].toDouble() / 10.0) / 100.0;
root[F("VPV")]["u"] = "V";
root[F("V")]["v"] = round(VeDirect.veMap["V"].toDouble() / 10.0) / 100.0;
root[F("V")]["u"] = "V";
root[F("I")]["v"] = round(VeDirect.veMap["I"].toDouble() / 10.0) / 100.0;
root[F("I")]["u"] = "A";
root[F("PPV")]["v"] = VeDirect.veMap["PPV"].toInt();
root[F("PPV")]["u"] = "W";
root[F("H21")]["v"] = VeDirect.veMap["H21"].toInt();
root[F("H21")]["u"] = "W";
root[F("H23")]["v"] = VeDirect.veMap["H23"].toInt();
root[F("H23")]["u"] = "W";
root[F("H19")]["v"] = VeDirect.veMap["H19"].toDouble() / 100.0;
root[F("H19")]["u"] = "kWh";
root[F("H20")]["v"] = VeDirect.veMap["H20"].toDouble() / 100.0;
root[F("H20")]["u"] = "kWh";
root[F("H22")]["v"] = VeDirect.veMap["H22"].toDouble() / 100.0;
root[F("H22")]["u"] = "kWh";
root[F("HSDS")]["v"] = VeDirect.veMap["HSDS"].toInt();
root[F("HSDS")]["u"] = "Days";
if (VeDirect.getLastUpdate() > _newestVedirectTimestamp) {
_newestVedirectTimestamp = VeDirect.getLastUpdate();
}