Skip to content

Commit

Permalink
first version
Browse files Browse the repository at this point in the history
  • Loading branch information
helgeerbe committed Feb 21, 2023
1 parent 3044047 commit 1e7f6b8
Show file tree
Hide file tree
Showing 7 changed files with 240 additions and 137 deletions.
2 changes: 1 addition & 1 deletion include/MqttHandleVedirect.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class MqttHandleVedirectClass {
void init();
void loop();
private:
std::map<String, String> _kv_map;
veStruct _kvFrame;
uint32_t _lastPublish;
};

Expand Down
143 changes: 111 additions & 32 deletions lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ char MODULE[] = "VE.Frame"; // Victron seems to use this to find out where loggi
// The name of the record that contains the checksum.
static constexpr char checksumTagName[] = "CHECKSUM";

// state machine
enum States {
IDLE,
RECORD_BEGIN,
RECORD_NAME,
RECORD_VALUE,
CHECKSUM,
RECORD_HEX
};

HardwareSerial VedirectSerial(1);

VeDirectFrameHandler VeDirect;
Expand All @@ -49,8 +59,10 @@ VeDirectFrameHandler::VeDirectFrameHandler() :
//mStop(false), // don't know what Victron uses this for, not using
_state(IDLE),
_checksum(0),
_textPointer(0),
_name(""),
_value(""),
_tmpFrame(),
_pollInterval(5),
_lastPoll(0)
{
Expand Down Expand Up @@ -107,41 +119,50 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte)
}
break;
case RECORD_BEGIN:
_name = (char) inbyte;
_textPointer = _name;
*_textPointer++ = 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 (_name.equals(checksumTagName)) {
_state = CHECKSUM;
break;
if ( _textPointer < (_name + sizeof(_name)) ) {
*_textPointer = 0; /* Zero terminate */
if (strcmp(_name, checksumTagName) == 0) {
_state = CHECKSUM;
break;
}
}
_textPointer = _value; /* Reset value pointer */
_state = RECORD_VALUE;
_value = "";
break;
case '#': /* Ignore # from serial number*/
break;
default:
// add byte to name, but do no overflow
_name += (char) inbyte;
if ( _textPointer < (_name + sizeof(_name)) )
*_textPointer++ = inbyte;
break;
}
break;
case RECORD_VALUE:
// The record value is being received. The \r indicates a new record.
switch(inbyte) {
case '\n':
_tmpMap[_name] = _value;
if ( _textPointer < (_value + sizeof(_value)) ) {
*_textPointer = 0; // make zero ended
textRxEvent(_name, _value);
}
_state = RECORD_BEGIN;
break;
case '\r': /* Skip */
break;
default:
// add byte to value, but do no overflow
_value += (char) inbyte;
if ( _textPointer < (_value + sizeof(_value)) )
*_textPointer++ = inbyte;
break;
}
break;
Expand All @@ -164,6 +185,72 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte)
}
}

/*
* 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 * name, char * value) {
if (strcmp(name, "PID") == 0) {
_tmpFrame.PID = strtol(value, nullptr, 0);
}
else if (strcmp(name, "SER") == 0) {
strcpy(_tmpFrame.SER, value);
}
else if (strcmp(name, "FW") == 0) {
strcpy(_tmpFrame.FW, value);
}
else if (strcmp(name, "LOAD") == 0) {
if (strcmp(value, "ON") == 0)
_tmpFrame.LOAD = true;
else
_tmpFrame.LOAD = false;
}
else if (strcmp(name, "CS") == 0) {
_tmpFrame.CS = atoi(value);
}
else if (strcmp(name, "ERR") == 0) {
_tmpFrame.ERR = atoi(value);
}
else if (strcmp(name, "OR") == 0) {
_tmpFrame.OR = strtol(value, nullptr, 0);
}
else if (strcmp(name, "MPPT") == 0) {
_tmpFrame.MPPT = atoi(value);
}
else if (strcmp(name, "HSDS") == 0) {
_tmpFrame.HSDS = atoi(value);
}
else if (strcmp(name, "V") == 0) {
_tmpFrame.V = round(atof(value) / 10.0) / 100.0;
}
else if (strcmp(name, "I") == 0) {
_tmpFrame.I = round(atof(value) / 10.0) / 100.0;
}
else if (strcmp(name, "VPV") == 0) {
_tmpFrame.VPV = round(atof(value) / 10.0) / 100.0;
}
else if (strcmp(name, "PPV") == 0) {
_tmpFrame.PPV = atoi(value);
}
else if (strcmp(name, "H19") == 0) {
_tmpFrame.H19 = atof(value) / 100.0;
}
else if (strcmp(name, "H20") == 0) {
_tmpFrame.H20 = atof(value) / 100.0;
}
else if (strcmp(name, "H21") == 0) {
_tmpFrame.H21 = atoi(value);
}
else if (strcmp(name, "H22") == 0) {
_tmpFrame.H22 = atof(value) / 100.0;
}
else if (strcmp(name, "H23") == 0) {
_tmpFrame.H23 = atoi(value);
}

}


/*
* 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 @@ -172,10 +259,10 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte)
*/
void VeDirectFrameHandler::frameEndEvent(bool valid) {
if ( valid ) {
veMap = _tmpMap;
veFrame = _tmpFrame;
setLastUpdate();
}
_tmpMap.clear();
_tmpFrame = {};
}

