first version
This commit is contained in:
parent
30440472f7
commit
1e7f6b8f0f
@ -19,7 +19,7 @@ public:
|
|||||||
void init();
|
void init();
|
||||||
void loop();
|
void loop();
|
||||||
private:
|
private:
|
||||||
std::map<String, String> _kv_map;
|
veStruct _kvFrame;
|
||||||
uint32_t _lastPublish;
|
uint32_t _lastPublish;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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.
|
// The name of the record that contains the checksum.
|
||||||
static constexpr char checksumTagName[] = "CHECKSUM";
|
static constexpr char checksumTagName[] = "CHECKSUM";
|
||||||
|
|
||||||
|
// state machine
|
||||||
|
enum States {
|
||||||
|
IDLE,
|
||||||
|
RECORD_BEGIN,
|
||||||
|
RECORD_NAME,
|
||||||
|
RECORD_VALUE,
|
||||||
|
CHECKSUM,
|
||||||
|
RECORD_HEX
|
||||||
|
};
|
||||||
|
|
||||||
HardwareSerial VedirectSerial(1);
|
HardwareSerial VedirectSerial(1);
|
||||||
|
|
||||||
VeDirectFrameHandler VeDirect;
|
VeDirectFrameHandler VeDirect;
|
||||||
@ -49,8 +59,10 @@ VeDirectFrameHandler::VeDirectFrameHandler() :
|
|||||||
//mStop(false), // don't know what Victron uses this for, not using
|
//mStop(false), // don't know what Victron uses this for, not using
|
||||||
_state(IDLE),
|
_state(IDLE),
|
||||||
_checksum(0),
|
_checksum(0),
|
||||||
|
_textPointer(0),
|
||||||
_name(""),
|
_name(""),
|
||||||
_value(""),
|
_value(""),
|
||||||
|
_tmpFrame(),
|
||||||
_pollInterval(5),
|
_pollInterval(5),
|
||||||
_lastPoll(0)
|
_lastPoll(0)
|
||||||
{
|
{
|
||||||
@ -107,7 +119,8 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case RECORD_BEGIN:
|
case RECORD_BEGIN:
|
||||||
_name = (char) inbyte;
|
_textPointer = _name;
|
||||||
|
*_textPointer++ = inbyte;
|
||||||
_state = RECORD_NAME;
|
_state = RECORD_NAME;
|
||||||
break;
|
break;
|
||||||
case RECORD_NAME:
|
case RECORD_NAME:
|
||||||
@ -115,18 +128,22 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte)
|
|||||||
switch(inbyte) {
|
switch(inbyte) {
|
||||||
case '\t':
|
case '\t':
|
||||||
// the Checksum record indicates a EOR
|
// the Checksum record indicates a EOR
|
||||||
if (_name.equals(checksumTagName)) {
|
if ( _textPointer < (_name + sizeof(_name)) ) {
|
||||||
_state = CHECKSUM;
|
*_textPointer = 0; /* Zero terminate */
|
||||||
break;
|
if (strcmp(_name, checksumTagName) == 0) {
|
||||||
|
_state = CHECKSUM;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
_textPointer = _value; /* Reset value pointer */
|
||||||
_state = RECORD_VALUE;
|
_state = RECORD_VALUE;
|
||||||
_value = "";
|
|
||||||
break;
|
break;
|
||||||
case '#': /* Ignore # from serial number*/
|
case '#': /* Ignore # from serial number*/
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// add byte to name, but do no overflow
|
// add byte to name, but do no overflow
|
||||||
_name += (char) inbyte;
|
if ( _textPointer < (_name + sizeof(_name)) )
|
||||||
|
*_textPointer++ = inbyte;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -134,14 +151,18 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte)
|
|||||||
// The record value is being received. The \r indicates a new record.
|
// The record value is being received. The \r indicates a new record.
|
||||||
switch(inbyte) {
|
switch(inbyte) {
|
||||||
case '\n':
|
case '\n':
|
||||||
_tmpMap[_name] = _value;
|
if ( _textPointer < (_value + sizeof(_value)) ) {
|
||||||
|
*_textPointer = 0; // make zero ended
|
||||||
|
textRxEvent(_name, _value);
|
||||||
|
}
|
||||||
_state = RECORD_BEGIN;
|
_state = RECORD_BEGIN;
|
||||||
break;
|
break;
|
||||||
case '\r': /* Skip */
|
case '\r': /* Skip */
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// add byte to value, but do no overflow
|
// add byte to value, but do no overflow
|
||||||
_value += (char) inbyte;
|
if ( _textPointer < (_value + sizeof(_value)) )
|
||||||
|
*_textPointer++ = inbyte;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -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
|
* 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.
|
* This function is called at the end of the received frame. If the checksum is valid, the temp buffer is read line by line.
|
||||||
@ -172,10 +259,10 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte)
|
|||||||
*/
|
*/
|
||||||
void VeDirectFrameHandler::frameEndEvent(bool valid) {
|
void VeDirectFrameHandler::frameEndEvent(bool valid) {
|
||||||
if ( valid ) {
|
if ( valid ) {
|
||||||
veMap = _tmpMap;
|
veFrame = _tmpFrame;
|
||||||
setLastUpdate();
|
setLastUpdate();
|
||||||
}
|
}
|
||||||
_tmpMap.clear();
|
_tmpFrame = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -199,15 +286,12 @@ bool VeDirectFrameHandler::hexRxEvent(uint8_t inbyte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool VeDirectFrameHandler::isDataValid() {
|
bool VeDirectFrameHandler::isDataValid() {
|
||||||
if (veMap.empty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if ((millis() - getLastUpdate()) / 1000 > _pollInterval * 5) {
|
if ((millis() - getLastUpdate()) / 1000 > _pollInterval * 5) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (veMap.find("SER") == veMap.end()) {
|
if (strlen(veFrame.SER) == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,12 +313,11 @@ void VeDirectFrameHandler::setLastUpdate()
|
|||||||
* getPidAsString
|
* getPidAsString
|
||||||
* This function returns the product id (PID) as readable text.
|
* This function returns the product id (PID) as readable text.
|
||||||
*/
|
*/
|
||||||
String VeDirectFrameHandler::getPidAsString(const char* pid)
|
String VeDirectFrameHandler::getPidAsString(uint16_t pid)
|
||||||
{
|
{
|
||||||
String strPID ="";
|
String strPID ="";
|
||||||
|
|
||||||
long lPID = strtol(pid, nullptr, 0);
|
switch(pid) {
|
||||||
switch(lPID) {
|
|
||||||
case 0x0300:
|
case 0x0300:
|
||||||
strPID = "BlueSolar MPPT 70|15";
|
strPID = "BlueSolar MPPT 70|15";
|
||||||
break;
|
break;
|
||||||
@ -452,12 +535,11 @@ String VeDirectFrameHandler::getPidAsString(const char* pid)
|
|||||||
* getCsAsString
|
* getCsAsString
|
||||||
* This function returns the state of operations (CS) as readable text.
|
* 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 ="";
|
String strCS ="";
|
||||||
|
|
||||||
int iCS = atoi(cs);
|
switch(cs) {
|
||||||
switch(iCS) {
|
|
||||||
case 0:
|
case 0:
|
||||||
strCS = "OFF";
|
strCS = "OFF";
|
||||||
break;
|
break;
|
||||||
@ -495,12 +577,11 @@ String VeDirectFrameHandler::getCsAsString(const char* cs)
|
|||||||
* getErrAsString
|
* getErrAsString
|
||||||
* This function returns error state (ERR) as readable text.
|
* This function returns error state (ERR) as readable text.
|
||||||
*/
|
*/
|
||||||
String VeDirectFrameHandler::getErrAsString(const char* err)
|
String VeDirectFrameHandler::getErrAsString(uint8_t err)
|
||||||
{
|
{
|
||||||
String strERR ="";
|
String strERR ="";
|
||||||
|
|
||||||
int iERR = atoi(err);
|
switch(err) {
|
||||||
switch(iERR) {
|
|
||||||
case 0:
|
case 0:
|
||||||
strERR = "No error";
|
strERR = "No error";
|
||||||
break;
|
break;
|
||||||
@ -571,12 +652,11 @@ String VeDirectFrameHandler::getErrAsString(const char* err)
|
|||||||
* getOrAsString
|
* getOrAsString
|
||||||
* This function returns the off reason (OR) as readable text.
|
* This function returns the off reason (OR) as readable text.
|
||||||
*/
|
*/
|
||||||
String VeDirectFrameHandler::getOrAsString(const char* offReason)
|
String VeDirectFrameHandler::getOrAsString(uint32_t offReason)
|
||||||
{
|
{
|
||||||
String strOR ="";
|
String strOR ="";
|
||||||
|
|
||||||
long lOR = strtol(offReason, nullptr, 0);
|
switch(offReason) {
|
||||||
switch(lOR) {
|
|
||||||
case 0x00000000:
|
case 0x00000000:
|
||||||
strOR = "Not off";
|
strOR = "Not off";
|
||||||
break;
|
break;
|
||||||
@ -617,14 +697,13 @@ String VeDirectFrameHandler::getOrAsString(const char* offReason)
|
|||||||
* getMpptAsString
|
* getMpptAsString
|
||||||
* This function returns the state of MPPT (MPPT) as readable text.
|
* 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 ="";
|
String strMPPT ="";
|
||||||
|
|
||||||
int iMPPT = atoi(mppt);
|
switch(mppt) {
|
||||||
switch(iMPPT) {
|
|
||||||
case 0:
|
case 0:
|
||||||
strMPPT = "Off";
|
strMPPT = "OFF";
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
strMPPT = "Voltage or current limited";
|
strMPPT = "Voltage or current limited";
|
||||||
|
|||||||
@ -22,7 +22,29 @@
|
|||||||
#define VICTRON_PIN_RX 22 // HardwareSerial RX Pin
|
#define VICTRON_PIN_RX 22 // HardwareSerial RX Pin
|
||||||
#endif
|
#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 {
|
class VeDirectFrameHandler {
|
||||||
|
|
||||||
@ -34,37 +56,29 @@ public:
|
|||||||
void loop(); // main loop to read ve.direct data
|
void loop(); // main loop to read ve.direct data
|
||||||
unsigned long getLastUpdate(); // timestamp of last successful frame read
|
unsigned long getLastUpdate(); // timestamp of last successful frame read
|
||||||
bool isDataValid(); // return true if data valid and not outdated
|
bool isDataValid(); // return true if data valid and not outdated
|
||||||
String getPidAsString(const char* pid); // product id as string
|
String getPidAsString(uint16_t pid); // product id as string
|
||||||
String getCsAsString(const char* cs); // current state as string
|
String getCsAsString(uint8_t cs); // current state as string
|
||||||
String getErrAsString(const char* err); // errer state as string
|
String getErrAsString(uint8_t err); // errer state as string
|
||||||
String getOrAsString(const char* offReason); // off reason as string
|
String getOrAsString(uint32_t offReason); // off reason as string
|
||||||
String getMpptAsString(const char* mppt); // state of mppt 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:
|
private:
|
||||||
void setLastUpdate(); // set timestampt after successful frame read
|
void setLastUpdate(); // set timestampt after successful frame read
|
||||||
void rxData(uint8_t inbyte); // byte of serial data
|
void rxData(uint8_t inbyte); // byte of serial data
|
||||||
|
void textRxEvent(char *, char *);
|
||||||
void frameEndEvent(bool); // copy temp map to public map
|
void frameEndEvent(bool); // copy temp map to public map
|
||||||
void logE(const char *, const char *);
|
void logE(const char *, const char *);
|
||||||
bool hexRxEvent(uint8_t);
|
bool hexRxEvent(uint8_t);
|
||||||
|
|
||||||
//bool mStop; // not sure what Victron uses this for, not using
|
//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
|
int _state; // current state
|
||||||
uint8_t _checksum; // checksum value
|
uint8_t _checksum; // checksum value
|
||||||
String _name; // buffer for the field name
|
char * _textPointer; // pointer to the private buffer we're writing to, name or value
|
||||||
String _value; // buffer for the field value
|
char _name[VE_MAX_VALUE_LEN]; // buffer for the field name
|
||||||
std::map<String, String> _tmpMap; // private map for received name and value pairs
|
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 _pollInterval;
|
||||||
unsigned long _lastPoll;
|
unsigned long _lastPoll;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -83,7 +83,7 @@ void MqttHandleVedirectHassClass::publishConfig()
|
|||||||
|
|
||||||
void MqttHandleVedirectHassClass::publishSensor(const char* caption, const char* subTopic, const char* deviceClass, const char* stateClass, const char* unitOfMeasurement )
|
void MqttHandleVedirectHassClass::publishSensor(const char* caption, const char* subTopic, const char* deviceClass, const char* stateClass, const char* unitOfMeasurement )
|
||||||
{
|
{
|
||||||
String serial = VeDirect.veMap["SER"];
|
String serial = VeDirect.veFrame.SER;
|
||||||
|
|
||||||
String sensorId = caption;
|
String sensorId = caption;
|
||||||
sensorId.replace(" ", "_");
|
sensorId.replace(" ", "_");
|
||||||
@ -93,7 +93,10 @@ void MqttHandleVedirectHassClass::publishSensor(const char* caption, const char*
|
|||||||
+ "/" + sensorId
|
+ "/" + sensorId
|
||||||
+ "/config";
|
+ "/config";
|
||||||
|
|
||||||
String statTopic = MqttSettings.getPrefix() + "victron/" + VeDirect.veMap["SER"] + "/" + subTopic;
|
String statTopic = MqttSettings.getPrefix() + "victron/";
|
||||||
|
statTopic.concat(VeDirect.veFrame.SER);
|
||||||
|
statTopic.concat("/");
|
||||||
|
statTopic.concat(subTopic);
|
||||||
|
|
||||||
DynamicJsonDocument root(1024);
|
DynamicJsonDocument root(1024);
|
||||||
root[F("name")] = caption;
|
root[F("name")] = caption;
|
||||||
@ -124,7 +127,7 @@ void MqttHandleVedirectHassClass::publishSensor(const char* caption, const char*
|
|||||||
}
|
}
|
||||||
void MqttHandleVedirectHassClass::publishBinarySensor(const char* caption, const char* subTopic, const char* payload_on, const char* payload_off)
|
void MqttHandleVedirectHassClass::publishBinarySensor(const char* caption, const char* subTopic, const char* payload_on, const char* payload_off)
|
||||||
{
|
{
|
||||||
String serial = VeDirect.veMap["SER"];
|
String serial = VeDirect.veFrame.SER;
|
||||||
|
|
||||||
String sensorId = caption;
|
String sensorId = caption;
|
||||||
sensorId.replace(" ", "_");
|
sensorId.replace(" ", "_");
|
||||||
@ -134,7 +137,10 @@ void MqttHandleVedirectHassClass::publishBinarySensor(const char* caption, const
|
|||||||
+ "/" + sensorId
|
+ "/" + sensorId
|
||||||
+ "/config";
|
+ "/config";
|
||||||
|
|
||||||
String statTopic = MqttSettings.getPrefix() + "victron/" + VeDirect.veMap["SER"] + "/" + subTopic;
|
String statTopic = MqttSettings.getPrefix() + "victron/";
|
||||||
|
statTopic.concat(VeDirect.veFrame.SER);
|
||||||
|
statTopic.concat("/");
|
||||||
|
statTopic.concat(subTopic);
|
||||||
|
|
||||||
DynamicJsonDocument root(1024);
|
DynamicJsonDocument root(1024);
|
||||||
root[F("name")] = caption;
|
root[F("name")] = caption;
|
||||||
@ -153,11 +159,12 @@ void MqttHandleVedirectHassClass::publishBinarySensor(const char* caption, const
|
|||||||
|
|
||||||
void MqttHandleVedirectHassClass::createDeviceInfo(JsonObject& object)
|
void MqttHandleVedirectHassClass::createDeviceInfo(JsonObject& object)
|
||||||
{
|
{
|
||||||
object[F("name")] = "Victron(" + VeDirect.veMap["SER"] + ")";
|
String serial = VeDirect.veFrame.SER;
|
||||||
object[F("ids")] = VeDirect.veMap["SER"];
|
object[F("name")] = "Victron(" + serial + ")";
|
||||||
|
object[F("ids")] = serial;
|
||||||
object[F("cu")] = String(F("http://")) + NetworkSettings.localIP().toString();
|
object[F("cu")] = String(F("http://")) + NetworkSettings.localIP().toString();
|
||||||
object[F("mf")] = F("OpenDTU");
|
object[F("mf")] = F("OpenDTU");
|
||||||
object[F("mdl")] = VeDirect.getPidAsString(VeDirect.veMap["PID"].c_str());
|
object[F("mdl")] = VeDirect.getPidAsString(VeDirect.veFrame.PID);
|
||||||
object[F("sw")] = AUTO_GIT_HASH;
|
object[F("sw")] = AUTO_GIT_HASH;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -29,65 +29,69 @@ void MqttHandleVedirectClass::loop()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (millis() - _lastPublish > (config.Mqtt_PublishInterval * 1000)) {
|
if (millis() - _lastPublish > (config.Mqtt_PublishInterval * 1000)) {
|
||||||
String key;
|
|
||||||
String value;
|
String value;
|
||||||
String mapedValue;
|
String topic = "victron/";
|
||||||
bool bChanged = false;
|
topic.concat(VeDirect.veFrame.SER);
|
||||||
String serial = VeDirect.veMap["SER"];
|
topic.concat("/");
|
||||||
|
|
||||||
String topic = "";
|
if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.PID != _kvFrame.PID)
|
||||||
for (auto it = VeDirect.veMap.begin(); it != VeDirect.veMap.end(); ++it) {
|
MqttSettings.publish(topic + "PID", VeDirect.getPidAsString(VeDirect.veFrame.PID));
|
||||||
key = it->first;
|
if (!config.Vedirect_UpdatesOnly || strcmp(VeDirect.veFrame.SER, _kvFrame.SER) != 0)
|
||||||
value = it->second;
|
MqttSettings.publish(topic + "SER", VeDirect.veFrame.SER );
|
||||||
|
if (!config.Vedirect_UpdatesOnly || strcmp(VeDirect.veFrame.FW, _kvFrame.FW) != 0)
|
||||||
if (config.Vedirect_UpdatesOnly){
|
MqttSettings.publish(topic + "FW", VeDirect.veFrame.FW);
|
||||||
// Mark changed values
|
if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.LOAD != _kvFrame.LOAD)
|
||||||
auto a = _kv_map.find(key);
|
MqttSettings.publish(topic + "LOAD", VeDirect.veFrame.LOAD == true ? "ON": "OFF");
|
||||||
bChanged = true;
|
if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.CS != _kvFrame.CS)
|
||||||
if (a != _kv_map.end()) {
|
MqttSettings.publish(topic + "CS", VeDirect.getCsAsString(VeDirect.veFrame.CS));
|
||||||
if (a->first.equals(value)) {
|
if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.ERR != _kvFrame.ERR)
|
||||||
bChanged = false;
|
MqttSettings.publish(topic + "ERR", VeDirect.getErrAsString(VeDirect.veFrame.ERR));
|
||||||
}
|
if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.OR != _kvFrame.OR)
|
||||||
}
|
MqttSettings.publish(topic + "OR", VeDirect.getOrAsString(VeDirect.veFrame.OR));
|
||||||
}
|
if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.MPPT != _kvFrame.MPPT)
|
||||||
|
MqttSettings.publish(topic + "MPPT", VeDirect.getMpptAsString(VeDirect.veFrame.MPPT));
|
||||||
// publish only changed key, values pairs
|
if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.HSDS != _kvFrame.HSDS) {
|
||||||
if (!config.Vedirect_UpdatesOnly || (bChanged && config.Vedirect_UpdatesOnly)) {
|
value = VeDirect.veFrame.HSDS;
|
||||||
topic = "victron/" + serial + "/";
|
MqttSettings.publish(topic + "HSDS", value);
|
||||||
topic.concat(key);
|
}
|
||||||
if (key.equals("PID")) {
|
if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.V != _kvFrame.V) {
|
||||||
mapedValue = VeDirect.getPidAsString(value.c_str());
|
value = VeDirect.veFrame.V;
|
||||||
}
|
MqttSettings.publish(topic + "V", value);
|
||||||
else if (key.equals("CS")) {
|
}
|
||||||
mapedValue = VeDirect.getCsAsString(value.c_str());
|
if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.I != _kvFrame.I) {
|
||||||
}
|
value = VeDirect.veFrame.I;
|
||||||
else if (key.equals("ERR")) {
|
MqttSettings.publish(topic + "I", value);
|
||||||
mapedValue = VeDirect.getErrAsString(value.c_str());
|
}
|
||||||
}
|
if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.VPV != _kvFrame.VPV) {
|
||||||
else if (key.equals("OR")) {
|
value = VeDirect.veFrame.VPV;
|
||||||
mapedValue = VeDirect.getOrAsString(value.c_str());
|
MqttSettings.publish(topic + "VPV", value);
|
||||||
}
|
}
|
||||||
else if (key.equals("MPPT")) {
|
if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.PPV != _kvFrame.PPV) {
|
||||||
mapedValue = VeDirect.getMpptAsString(value.c_str());
|
value = VeDirect.veFrame.PPV;
|
||||||
}
|
MqttSettings.publish(topic + "PPV", value);
|
||||||
else if (key.equals("V") ||
|
}
|
||||||
key.equals("I") ||
|
if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.H19 != _kvFrame.H19) {
|
||||||
key.equals("VPV")) {
|
value = VeDirect.veFrame.H19;
|
||||||
mapedValue = round(value.toDouble() / 10.0) / 100.0;
|
MqttSettings.publish(topic + "H19", value);
|
||||||
}
|
}
|
||||||
else if (key.equals("H19") ||
|
if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.H20 != _kvFrame.H20) {
|
||||||
key.equals("H20") ||
|
value = VeDirect.veFrame.H20;
|
||||||
key.equals("H22")) {
|
MqttSettings.publish(topic + "H20", value);
|
||||||
mapedValue = value.toDouble() / 100.0;
|
}
|
||||||
}
|
if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.H21 != _kvFrame.H21) {
|
||||||
else {
|
value = VeDirect.veFrame.H21;
|
||||||
mapedValue = value;
|
MqttSettings.publish(topic + "H21", value);
|
||||||
}
|
}
|
||||||
MqttSettings.publish(topic.c_str(), mapedValue.c_str());
|
if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.H22 != _kvFrame.H22) {
|
||||||
}
|
value = VeDirect.veFrame.H22;
|
||||||
|
MqttSettings.publish(topic + "H22", value);
|
||||||
|
}
|
||||||
|
if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.H23 != _kvFrame.H23) {
|
||||||
|
value = VeDirect.veFrame.H23;
|
||||||
|
MqttSettings.publish(topic + "H23", value);
|
||||||
}
|
}
|
||||||
if (config.Vedirect_UpdatesOnly){
|
if (config.Vedirect_UpdatesOnly){
|
||||||
_kv_map = VeDirect.veMap;
|
_kvFrame= VeDirect.veFrame;
|
||||||
}
|
}
|
||||||
_lastPublish = millis();
|
_lastPublish = millis();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -190,12 +190,11 @@ bool PowerLimiterClass::canUseDirectSolarPower()
|
|||||||
CONFIG_T& config = Configuration.get();
|
CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
if (!config.PowerLimiter_SolarPassTroughEnabled
|
if (!config.PowerLimiter_SolarPassTroughEnabled
|
||||||
|| !config.Vedirect_Enabled
|
|| !config.Vedirect_Enabled) {
|
||||||
|| !VeDirect.veMap.count("PPV")) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (VeDirect.veMap["PPV"].toInt() < 10) {
|
if (VeDirect.veFrame.PPV < 10.0) {
|
||||||
// Not enough power
|
// Not enough power
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -209,7 +208,7 @@ uint32_t PowerLimiterClass::getDirectSolarPower()
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return VeDirect.veMap["PPV"].toInt();
|
return (uint32_t) round(VeDirect.veFrame.PPV);
|
||||||
}
|
}
|
||||||
|
|
||||||
float PowerLimiterClass::getLoadCorrectedVoltage(std::shared_ptr<InverterAbstract> inverter)
|
float PowerLimiterClass::getLoadCorrectedVoltage(std::shared_ptr<InverterAbstract> inverter)
|
||||||
|
|||||||
@ -82,37 +82,37 @@ void WebApiWsVedirectLiveClass::generateJsonResponse(JsonVariant& root)
|
|||||||
// device info
|
// device info
|
||||||
root["data_age"] = (millis() - VeDirect.getLastUpdate() ) / 1000;
|
root["data_age"] = (millis() - VeDirect.getLastUpdate() ) / 1000;
|
||||||
root["age_critical"] = !VeDirect.isDataValid();
|
root["age_critical"] = !VeDirect.isDataValid();
|
||||||
root["PID"] = VeDirect.getPidAsString(VeDirect.veMap["PID"].c_str());
|
root["PID"] = VeDirect.getPidAsString(VeDirect.veFrame.PID);
|
||||||
root["SER"] = VeDirect.veMap["SER"];
|
root["SER"] = VeDirect.veFrame.SER;
|
||||||
root["FW"] = VeDirect.veMap["FW"];
|
root["FW"] = VeDirect.veFrame.FW;
|
||||||
root["LOAD"] = VeDirect.veMap["LOAD"];
|
root["LOAD"] = VeDirect.veFrame.LOAD == true ? "ON" : "OFF";
|
||||||
root["CS"] = VeDirect.getCsAsString(VeDirect.veMap["CS"].c_str());
|
root["CS"] = VeDirect.getCsAsString(VeDirect.veFrame.CS);
|
||||||
root["ERR"] = VeDirect.getErrAsString(VeDirect.veMap["ERR"].c_str());
|
root["ERR"] = VeDirect.getErrAsString(VeDirect.veFrame.ERR);
|
||||||
root["OR"] = VeDirect.getOrAsString(VeDirect.veMap["OR"].c_str());
|
root["OR"] = VeDirect.getOrAsString(VeDirect.veFrame.OR);
|
||||||
root["MPPT"] = VeDirect.getMpptAsString(VeDirect.veMap["MPPT"].c_str());
|
root["MPPT"] = VeDirect.getMpptAsString(VeDirect.veFrame.MPPT);
|
||||||
root["HSDS"]["v"] = VeDirect.veMap["HSDS"].toInt();
|
root["HSDS"]["v"] = VeDirect.veFrame.HSDS;
|
||||||
root["HSDS"]["u"] = "Days";
|
root["HSDS"]["u"] = "Days";
|
||||||
|
|
||||||
// battery info
|
// battery info
|
||||||
root["V"]["v"] = round(VeDirect.veMap["V"].toDouble() / 10.0) / 100.0;
|
root["V"]["v"] = VeDirect.veFrame.V;
|
||||||
root["V"]["u"] = "V";
|
root["V"]["u"] = "V";
|
||||||
root["I"]["v"] = round(VeDirect.veMap["I"].toDouble() / 10.0) / 100.0;
|
root["I"]["v"] = VeDirect.veFrame.I;
|
||||||
root["I"]["u"] = "A";
|
root["I"]["u"] = "A";
|
||||||
|
|
||||||
// panel info
|
// panel info
|
||||||
root["VPV"]["v"] = round(VeDirect.veMap["VPV"].toDouble() / 10.0) / 100.0;
|
root["VPV"]["v"] = VeDirect.veFrame.VPV;
|
||||||
root["VPV"]["u"] = "V";
|
root["VPV"]["u"] = "V";
|
||||||
root["PPV"]["v"] = VeDirect.veMap["PPV"].toInt();
|
root["PPV"]["v"] = VeDirect.veFrame.PPV;
|
||||||
root["PPV"]["u"] = "W";
|
root["PPV"]["u"] = "W";
|
||||||
root["H19"]["v"] = VeDirect.veMap["H19"].toDouble() / 100.0;
|
root["H19"]["v"] = VeDirect.veFrame.H19;
|
||||||
root["H19"]["u"] = "kWh";
|
root["H19"]["u"] = "kWh";
|
||||||
root["H20"]["v"] = VeDirect.veMap["H20"].toDouble() / 100.0;
|
root["H20"]["v"] = VeDirect.veFrame.H20;
|
||||||
root["H20"]["u"] = "kWh";
|
root["H20"]["u"] = "kWh";
|
||||||
root["H21"]["v"] = VeDirect.veMap["H21"].toInt();
|
root["H21"]["v"] = VeDirect.veFrame.H21;
|
||||||
root["H21"]["u"] = "W";
|
root["H21"]["u"] = "W";
|
||||||
root["H22"]["v"] = VeDirect.veMap["H22"].toDouble() / 100.0;
|
root["H22"]["v"] = VeDirect.veFrame.H22;
|
||||||
root["H22"]["u"] = "kWh";
|
root["H22"]["u"] = "kWh";
|
||||||
root["H23"]["v"] = VeDirect.veMap["H23"].toInt();
|
root["H23"]["v"] = VeDirect.veFrame.H23;
|
||||||
root["H23"]["u"] = "W";
|
root["H23"]["u"] = "W";
|
||||||
|
|
||||||
if (VeDirect.getLastUpdate() > _newestVedirectTimestamp) {
|
if (VeDirect.getLastUpdate() > _newestVedirectTimestamp) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user