Skip to content

Commit

Permalink
frame handler with string and map
Browse files Browse the repository at this point in the history
  • Loading branch information
helgeerbe committed Aug 20, 2022
1 parent 7140574 commit 48e5b56
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 166 deletions.
109 changes: 33 additions & 76 deletions lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -74,8 +69,6 @@ void VeDirectFrameHandler::setPollInterval(unsigned long interval)

void VeDirectFrameHandler::loop()
{
polltime = _pollInterval;

if ((millis() - getLastUpdate()) < _pollInterval * 1000) {
return;
}
Expand All @@ -87,108 +80,90 @@ 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:
break;
}
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;
case RECORD_VALUE:
// 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.
Expand All @@ -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
}

/*
Expand All @@ -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
Expand Down
67 changes: 27 additions & 40 deletions lib/VeDirectFrameHandler/VeDirectFrameHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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);

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;
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

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
Expand All @@ -61,22 +59,11 @@ class VeDirectFrameHandler {
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;
};
Expand Down
7 changes: 3 additions & 4 deletions src/MqttVedirectPublishing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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());
}
}
Expand Down
Loading

0 comments on commit 48e5b56

Please sign in to comment.