diff --git a/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp b/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp index 73c8189d..9db105c7 100644 --- a/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp +++ b/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp @@ -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 diff --git a/lib/VeDirectFrameHandler/VeDirectFrameHandler.h b/lib/VeDirectFrameHandler/VeDirectFrameHandler.h index 07cfc945..71343041 100644 --- a/lib/VeDirectFrameHandler/VeDirectFrameHandler.h +++ b/lib/VeDirectFrameHandler/VeDirectFrameHandler.h @@ -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 - -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 #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 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 _tmpMap; // private map for received name and value pairs unsigned long _pollInterval; unsigned long _lastPoll; }; diff --git a/src/MqttVedirectPublishing.cpp b/src/MqttVedirectPublishing.cpp index cea99327..809c526a 100644 --- a/src/MqttVedirectPublishing.cpp +++ b/src/MqttVedirectPublishing.cpp @@ -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()); } } diff --git a/src/WebApi_ws_vedirect_live.cpp b/src/WebApi_ws_vedirect_live.cpp index ebd10667..8a30f2b2 100644 --- a/src/WebApi_ws_vedirect_live.cpp +++ b/src/WebApi_ws_vedirect_live.cpp @@ -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(); }