/*
Expand All @@ -199,15 +286,12 @@ bool VeDirectFrameHandler::hexRxEvent(uint8_t inbyte) {
}

bool VeDirectFrameHandler::isDataValid() {
if (veMap.empty()) {
return false;
}
if ((millis() - getLastUpdate()) / 1000 > _pollInterval * 5) {
return false;
}
if (veMap.find("SER") == veMap.end()) {
return false;
}
if (strlen(veFrame.SER) == 0) {
return false;
}
return true;
}

Expand All @@ -229,12 +313,11 @@ void VeDirectFrameHandler::setLastUpdate()
* getPidAsString
* This function returns the product id (PID) as readable text.
*/
String VeDirectFrameHandler::getPidAsString(const char* pid)
String VeDirectFrameHandler::getPidAsString(uint16_t pid)
{
String strPID ="";

long lPID = strtol(pid, nullptr, 0);
switch(lPID) {
switch(pid) {
case 0x0300:
strPID = "BlueSolar MPPT 70|15";
break;
Expand Down Expand Up @@ -452,12 +535,11 @@ String VeDirectFrameHandler::getPidAsString(const char* pid)
* getCsAsString
* This function returns the state of operations (CS) as readable text.
*/
String VeDirectFrameHandler::getCsAsString(const char* cs)
String VeDirectFrameHandler::getCsAsString(uint8_t cs)
{
String strCS ="";

int iCS = atoi(cs);
switch(iCS) {
switch(cs) {
case 0:
strCS = "OFF";
break;
Expand Down Expand Up @@ -495,12 +577,11 @@ String VeDirectFrameHandler::getCsAsString(const char* cs)
* getErrAsString
* This function returns error state (ERR) as readable text.
*/
String VeDirectFrameHandler::getErrAsString(const char* err)
String VeDirectFrameHandler::getErrAsString(uint8_t err)
{
String strERR ="";

int iERR = atoi(err);
switch(iERR) {
switch(err) {
case 0:
strERR = "No error";
break;
Expand Down Expand Up @@ -571,12 +652,11 @@ String VeDirectFrameHandler::getErrAsString(const char* err)
* getOrAsString
* This function returns the off reason (OR) as readable text.
*/
String VeDirectFrameHandler::getOrAsString(const char* offReason)
String VeDirectFrameHandler::getOrAsString(uint32_t offReason)
{
String strOR ="";

long lOR = strtol(offReason, nullptr, 0);
switch(lOR) {
switch(offReason) {
case 0x00000000:
strOR = "Not off";
break;
Expand Down Expand Up @@ -617,14 +697,13 @@ String VeDirectFrameHandler::getOrAsString(const char* offReason)
* getMpptAsString
* This function returns the state of MPPT (MPPT) as readable text.
*/
String VeDirectFrameHandler::getMpptAsString(const char* mppt)
String VeDirectFrameHandler::getMpptAsString(uint8_t mppt)
{
String strMPPT ="";

int iMPPT = atoi(mppt);
switch(iMPPT) {
switch(mppt) {
case 0:
strMPPT = "Off";
strMPPT = "OFF";
break;
case 1:
strMPPT = "Voltage or current limited";
Expand Down
54 changes: 34 additions & 20 deletions lib/VeDirectFrameHandler/VeDirectFrameHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,29 @@
#define VICTRON_PIN_RX 22 // HardwareSerial RX Pin
#endif


#define VE_MAX_NAME_LEN 9 // VE.Direct Protocol: max name size is 9 including /0
#define VE_MAX_VALUE_LEN 33 // VE.Direct Protocol: max value size is 33 including /0

typedef struct {
uint16_t PID; // pruduct id
char SER[VE_MAX_VALUE_LEN]; // serial number
char FW[VE_MAX_VALUE_LEN]; // firmware release number
bool LOAD; // virtual load output state (on if battery voltage reaches upper limit, off if battery reaches lower limit)
uint8_t CS; // current state of operation e. g. OFF or Bulk
uint8_t ERR; // error code
uint32_t OR; // off reason
uint8_t MPPT; // state of MPP tracker
uint16_t HSDS; // day sequence number 1...365
double V; // battery voltage in V
double I; // battery current in A
double VPV; // panel voltage in V
double PPV; // panel power in W
double H19; // yield total kWh
double H20; // yield today kWh
uint16_t H21; // maximum power today W
double H22; // yield yesterday kWh
uint16_t H23; // maximum power yesterday W
} veStruct;

class VeDirectFrameHandler {

Expand All @@ -34,37 +56,29 @@ class VeDirectFrameHandler {
void loop(); // main loop to read ve.direct data
unsigned long getLastUpdate(); // timestamp of last successful frame read
bool isDataValid(); // return true if data valid and not outdated
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
String getPidAsString(uint16_t pid); // product id as string
String getCsAsString(uint8_t cs); // current state as string
String getErrAsString(uint8_t err); // errer state as string
String getOrAsString(uint32_t offReason); // off reason as string
String getMpptAsString(uint8_t mppt); // state of mppt as string

std::map<String, String> veMap; // public map for received name and value pairs
veStruct veFrame; // 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 textRxEvent(char *, char *);
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
IDLE,
RECORD_BEGIN,
RECORD_NAME,
RECORD_VALUE,
CHECKSUM,
RECORD_HEX
};

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
char * _textPointer; // pointer to the private buffer we're writing to, name or value
char _name[VE_MAX_VALUE_LEN]; // buffer for the field name
char _value[VE_MAX_VALUE_LEN]; // buffer for the field value
veStruct _tmpFrame; // private struct for received name and value pairs
unsigned long _pollInterval;
unsigned long _lastPoll;
};
Expand Down
Loading

0 comments on commit 1e7f6b8

Please sign in to comment.