frame handler with string and map
This commit is contained in:
parent
7140574c37
commit
48e5b567cb
@ -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
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user