Integration of Victron SmartShunt via VE.Direct (#452)
* Move Mppt logic to subclass * Added Definitions for Shunts and restructering * First integration of SmartShunt data into Web Interface * Code cleanup * VE.Direct: whitespace cleanup * VE.Direct: manage HardwareSerial in unique_ptr * VE.Direct: _efficiency is only needed by MPPT * VE.Direct: keep as many members private as possible * VE.Direct: use int8_t for pins (as before) * VictronSmartShunt: _verboseLogging is not used * VE.Direct: OR (off reason) is MPPT specific it also applies to Phoenix inverters and Smart BuckBoost, but since there is no support for those, the code is moved to the MPPT controller. * Added Shunt alarms to liveview Changed from double to int for several readings * Update build.yml to allow manual builds --------- Co-authored-by: Philipp Sandhaus <philipp.sandhaus@cewe.de> Co-authored-by: Bernhard Kirchen <schlimmchen@posteo.net>
This commit is contained in:
parent
160d3f23bd
commit
7142921021
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@ -14,6 +14,7 @@ on:
|
|||||||
paths-ignore:
|
paths-ignore:
|
||||||
- docs/**
|
- docs/**
|
||||||
- '**/*.md'
|
- '**/*.md'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
get_default_envs:
|
get_default_envs:
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
#include "AsyncJson.h"
|
#include "AsyncJson.h"
|
||||||
#include "Arduino.h"
|
#include "Arduino.h"
|
||||||
#include "JkBmsDataPoints.h"
|
#include "JkBmsDataPoints.h"
|
||||||
|
#include "VeDirectShuntController.h"
|
||||||
|
|
||||||
// mandatory interface for all kinds of batteries
|
// mandatory interface for all kinds of batteries
|
||||||
class BatteryStats {
|
class BatteryStats {
|
||||||
@ -98,3 +99,27 @@ class JkBmsBatteryStats : public BatteryStats {
|
|||||||
mutable uint32_t _lastMqttPublish = 0;
|
mutable uint32_t _lastMqttPublish = 0;
|
||||||
mutable uint32_t _lastFullMqttPublish = 0;
|
mutable uint32_t _lastFullMqttPublish = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class VictronSmartShuntStats : public BatteryStats {
|
||||||
|
public:
|
||||||
|
void getLiveViewData(JsonVariant& root) const final;
|
||||||
|
void mqttPublish() const final;
|
||||||
|
|
||||||
|
void updateFrom(VeDirectShuntController::veShuntStruct const& shuntData);
|
||||||
|
|
||||||
|
private:
|
||||||
|
float _voltage;
|
||||||
|
float _current;
|
||||||
|
float _temperature;
|
||||||
|
uint8_t _chargeCycles;
|
||||||
|
uint32_t _timeToGo;
|
||||||
|
float _chargedEnergy;
|
||||||
|
float _dischargedEnergy;
|
||||||
|
String _modelName;
|
||||||
|
|
||||||
|
bool _alarmLowVoltage;
|
||||||
|
bool _alarmHighVoltage;
|
||||||
|
bool _alarmLowSOC;
|
||||||
|
bool _alarmLowTemperature;
|
||||||
|
bool _alarmHighTemperature;
|
||||||
|
};
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "VeDirectFrameHandler.h"
|
#include "VeDirectMpptController.h"
|
||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
@ -18,7 +18,8 @@ public:
|
|||||||
void init();
|
void init();
|
||||||
void loop();
|
void loop();
|
||||||
private:
|
private:
|
||||||
veStruct _kvFrame{};
|
|
||||||
|
VeDirectMpptController::veMpptStruct _kvFrame{};
|
||||||
|
|
||||||
// point of time in millis() when updated values will be published
|
// point of time in millis() when updated values will be published
|
||||||
uint32_t _nextPublishUpdatesOnly = 0;
|
uint32_t _nextPublishUpdatesOnly = 0;
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include "VeDirectFrameHandler.h"
|
#include "VeDirectMpptController.h"
|
||||||
|
|
||||||
class MqttHandleVedirectHassClass {
|
class MqttHandleVedirectHassClass {
|
||||||
public:
|
public:
|
||||||
|
|||||||
16
include/VictronSmartShunt.h
Normal file
16
include/VictronSmartShunt.h
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Battery.h"
|
||||||
|
|
||||||
|
class VictronSmartShunt : public BatteryProvider {
|
||||||
|
public:
|
||||||
|
bool init(bool verboseLogging) final;
|
||||||
|
void deinit() final { }
|
||||||
|
void loop() final;
|
||||||
|
std::shared_ptr<BatteryStats> getStats() const final { return _stats; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<VictronSmartShuntStats> _stats =
|
||||||
|
std::make_shared<VictronSmartShuntStats>();
|
||||||
|
};
|
||||||
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
#include "ArduinoJson.h"
|
#include "ArduinoJson.h"
|
||||||
#include <ESPAsyncWebServer.h>
|
#include <ESPAsyncWebServer.h>
|
||||||
#include <VeDirectFrameHandler.h>
|
#include <VeDirectMpptController.h>
|
||||||
|
|
||||||
class WebApiWsVedirectLiveClass {
|
class WebApiWsVedirectLiveClass {
|
||||||
public:
|
public:
|
||||||
|
|||||||
@ -49,9 +49,7 @@ enum States {
|
|||||||
RECORD_HEX = 6
|
RECORD_HEX = 6
|
||||||
};
|
};
|
||||||
|
|
||||||
HardwareSerial VedirectSerial(1);
|
|
||||||
|
|
||||||
VeDirectFrameHandler VeDirect;
|
|
||||||
|
|
||||||
class Silent : public Print {
|
class Silent : public Print {
|
||||||
public:
|
public:
|
||||||
@ -62,16 +60,15 @@ static Silent MessageOutputDummy;
|
|||||||
|
|
||||||
VeDirectFrameHandler::VeDirectFrameHandler() :
|
VeDirectFrameHandler::VeDirectFrameHandler() :
|
||||||
_msgOut(&MessageOutputDummy),
|
_msgOut(&MessageOutputDummy),
|
||||||
|
_lastUpdate(0),
|
||||||
_state(IDLE),
|
_state(IDLE),
|
||||||
_checksum(0),
|
_checksum(0),
|
||||||
_textPointer(0),
|
_textPointer(0),
|
||||||
_hexSize(0),
|
_hexSize(0),
|
||||||
_name(""),
|
_name(""),
|
||||||
_value(""),
|
_value(""),
|
||||||
_tmpFrame(),
|
|
||||||
_debugIn(0),
|
_debugIn(0),
|
||||||
_lastByteMillis(0),
|
_lastByteMillis(0)
|
||||||
_lastUpdate(0)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,10 +78,11 @@ void VeDirectFrameHandler::setVerboseLogging(bool verboseLogging)
|
|||||||
if (!_verboseLogging) { _debugIn = 0; }
|
if (!_verboseLogging) { _debugIn = 0; }
|
||||||
}
|
}
|
||||||
|
|
||||||
void VeDirectFrameHandler::init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging)
|
void VeDirectFrameHandler::init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging, uint16_t hwSerialPort)
|
||||||
{
|
{
|
||||||
VedirectSerial.begin(19200, SERIAL_8N1, rx, tx);
|
_vedirectSerial = std::make_unique<HardwareSerial>(hwSerialPort);
|
||||||
VedirectSerial.flush();
|
_vedirectSerial->begin(19200, SERIAL_8N1, rx, tx);
|
||||||
|
_vedirectSerial->flush();
|
||||||
_msgOut = msgOut;
|
_msgOut = msgOut;
|
||||||
setVerboseLogging(verboseLogging);
|
setVerboseLogging(verboseLogging);
|
||||||
}
|
}
|
||||||
@ -103,8 +101,8 @@ void VeDirectFrameHandler::dumpDebugBuffer() {
|
|||||||
|
|
||||||
void VeDirectFrameHandler::loop()
|
void VeDirectFrameHandler::loop()
|
||||||
{
|
{
|
||||||
while ( VedirectSerial.available()) {
|
while ( _vedirectSerial->available()) {
|
||||||
rxData(VedirectSerial.read());
|
rxData(_vedirectSerial->read());
|
||||||
_lastByteMillis = millis();
|
_lastByteMillis = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +114,6 @@ void VeDirectFrameHandler::loop()
|
|||||||
if (_verboseLogging) { dumpDebugBuffer(); }
|
if (_verboseLogging) { dumpDebugBuffer(); }
|
||||||
_checksum = 0;
|
_checksum = 0;
|
||||||
_state = IDLE;
|
_state = IDLE;
|
||||||
_tmpFrame = { };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,93 +224,25 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte)
|
|||||||
* textRxEvent
|
* textRxEvent
|
||||||
* This function is called every time a new name/value is successfully parsed. It writes the values to the temporary buffer.
|
* 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) {
|
void VeDirectFrameHandler::textRxEvent(char * name, char * value, veStruct& frame) {
|
||||||
if (strcmp(name, "PID") == 0) {
|
if (strcmp(name, "PID") == 0) {
|
||||||
_tmpFrame.PID = strtol(value, nullptr, 0);
|
frame.PID = strtol(value, nullptr, 0);
|
||||||
}
|
}
|
||||||
else if (strcmp(name, "SER") == 0) {
|
else if (strcmp(name, "SER") == 0) {
|
||||||
strcpy(_tmpFrame.SER, value);
|
strcpy(frame.SER, value);
|
||||||
}
|
}
|
||||||
else if (strcmp(name, "FW") == 0) {
|
else if (strcmp(name, "FW") == 0) {
|
||||||
strcpy(_tmpFrame.FW, value);
|
strcpy(frame.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) {
|
else if (strcmp(name, "V") == 0) {
|
||||||
_tmpFrame.V = round(atof(value) / 10.0) / 100.0;
|
frame.V = round(atof(value) / 10.0) / 100.0;
|
||||||
}
|
}
|
||||||
else if (strcmp(name, "I") == 0) {
|
else if (strcmp(name, "I") == 0) {
|
||||||
_tmpFrame.I = round(atof(value) / 10.0) / 100.0;
|
frame.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.
|
|
||||||
* If the name exists in the public buffer, the new value is copied to the public buffer. If not, a new name/value entry
|
|
||||||
* is created in the public buffer.
|
|
||||||
*/
|
|
||||||
void VeDirectFrameHandler::frameEndEvent(bool valid) {
|
|
||||||
if ( valid ) {
|
|
||||||
_tmpFrame.P = _tmpFrame.V * _tmpFrame.I;
|
|
||||||
|
|
||||||
_tmpFrame.IPV = 0;
|
|
||||||
if ( _tmpFrame.VPV > 0) {
|
|
||||||
_tmpFrame.IPV = _tmpFrame.PPV / _tmpFrame.VPV;
|
|
||||||
}
|
|
||||||
|
|
||||||
_tmpFrame.E = 0;
|
|
||||||
if ( _tmpFrame.PPV > 0) {
|
|
||||||
_efficiency.addNumber(static_cast<double>(_tmpFrame.P * 100) / _tmpFrame.PPV);
|
|
||||||
_tmpFrame.E = _efficiency.getAverage();
|
|
||||||
}
|
|
||||||
|
|
||||||
veFrame = _tmpFrame;
|
|
||||||
_lastUpdate = millis();
|
|
||||||
}
|
|
||||||
_tmpFrame = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* hexRxEvent
|
* hexRxEvent
|
||||||
@ -340,11 +269,11 @@ int VeDirectFrameHandler::hexRxEvent(uint8_t inbyte) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VeDirectFrameHandler::isDataValid() {
|
bool VeDirectFrameHandler::isDataValid(veStruct frame) {
|
||||||
if (_lastUpdate == 0) {
|
if (_lastUpdate == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (strlen(veFrame.SER) == 0) {
|
if (strlen(frame.SER) == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -574,53 +503,34 @@ String VeDirectFrameHandler::getPidAsString(uint16_t pid)
|
|||||||
case 0XA116:
|
case 0XA116:
|
||||||
strPID = "SmartSolar MPPT VE.Can 250|85 rev2";
|
strPID = "SmartSolar MPPT VE.Can 250|85 rev2";
|
||||||
break;
|
break;
|
||||||
|
case 0xA381:
|
||||||
|
strPID = "BMV-712 Smart";
|
||||||
|
break;
|
||||||
|
case 0xA382:
|
||||||
|
strPID = "BMV-710H Smart";
|
||||||
|
break;
|
||||||
|
case 0xA383:
|
||||||
|
strPID = "BMV-712 Smart Rev2";
|
||||||
|
break;
|
||||||
|
case 0xA389:
|
||||||
|
strPID = "SmartShunt 500A/50mV";
|
||||||
|
break;
|
||||||
|
case 0xA38A:
|
||||||
|
strPID = "SmartShunt 1000A/50mV";
|
||||||
|
break;
|
||||||
|
case 0xA38B:
|
||||||
|
strPID = "SmartShunt 2000A/50mV";
|
||||||
|
break;
|
||||||
|
case 0xA3F0:
|
||||||
|
strPID = "SmartShunt 2000A/50mV" ;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
strPID = pid;
|
strPID = pid;
|
||||||
}
|
}
|
||||||
return strPID;
|
return strPID;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* getCsAsString
|
|
||||||
* This function returns the state of operations (CS) as readable text.
|
|
||||||
*/
|
|
||||||
String VeDirectFrameHandler::getCsAsString(uint8_t cs)
|
|
||||||
{
|
|
||||||
String strCS ="";
|
|
||||||
|
|
||||||
switch(cs) {
|
|
||||||
case 0:
|
|
||||||
strCS = "OFF";
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
strCS = "Fault";
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
strCS = "Bulk";
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
strCS = "Absorbtion";
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
strCS = "Float";
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
strCS = "Equalize (manual)";
|
|
||||||
break;
|
|
||||||
case 245:
|
|
||||||
strCS = "Starting-up";
|
|
||||||
break;
|
|
||||||
case 247:
|
|
||||||
strCS = "Auto equalize / Recondition";
|
|
||||||
break;
|
|
||||||
case 252:
|
|
||||||
strCS = "External Control";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
strCS = cs;
|
|
||||||
}
|
|
||||||
return strCS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* getErrAsString
|
* getErrAsString
|
||||||
@ -696,72 +606,3 @@ String VeDirectFrameHandler::getErrAsString(uint8_t err)
|
|||||||
}
|
}
|
||||||
return strERR;
|
return strERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* getOrAsString
|
|
||||||
* This function returns the off reason (OR) as readable text.
|
|
||||||
*/
|
|
||||||
String VeDirectFrameHandler::getOrAsString(uint32_t offReason)
|
|
||||||
{
|
|
||||||
String strOR ="";
|
|
||||||
|
|
||||||
switch(offReason) {
|
|
||||||
case 0x00000000:
|
|
||||||
strOR = "Not off";
|
|
||||||
break;
|
|
||||||
case 0x00000001:
|
|
||||||
strOR = "No input power";
|
|
||||||
break;
|
|
||||||
case 0x00000002:
|
|
||||||
strOR = "Switched off (power switch)";
|
|
||||||
break;
|
|
||||||
case 0x00000004:
|
|
||||||
strOR = "Switched off (device moderegister)";
|
|
||||||
break;
|
|
||||||
case 0x00000008:
|
|
||||||
strOR = "Remote input";
|
|
||||||
break;
|
|
||||||
case 0x00000010:
|
|
||||||
strOR = "Protection active";
|
|
||||||
break;
|
|
||||||
case 0x00000020:
|
|
||||||
strOR = "Paygo";
|
|
||||||
break;
|
|
||||||
case 0x00000040:
|
|
||||||
strOR = "BMS";
|
|
||||||
break;
|
|
||||||
case 0x00000080:
|
|
||||||
strOR = "Engine shutdown detection";
|
|
||||||
break;
|
|
||||||
case 0x00000100:
|
|
||||||
strOR = "Analysing input voltage";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
strOR = offReason;
|
|
||||||
}
|
|
||||||
return strOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* getMpptAsString
|
|
||||||
* This function returns the state of MPPT (MPPT) as readable text.
|
|
||||||
*/
|
|
||||||
String VeDirectFrameHandler::getMpptAsString(uint8_t mppt)
|
|
||||||
{
|
|
||||||
String strMPPT ="";
|
|
||||||
|
|
||||||
switch(mppt) {
|
|
||||||
case 0:
|
|
||||||
strMPPT = "OFF";
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
strMPPT = "Voltage or current limited";
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
strMPPT = "MPP Tracker active";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
strMPPT = mppt;
|
|
||||||
}
|
|
||||||
return strMPPT;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -13,94 +13,48 @@
|
|||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#define VE_MAX_VALUE_LEN 33 // VE.Direct Protocol: max value size is 33 including /0
|
#define VE_MAX_VALUE_LEN 33 // VE.Direct Protocol: max value size is 33 including /0
|
||||||
#define VE_MAX_HEX_LEN 100 // Maximum size of hex frame - max payload 34 byte (=68 char) + safe buffer
|
#define VE_MAX_HEX_LEN 100 // Maximum size of hex frame - max payload 34 byte (=68 char) + safe buffer
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint16_t PID; // product id
|
uint16_t PID = 0; // product id
|
||||||
char SER[VE_MAX_VALUE_LEN]; // serial number
|
char SER[VE_MAX_VALUE_LEN]; // serial number
|
||||||
char FW[VE_MAX_VALUE_LEN]; // firmware release 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)
|
int32_t P = 0; // battery output power in W (calculated)
|
||||||
uint8_t CS; // current state of operation e. g. OFF or Bulk
|
double V = 0; // battery voltage in V
|
||||||
uint8_t ERR; // error code
|
double I = 0; // battery current in A
|
||||||
uint32_t OR; // off reason
|
double E = 0; // efficiency in percent (calculated, moving average)
|
||||||
uint8_t MPPT; // state of MPP tracker
|
|
||||||
uint32_t HSDS; // day sequence number 1...365
|
|
||||||
int32_t P; // battery output power in W (calculated)
|
|
||||||
double V; // battery voltage in V
|
|
||||||
double I; // battery current in A
|
|
||||||
double E; // efficiency in percent (calculated, moving average)
|
|
||||||
int32_t PPV; // panel power in W
|
|
||||||
double VPV; // panel voltage in V
|
|
||||||
double IPV; // panel current in A (calculated)
|
|
||||||
double H19; // yield total kWh
|
|
||||||
double H20; // yield today kWh
|
|
||||||
int32_t H21; // maximum power today W
|
|
||||||
double H22; // yield yesterday kWh
|
|
||||||
int32_t H23; // maximum power yesterday W
|
|
||||||
} veStruct;
|
} veStruct;
|
||||||
|
|
||||||
template<typename T, size_t WINDOW_SIZE>
|
|
||||||
class MovingAverage {
|
|
||||||
public:
|
|
||||||
MovingAverage()
|
|
||||||
: _sum(0)
|
|
||||||
, _index(0)
|
|
||||||
, _count(0) { }
|
|
||||||
|
|
||||||
void addNumber(T num) {
|
|
||||||
if (_count < WINDOW_SIZE) {
|
|
||||||
_count++;
|
|
||||||
} else {
|
|
||||||
_sum -= _window[_index];
|
|
||||||
}
|
|
||||||
|
|
||||||
_window[_index] = num;
|
|
||||||
_sum += num;
|
|
||||||
_index = (_index + 1) % WINDOW_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
double getAverage() const {
|
|
||||||
if (_count == 0) { return 0.0; }
|
|
||||||
return static_cast<double>(_sum) / _count;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::array<T, WINDOW_SIZE> _window;
|
|
||||||
T _sum;
|
|
||||||
size_t _index;
|
|
||||||
size_t _count;
|
|
||||||
};
|
|
||||||
|
|
||||||
class VeDirectFrameHandler {
|
class VeDirectFrameHandler {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
VeDirectFrameHandler();
|
VeDirectFrameHandler();
|
||||||
void setVerboseLogging(bool verboseLogging);
|
void setVerboseLogging(bool verboseLogging);
|
||||||
void init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging);
|
virtual void init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging, uint16_t hwSerialPort);
|
||||||
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(veStruct frame); // return true if data valid and not outdated
|
||||||
String getPidAsString(uint16_t pid); // product id 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 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
|
|
||||||
|
|
||||||
veStruct veFrame{}; // public struct for received name and value pairs
|
protected:
|
||||||
|
void textRxEvent(char *, char *, veStruct& );
|
||||||
|
|
||||||
|
bool _verboseLogging;
|
||||||
|
Print* _msgOut;
|
||||||
|
uint32_t _lastUpdate;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setLastUpdate(); // set timestampt after successful frame read
|
void setLastUpdate(); // set timestampt after successful frame read
|
||||||
void dumpDebugBuffer();
|
void dumpDebugBuffer();
|
||||||
void rxData(uint8_t inbyte); // byte of serial data
|
void rxData(uint8_t inbyte); // byte of serial data
|
||||||
void textRxEvent(char *, char *);
|
virtual void textRxEvent(char *, char *) = 0;
|
||||||
void frameEndEvent(bool); // copy temp struct to public struct
|
virtual void frameEndEvent(bool) = 0; // copy temp struct to public struct
|
||||||
int hexRxEvent(uint8_t);
|
int hexRxEvent(uint8_t);
|
||||||
|
|
||||||
Print* _msgOut;
|
std::unique_ptr<HardwareSerial> _vedirectSerial;
|
||||||
bool _verboseLogging;
|
|
||||||
int _state; // current state
|
int _state; // current state
|
||||||
int _prevState; // previous state
|
int _prevState; // previous state
|
||||||
uint8_t _checksum; // checksum value
|
uint8_t _checksum; // checksum value
|
||||||
@ -108,13 +62,7 @@ private:
|
|||||||
int _hexSize; // length of hex buffer
|
int _hexSize; // length of hex buffer
|
||||||
char _name[VE_MAX_VALUE_LEN]; // buffer for the field name
|
char _name[VE_MAX_VALUE_LEN]; // buffer for the field name
|
||||||
char _value[VE_MAX_VALUE_LEN]; // buffer for the field value
|
char _value[VE_MAX_VALUE_LEN]; // buffer for the field value
|
||||||
veStruct _tmpFrame{}; // private struct for received name and value pairs
|
|
||||||
MovingAverage<double, 5> _efficiency;
|
|
||||||
std::array<uint8_t, 512> _debugBuffer;
|
std::array<uint8_t, 512> _debugBuffer;
|
||||||
unsigned _debugIn;
|
unsigned _debugIn;
|
||||||
uint32_t _lastByteMillis;
|
uint32_t _lastByteMillis;
|
||||||
uint32_t _lastUpdate;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern VeDirectFrameHandler VeDirect;
|
|
||||||
|
|
||||||
|
|||||||
203
lib/VeDirectFrameHandler/VeDirectMpptController.cpp
Normal file
203
lib/VeDirectFrameHandler/VeDirectMpptController.cpp
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include "VeDirectMpptController.h"
|
||||||
|
|
||||||
|
VeDirectMpptController VeDirectMppt;
|
||||||
|
|
||||||
|
VeDirectMpptController::VeDirectMpptController()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void VeDirectMpptController::init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging)
|
||||||
|
{
|
||||||
|
VeDirectFrameHandler::init(rx, tx, msgOut, verboseLogging, 1);
|
||||||
|
if (_verboseLogging) { _msgOut->println("Finished init MPPTController"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VeDirectMpptController::isDataValid() {
|
||||||
|
return VeDirectFrameHandler::isDataValid(veFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VeDirectMpptController::textRxEvent(char * name, char * value) {
|
||||||
|
if (_verboseLogging) { _msgOut->printf("[Victron MPPT] Received Text Event %s: Value: %s\r\n", name, value ); }
|
||||||
|
VeDirectFrameHandler::textRxEvent(name, value, _tmpFrame);
|
||||||
|
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, "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.
|
||||||
|
* If the name exists in the public buffer, the new value is copied to the public buffer. If not, a new name/value entry
|
||||||
|
* is created in the public buffer.
|
||||||
|
*/
|
||||||
|
void VeDirectMpptController::frameEndEvent(bool valid) {
|
||||||
|
if (valid) {
|
||||||
|
_tmpFrame.P = _tmpFrame.V * _tmpFrame.I;
|
||||||
|
|
||||||
|
_tmpFrame.IPV = 0;
|
||||||
|
if (_tmpFrame.VPV > 0) {
|
||||||
|
_tmpFrame.IPV = _tmpFrame.PPV / _tmpFrame.VPV;
|
||||||
|
}
|
||||||
|
|
||||||
|
_tmpFrame.E = 0;
|
||||||
|
if ( _tmpFrame.PPV > 0) {
|
||||||
|
_efficiency.addNumber(static_cast<double>(_tmpFrame.P * 100) / _tmpFrame.PPV);
|
||||||
|
_tmpFrame.E = _efficiency.getAverage();
|
||||||
|
}
|
||||||
|
|
||||||
|
veFrame = _tmpFrame;
|
||||||
|
_tmpFrame = {};
|
||||||
|
_lastUpdate = millis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getCsAsString
|
||||||
|
* This function returns the state of operations (CS) as readable text.
|
||||||
|
*/
|
||||||
|
String VeDirectMpptController::getCsAsString(uint8_t cs)
|
||||||
|
{
|
||||||
|
String strCS ="";
|
||||||
|
|
||||||
|
switch(cs) {
|
||||||
|
case 0:
|
||||||
|
strCS = "OFF";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
strCS = "Fault";
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
strCS = "Bulk";
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
strCS = "Absorbtion";
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
strCS = "Float";
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
strCS = "Equalize (manual)";
|
||||||
|
break;
|
||||||
|
case 245:
|
||||||
|
strCS = "Starting-up";
|
||||||
|
break;
|
||||||
|
case 247:
|
||||||
|
strCS = "Auto equalize / Recondition";
|
||||||
|
break;
|
||||||
|
case 252:
|
||||||
|
strCS = "External Control";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
strCS = cs;
|
||||||
|
}
|
||||||
|
return strCS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getMpptAsString
|
||||||
|
* This function returns the state of MPPT (MPPT) as readable text.
|
||||||
|
*/
|
||||||
|
String VeDirectMpptController::getMpptAsString(uint8_t mppt)
|
||||||
|
{
|
||||||
|
String strMPPT ="";
|
||||||
|
|
||||||
|
switch(mppt) {
|
||||||
|
case 0:
|
||||||
|
strMPPT = "OFF";
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
strMPPT = "Voltage or current limited";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
strMPPT = "MPP Tracker active";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
strMPPT = mppt;
|
||||||
|
}
|
||||||
|
return strMPPT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getOrAsString
|
||||||
|
* This function returns the off reason (OR) as readable text.
|
||||||
|
*/
|
||||||
|
String VeDirectMpptController::getOrAsString(uint32_t offReason)
|
||||||
|
{
|
||||||
|
String strOR ="";
|
||||||
|
|
||||||
|
switch(offReason) {
|
||||||
|
case 0x00000000:
|
||||||
|
strOR = "Not off";
|
||||||
|
break;
|
||||||
|
case 0x00000001:
|
||||||
|
strOR = "No input power";
|
||||||
|
break;
|
||||||
|
case 0x00000002:
|
||||||
|
strOR = "Switched off (power switch)";
|
||||||
|
break;
|
||||||
|
case 0x00000004:
|
||||||
|
strOR = "Switched off (device moderegister)";
|
||||||
|
break;
|
||||||
|
case 0x00000008:
|
||||||
|
strOR = "Remote input";
|
||||||
|
break;
|
||||||
|
case 0x00000010:
|
||||||
|
strOR = "Protection active";
|
||||||
|
break;
|
||||||
|
case 0x00000020:
|
||||||
|
strOR = "Paygo";
|
||||||
|
break;
|
||||||
|
case 0x00000040:
|
||||||
|
strOR = "BMS";
|
||||||
|
break;
|
||||||
|
case 0x00000080:
|
||||||
|
strOR = "Engine shutdown detection";
|
||||||
|
break;
|
||||||
|
case 0x00000100:
|
||||||
|
strOR = "Analysing input voltage";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
strOR = offReason;
|
||||||
|
}
|
||||||
|
return strOR;
|
||||||
|
}
|
||||||
74
lib/VeDirectFrameHandler/VeDirectMpptController.h
Normal file
74
lib/VeDirectFrameHandler/VeDirectMpptController.h
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "VeDirectFrameHandler.h"
|
||||||
|
|
||||||
|
template<typename T, size_t WINDOW_SIZE>
|
||||||
|
class MovingAverage {
|
||||||
|
public:
|
||||||
|
MovingAverage()
|
||||||
|
: _sum(0)
|
||||||
|
, _index(0)
|
||||||
|
, _count(0) { }
|
||||||
|
|
||||||
|
void addNumber(T num) {
|
||||||
|
if (_count < WINDOW_SIZE) {
|
||||||
|
_count++;
|
||||||
|
} else {
|
||||||
|
_sum -= _window[_index];
|
||||||
|
}
|
||||||
|
|
||||||
|
_window[_index] = num;
|
||||||
|
_sum += num;
|
||||||
|
_index = (_index + 1) % WINDOW_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getAverage() const {
|
||||||
|
if (_count == 0) { return 0.0; }
|
||||||
|
return static_cast<double>(_sum) / _count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::array<T, WINDOW_SIZE> _window;
|
||||||
|
T _sum;
|
||||||
|
size_t _index;
|
||||||
|
size_t _count;
|
||||||
|
};
|
||||||
|
|
||||||
|
class VeDirectMpptController : public VeDirectFrameHandler {
|
||||||
|
public:
|
||||||
|
VeDirectMpptController();
|
||||||
|
|
||||||
|
void init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging);
|
||||||
|
String getMpptAsString(uint8_t mppt); // state of mppt as string
|
||||||
|
String getCsAsString(uint8_t cs); // current state as string
|
||||||
|
String getOrAsString(uint32_t offReason); // off reason as string
|
||||||
|
bool isDataValid(); // return true if data valid and not outdated
|
||||||
|
|
||||||
|
struct veMpptStruct : veStruct {
|
||||||
|
uint8_t MPPT; // state of MPP tracker
|
||||||
|
int32_t PPV; // panel power in W
|
||||||
|
double VPV; // panel voltage in V
|
||||||
|
double IPV; // panel current in A (calculated)
|
||||||
|
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
|
||||||
|
uint32_t HSDS; // day sequence number 1...365
|
||||||
|
double H19; // yield total kWh
|
||||||
|
double H20; // yield today kWh
|
||||||
|
int32_t H21; // maximum power today W
|
||||||
|
double H22; // yield yesterday kWh
|
||||||
|
int32_t H23; // maximum power yesterday W
|
||||||
|
};
|
||||||
|
|
||||||
|
veMpptStruct veFrame{};
|
||||||
|
|
||||||
|
private:
|
||||||
|
void textRxEvent(char * name, char * value) final;
|
||||||
|
void frameEndEvent(bool) final; // copy temp struct to public struct
|
||||||
|
veMpptStruct _tmpFrame{}; // private struct for received name and value pairs
|
||||||
|
MovingAverage<double, 5> _efficiency;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern VeDirectMpptController VeDirectMppt;
|
||||||
113
lib/VeDirectFrameHandler/VeDirectShuntController.cpp
Normal file
113
lib/VeDirectFrameHandler/VeDirectShuntController.cpp
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include "VeDirectShuntController.h"
|
||||||
|
|
||||||
|
VeDirectShuntController VeDirectShunt;
|
||||||
|
|
||||||
|
VeDirectShuntController::VeDirectShuntController()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void VeDirectShuntController::init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging)
|
||||||
|
{
|
||||||
|
VeDirectFrameHandler::init(rx, tx, msgOut, verboseLogging, 2);
|
||||||
|
if (_verboseLogging) {
|
||||||
|
_msgOut->println("Finished init ShuntController");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VeDirectShuntController::textRxEvent(char* name, char* value)
|
||||||
|
{
|
||||||
|
VeDirectFrameHandler::textRxEvent(name, value, _tmpFrame);
|
||||||
|
if (_verboseLogging) {
|
||||||
|
_msgOut->printf("[Victron SmartShunt] Received Text Event %s: Value: %s\r\n", name, value );
|
||||||
|
}
|
||||||
|
if (strcmp(name, "T") == 0) {
|
||||||
|
_tmpFrame.T = atoi(value);
|
||||||
|
}
|
||||||
|
else if (strcmp(name, "P") == 0) {
|
||||||
|
_tmpFrame.P = atoi(value);
|
||||||
|
}
|
||||||
|
else if (strcmp(name, "CE") == 0) {
|
||||||
|
_tmpFrame.CE = atoi(value);
|
||||||
|
}
|
||||||
|
else if (strcmp(name, "SOC") == 0) {
|
||||||
|
_tmpFrame.SOC = atoi(value);
|
||||||
|
}
|
||||||
|
else if (strcmp(name, "TTG") == 0) {
|
||||||
|
_tmpFrame.TTG = atoi(value);
|
||||||
|
}
|
||||||
|
else if (strcmp(name, "ALARM") == 0) {
|
||||||
|
_tmpFrame.ALARM = (strcmp(value, "ON") == 0);
|
||||||
|
}
|
||||||
|
else if (strcmp(name, "H1") == 0) {
|
||||||
|
_tmpFrame.H1 = atoi(value);
|
||||||
|
}
|
||||||
|
else if (strcmp(name, "H2") == 0) {
|
||||||
|
_tmpFrame.H2 = atoi(value);
|
||||||
|
}
|
||||||
|
else if (strcmp(name, "H3") == 0) {
|
||||||
|
_tmpFrame.H3 = atoi(value);
|
||||||
|
}
|
||||||
|
else if (strcmp(name, "H4") == 0) {
|
||||||
|
_tmpFrame.H4 = atoi(value);
|
||||||
|
}
|
||||||
|
else if (strcmp(name, "H5") == 0) {
|
||||||
|
_tmpFrame.H5 = atoi(value);
|
||||||
|
}
|
||||||
|
else if (strcmp(name, "H6") == 0) {
|
||||||
|
_tmpFrame.H6 = atoi(value);
|
||||||
|
}
|
||||||
|
else if (strcmp(name, "H7") == 0) {
|
||||||
|
_tmpFrame.H7 = atoi(value);
|
||||||
|
}
|
||||||
|
else if (strcmp(name, "H8") == 0) {
|
||||||
|
_tmpFrame.H8 = atoi(value);
|
||||||
|
}
|
||||||
|
else if (strcmp(name, "H9") == 0) {
|
||||||
|
_tmpFrame.H9 = atoi(value);
|
||||||
|
}
|
||||||
|
else if (strcmp(name, "H10") == 0) {
|
||||||
|
_tmpFrame.H10 = atoi(value);
|
||||||
|
}
|
||||||
|
else if (strcmp(name, "H11") == 0) {
|
||||||
|
_tmpFrame.H11 = atoi(value);
|
||||||
|
}
|
||||||
|
else if (strcmp(name, "H12") == 0) {
|
||||||
|
_tmpFrame.H12 = atoi(value);
|
||||||
|
}
|
||||||
|
else if (strcmp(name, "H13") == 0) {
|
||||||
|
_tmpFrame.H13 = atoi(value);
|
||||||
|
}
|
||||||
|
else if (strcmp(name, "H14") == 0) {
|
||||||
|
_tmpFrame.H14 = atoi(value);
|
||||||
|
}
|
||||||
|
else if (strcmp(name, "H15") == 0) {
|
||||||
|
_tmpFrame.H15 = atoi(value);
|
||||||
|
}
|
||||||
|
else if (strcmp(name, "H16") == 0) {
|
||||||
|
_tmpFrame.H16 = atoi(value);
|
||||||
|
}
|
||||||
|
else if (strcmp(name, "H17") == 0) {
|
||||||
|
_tmpFrame.H17 = atoi(value);
|
||||||
|
}
|
||||||
|
else if (strcmp(name, "H18") == 0) {
|
||||||
|
_tmpFrame.H18 = 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.
|
||||||
|
* If the name exists in the public buffer, the new value is copied to the public buffer. If not, a new name/value entry
|
||||||
|
* is created in the public buffer.
|
||||||
|
*/
|
||||||
|
void VeDirectShuntController::frameEndEvent(bool valid) {
|
||||||
|
// other than in the MPPT controller, the SmartShunt seems to split all data
|
||||||
|
// into two seperate messagesas. Thus we update veFrame only every second message
|
||||||
|
// after a value for PID has been received
|
||||||
|
if (valid && _tmpFrame.PID != 0) {
|
||||||
|
veFrame = _tmpFrame;
|
||||||
|
_tmpFrame = {};
|
||||||
|
_lastUpdate = millis();
|
||||||
|
}
|
||||||
|
}
|
||||||
48
lib/VeDirectFrameHandler/VeDirectShuntController.h
Normal file
48
lib/VeDirectFrameHandler/VeDirectShuntController.h
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "VeDirectFrameHandler.h"
|
||||||
|
|
||||||
|
class VeDirectShuntController : public VeDirectFrameHandler {
|
||||||
|
public:
|
||||||
|
VeDirectShuntController();
|
||||||
|
|
||||||
|
void init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging);
|
||||||
|
|
||||||
|
struct veShuntStruct : veStruct {
|
||||||
|
int32_t T; // Battery temperature
|
||||||
|
int32_t P; // Instantaneous power
|
||||||
|
int32_t CE; // Consumed Amp Hours
|
||||||
|
int32_t SOC; // State-of-charge
|
||||||
|
uint32_t TTG; // Time-to-go
|
||||||
|
bool ALARM; // Alarm condition active
|
||||||
|
uint32_t AR; // Alarm Reason
|
||||||
|
int32_t H1; // Depth of the deepest discharge
|
||||||
|
int32_t H2; // Depth of the last discharge
|
||||||
|
int32_t H3; // Depth of the average discharge
|
||||||
|
int32_t H4; // Number of charge cycles
|
||||||
|
int32_t H5; // Number of full discharges
|
||||||
|
int32_t H6; // Cumulative Amp Hours drawn
|
||||||
|
int32_t H7; // Minimum main (battery) voltage
|
||||||
|
int32_t H8; // Maximum main (battery) voltage
|
||||||
|
int32_t H9; // Number of seconds since last full charge
|
||||||
|
int32_t H10; // Number of automatic synchronizations
|
||||||
|
int32_t H11; // Number of low main voltage alarms
|
||||||
|
int32_t H12; // Number of high main voltage alarms
|
||||||
|
int32_t H13; // Number of low auxiliary voltage alarms
|
||||||
|
int32_t H14; // Number of high auxiliary voltage alarms
|
||||||
|
int32_t H15; // Minimum auxiliary (battery) voltage
|
||||||
|
int32_t H16; // Maximum auxiliary (battery) voltage
|
||||||
|
int32_t H17; // Amount of discharged energy
|
||||||
|
int32_t H18; // Amount of charged energy
|
||||||
|
};
|
||||||
|
|
||||||
|
veShuntStruct veFrame{};
|
||||||
|
|
||||||
|
private:
|
||||||
|
void textRxEvent(char * name, char * value) final;
|
||||||
|
void frameEndEvent(bool) final; // copy temp struct to public struct
|
||||||
|
veShuntStruct _tmpFrame{}; // private struct for received name and value pairs
|
||||||
|
};
|
||||||
|
|
||||||
|
extern VeDirectShuntController VeDirectShunt;
|
||||||
@ -4,6 +4,7 @@
|
|||||||
#include "MqttSettings.h"
|
#include "MqttSettings.h"
|
||||||
#include "PylontechCanReceiver.h"
|
#include "PylontechCanReceiver.h"
|
||||||
#include "JkBmsController.h"
|
#include "JkBmsController.h"
|
||||||
|
#include "VictronSmartShunt.h"
|
||||||
|
|
||||||
BatteryClass Battery;
|
BatteryClass Battery;
|
||||||
|
|
||||||
@ -42,6 +43,10 @@ void BatteryClass::init()
|
|||||||
_upProvider = std::make_unique<JkBms::Controller>();
|
_upProvider = std::make_unique<JkBms::Controller>();
|
||||||
if (!_upProvider->init(verboseLogging)) { _upProvider = nullptr; }
|
if (!_upProvider->init(verboseLogging)) { _upProvider = nullptr; }
|
||||||
break;
|
break;
|
||||||
|
case 3:
|
||||||
|
_upProvider = std::make_unique<VictronSmartShunt>();
|
||||||
|
if (!_upProvider->init(verboseLogging)) { _upProvider = nullptr; }
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
MessageOutput.printf("Unknown battery provider: %d\r\n", config.Battery_Provider);
|
MessageOutput.printf("Unknown battery provider: %d\r\n", config.Battery_Provider);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -203,3 +203,52 @@ void JkBmsBatteryStats::updateFrom(JkBms::DataPointContainer const& dp)
|
|||||||
|
|
||||||
_lastUpdate = millis();
|
_lastUpdate = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VictronSmartShuntStats::updateFrom(VeDirectShuntController::veShuntStruct const& shuntData) {
|
||||||
|
_SoC = shuntData.SOC / 10;
|
||||||
|
_voltage = shuntData.V;
|
||||||
|
_current = shuntData.I;
|
||||||
|
_modelName = VeDirectShunt.getPidAsString(shuntData.PID);
|
||||||
|
_chargeCycles = shuntData.H4;
|
||||||
|
_timeToGo = shuntData.TTG / 60;
|
||||||
|
_chargedEnergy = shuntData.H18 / 100;
|
||||||
|
_dischargedEnergy = shuntData.H17 / 100;
|
||||||
|
_manufacturer = "Victron " + _modelName;
|
||||||
|
|
||||||
|
// shuntData.AR is a bitfield, so we need to check each bit individually
|
||||||
|
_alarmLowVoltage = shuntData.AR & 1;
|
||||||
|
_alarmHighVoltage = shuntData.AR & 2;
|
||||||
|
_alarmLowSOC = shuntData.AR & 4;
|
||||||
|
_alarmLowTemperature = shuntData.AR & 32;
|
||||||
|
_alarmHighTemperature = shuntData.AR & 64;
|
||||||
|
|
||||||
|
_lastUpdate = VeDirectShunt.getLastUpdate();
|
||||||
|
_lastUpdateSoC = VeDirectShunt.getLastUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VictronSmartShuntStats::getLiveViewData(JsonVariant& root) const {
|
||||||
|
BatteryStats::getLiveViewData(root);
|
||||||
|
|
||||||
|
// values go into the "Status" card of the web application
|
||||||
|
addLiveViewValue(root, "voltage", _voltage, "V", 2);
|
||||||
|
addLiveViewValue(root, "current", _current, "A", 1);
|
||||||
|
addLiveViewValue(root, "chargeCycles", _chargeCycles, "", 0);
|
||||||
|
addLiveViewValue(root, "chargedEnergy", _chargedEnergy, "KWh", 1);
|
||||||
|
addLiveViewValue(root, "dischargedEnergy", _dischargedEnergy, "KWh", 1);
|
||||||
|
|
||||||
|
addLiveViewAlarm(root, "lowVoltage", _alarmLowVoltage);
|
||||||
|
addLiveViewAlarm(root, "highVoltage", _alarmHighVoltage);
|
||||||
|
addLiveViewAlarm(root, "lowSOC", _alarmLowSOC);
|
||||||
|
addLiveViewAlarm(root, "lowTemperature", _alarmLowTemperature);
|
||||||
|
addLiveViewAlarm(root, "highTemperature", _alarmHighTemperature);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VictronSmartShuntStats::mqttPublish() const {
|
||||||
|
BatteryStats::mqttPublish();
|
||||||
|
|
||||||
|
MqttSettings.publish(F("battery/voltage"), String(_voltage));
|
||||||
|
MqttSettings.publish(F("battery/current"), String(_current));
|
||||||
|
MqttSettings.publish(F("battery/chargeCycles"), String(_chargeCycles));
|
||||||
|
MqttSettings.publish(F("battery/chargedEnergy"), String(_chargedEnergy));
|
||||||
|
MqttSettings.publish(F("battery/dischargedEnergy"), String(_dischargedEnergy));
|
||||||
|
}
|
||||||
|
|||||||
@ -50,7 +50,7 @@ void MqttHandleVedirectHassClass::publishConfig()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// ensure data is revieved from victron
|
// ensure data is revieved from victron
|
||||||
if (!VeDirect.isDataValid()) {
|
if (!VeDirectMppt.isDataValid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ void MqttHandleVedirectHassClass::publishConfig()
|
|||||||
|
|
||||||
void MqttHandleVedirectHassClass::publishSensor(const char* caption, const char* icon, const char* subTopic, const char* deviceClass, const char* stateClass, const char* unitOfMeasurement )
|
void MqttHandleVedirectHassClass::publishSensor(const char* caption, const char* icon, const char* subTopic, const char* deviceClass, const char* stateClass, const char* unitOfMeasurement )
|
||||||
{
|
{
|
||||||
String serial = VeDirect.veFrame.SER;
|
String serial = VeDirectMppt.veFrame.SER;
|
||||||
|
|
||||||
String sensorId = caption;
|
String sensorId = caption;
|
||||||
sensorId.replace(" ", "_");
|
sensorId.replace(" ", "_");
|
||||||
@ -96,7 +96,7 @@ void MqttHandleVedirectHassClass::publishSensor(const char* caption, const char*
|
|||||||
+ "/config";
|
+ "/config";
|
||||||
|
|
||||||
String statTopic = MqttSettings.getPrefix() + "victron/";
|
String statTopic = MqttSettings.getPrefix() + "victron/";
|
||||||
statTopic.concat(VeDirect.veFrame.SER);
|
statTopic.concat(VeDirectMppt.veFrame.SER);
|
||||||
statTopic.concat("/");
|
statTopic.concat("/");
|
||||||
statTopic.concat(subTopic);
|
statTopic.concat(subTopic);
|
||||||
|
|
||||||
@ -133,7 +133,7 @@ void MqttHandleVedirectHassClass::publishSensor(const char* caption, const char*
|
|||||||
}
|
}
|
||||||
void MqttHandleVedirectHassClass::publishBinarySensor(const char* caption, const char* icon, const char* subTopic, const char* payload_on, const char* payload_off)
|
void MqttHandleVedirectHassClass::publishBinarySensor(const char* caption, const char* icon, const char* subTopic, const char* payload_on, const char* payload_off)
|
||||||
{
|
{
|
||||||
String serial = VeDirect.veFrame.SER;
|
String serial = VeDirectMppt.veFrame.SER;
|
||||||
|
|
||||||
String sensorId = caption;
|
String sensorId = caption;
|
||||||
sensorId.replace(" ", "_");
|
sensorId.replace(" ", "_");
|
||||||
@ -147,7 +147,7 @@ void MqttHandleVedirectHassClass::publishBinarySensor(const char* caption, const
|
|||||||
+ "/config";
|
+ "/config";
|
||||||
|
|
||||||
String statTopic = MqttSettings.getPrefix() + "victron/";
|
String statTopic = MqttSettings.getPrefix() + "victron/";
|
||||||
statTopic.concat(VeDirect.veFrame.SER);
|
statTopic.concat(VeDirectMppt.veFrame.SER);
|
||||||
statTopic.concat("/");
|
statTopic.concat("/");
|
||||||
statTopic.concat(subTopic);
|
statTopic.concat(subTopic);
|
||||||
|
|
||||||
@ -172,12 +172,12 @@ void MqttHandleVedirectHassClass::publishBinarySensor(const char* caption, const
|
|||||||
|
|
||||||
void MqttHandleVedirectHassClass::createDeviceInfo(JsonObject& object)
|
void MqttHandleVedirectHassClass::createDeviceInfo(JsonObject& object)
|
||||||
{
|
{
|
||||||
String serial = VeDirect.veFrame.SER;
|
String serial = VeDirectMppt.veFrame.SER;
|
||||||
object[F("name")] = "Victron(" + serial + ")";
|
object[F("name")] = "Victron(" + serial + ")";
|
||||||
object[F("ids")] = 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.veFrame.PID);
|
object[F("mdl")] = VeDirectMppt.getPidAsString(VeDirectMppt.veFrame.PID);
|
||||||
object[F("sw")] = AUTO_GIT_HASH;
|
object[F("sw")] = AUTO_GIT_HASH;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Helge Erbe and others
|
* Copyright (C) 2022 Helge Erbe and others
|
||||||
*/
|
*/
|
||||||
#include "VeDirectFrameHandler.h"
|
#include "VeDirectMpptController.h"
|
||||||
#include "MqttHandleVedirect.h"
|
#include "MqttHandleVedirect.h"
|
||||||
#include "MqttSettings.h"
|
#include "MqttSettings.h"
|
||||||
#include "MessageOutput.h"
|
#include "MessageOutput.h"
|
||||||
@ -29,7 +29,7 @@ void MqttHandleVedirectClass::loop()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!VeDirect.isDataValid()) {
|
if (!VeDirectMppt.isDataValid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,67 +52,67 @@ void MqttHandleVedirectClass::loop()
|
|||||||
|
|
||||||
String value;
|
String value;
|
||||||
String topic = "victron/";
|
String topic = "victron/";
|
||||||
topic.concat(VeDirect.veFrame.SER);
|
topic.concat(VeDirectMppt.veFrame.SER);
|
||||||
topic.concat("/");
|
topic.concat("/");
|
||||||
|
|
||||||
if (_PublishFull || VeDirect.veFrame.PID != _kvFrame.PID)
|
if (_PublishFull || VeDirectMppt.veFrame.PID != _kvFrame.PID)
|
||||||
MqttSettings.publish(topic + "PID", VeDirect.getPidAsString(VeDirect.veFrame.PID));
|
MqttSettings.publish(topic + "PID", VeDirectMppt.getPidAsString(VeDirectMppt.veFrame.PID));
|
||||||
if (_PublishFull || strcmp(VeDirect.veFrame.SER, _kvFrame.SER) != 0)
|
if (_PublishFull || strcmp(VeDirectMppt.veFrame.SER, _kvFrame.SER) != 0)
|
||||||
MqttSettings.publish(topic + "SER", VeDirect.veFrame.SER );
|
MqttSettings.publish(topic + "SER", VeDirectMppt.veFrame.SER );
|
||||||
if (_PublishFull || strcmp(VeDirect.veFrame.FW, _kvFrame.FW) != 0)
|
if (_PublishFull || strcmp(VeDirectMppt.veFrame.FW, _kvFrame.FW) != 0)
|
||||||
MqttSettings.publish(topic + "FW", VeDirect.veFrame.FW);
|
MqttSettings.publish(topic + "FW", VeDirectMppt.veFrame.FW);
|
||||||
if (_PublishFull || VeDirect.veFrame.LOAD != _kvFrame.LOAD)
|
if (_PublishFull || VeDirectMppt.veFrame.LOAD != _kvFrame.LOAD)
|
||||||
MqttSettings.publish(topic + "LOAD", VeDirect.veFrame.LOAD == true ? "ON": "OFF");
|
MqttSettings.publish(topic + "LOAD", VeDirectMppt.veFrame.LOAD == true ? "ON": "OFF");
|
||||||
if (_PublishFull || VeDirect.veFrame.CS != _kvFrame.CS)
|
if (_PublishFull || VeDirectMppt.veFrame.CS != _kvFrame.CS)
|
||||||
MqttSettings.publish(topic + "CS", VeDirect.getCsAsString(VeDirect.veFrame.CS));
|
MqttSettings.publish(topic + "CS", VeDirectMppt.getCsAsString(VeDirectMppt.veFrame.CS));
|
||||||
if (_PublishFull || VeDirect.veFrame.ERR != _kvFrame.ERR)
|
if (_PublishFull || VeDirectMppt.veFrame.ERR != _kvFrame.ERR)
|
||||||
MqttSettings.publish(topic + "ERR", VeDirect.getErrAsString(VeDirect.veFrame.ERR));
|
MqttSettings.publish(topic + "ERR", VeDirectMppt.getErrAsString(VeDirectMppt.veFrame.ERR));
|
||||||
if (_PublishFull || VeDirect.veFrame.OR != _kvFrame.OR)
|
if (_PublishFull || VeDirectMppt.veFrame.OR != _kvFrame.OR)
|
||||||
MqttSettings.publish(topic + "OR", VeDirect.getOrAsString(VeDirect.veFrame.OR));
|
MqttSettings.publish(topic + "OR", VeDirectMppt.getOrAsString(VeDirectMppt.veFrame.OR));
|
||||||
if (_PublishFull || VeDirect.veFrame.MPPT != _kvFrame.MPPT)
|
if (_PublishFull || VeDirectMppt.veFrame.MPPT != _kvFrame.MPPT)
|
||||||
MqttSettings.publish(topic + "MPPT", VeDirect.getMpptAsString(VeDirect.veFrame.MPPT));
|
MqttSettings.publish(topic + "MPPT", VeDirectMppt.getMpptAsString(VeDirectMppt.veFrame.MPPT));
|
||||||
if (_PublishFull || VeDirect.veFrame.HSDS != _kvFrame.HSDS) {
|
if (_PublishFull || VeDirectMppt.veFrame.HSDS != _kvFrame.HSDS) {
|
||||||
value = VeDirect.veFrame.HSDS;
|
value = VeDirectMppt.veFrame.HSDS;
|
||||||
MqttSettings.publish(topic + "HSDS", value);
|
MqttSettings.publish(topic + "HSDS", value);
|
||||||
}
|
}
|
||||||
if (_PublishFull || VeDirect.veFrame.V != _kvFrame.V) {
|
if (_PublishFull || VeDirectMppt.veFrame.V != _kvFrame.V) {
|
||||||
value = VeDirect.veFrame.V;
|
value = VeDirectMppt.veFrame.V;
|
||||||
MqttSettings.publish(topic + "V", value);
|
MqttSettings.publish(topic + "V", value);
|
||||||
}
|
}
|
||||||
if (_PublishFull || VeDirect.veFrame.I != _kvFrame.I) {
|
if (_PublishFull || VeDirectMppt.veFrame.I != _kvFrame.I) {
|
||||||
value = VeDirect.veFrame.I;
|
value = VeDirectMppt.veFrame.I;
|
||||||
MqttSettings.publish(topic + "I", value);
|
MqttSettings.publish(topic + "I", value);
|
||||||
}
|
}
|
||||||
if (_PublishFull || VeDirect.veFrame.VPV != _kvFrame.VPV) {
|
if (_PublishFull || VeDirectMppt.veFrame.VPV != _kvFrame.VPV) {
|
||||||
value = VeDirect.veFrame.VPV;
|
value = VeDirectMppt.veFrame.VPV;
|
||||||
MqttSettings.publish(topic + "VPV", value);
|
MqttSettings.publish(topic + "VPV", value);
|
||||||
}
|
}
|
||||||
if (_PublishFull || VeDirect.veFrame.PPV != _kvFrame.PPV) {
|
if (_PublishFull || VeDirectMppt.veFrame.PPV != _kvFrame.PPV) {
|
||||||
value = VeDirect.veFrame.PPV;
|
value = VeDirectMppt.veFrame.PPV;
|
||||||
MqttSettings.publish(topic + "PPV", value);
|
MqttSettings.publish(topic + "PPV", value);
|
||||||
}
|
}
|
||||||
if (_PublishFull || VeDirect.veFrame.H19 != _kvFrame.H19) {
|
if (_PublishFull || VeDirectMppt.veFrame.H19 != _kvFrame.H19) {
|
||||||
value = VeDirect.veFrame.H19;
|
value = VeDirectMppt.veFrame.H19;
|
||||||
MqttSettings.publish(topic + "H19", value);
|
MqttSettings.publish(topic + "H19", value);
|
||||||
}
|
}
|
||||||
if (_PublishFull || VeDirect.veFrame.H20 != _kvFrame.H20) {
|
if (_PublishFull || VeDirectMppt.veFrame.H20 != _kvFrame.H20) {
|
||||||
value = VeDirect.veFrame.H20;
|
value = VeDirectMppt.veFrame.H20;
|
||||||
MqttSettings.publish(topic + "H20", value);
|
MqttSettings.publish(topic + "H20", value);
|
||||||
}
|
}
|
||||||
if (_PublishFull || VeDirect.veFrame.H21 != _kvFrame.H21) {
|
if (_PublishFull || VeDirectMppt.veFrame.H21 != _kvFrame.H21) {
|
||||||
value = VeDirect.veFrame.H21;
|
value = VeDirectMppt.veFrame.H21;
|
||||||
MqttSettings.publish(topic + "H21", value);
|
MqttSettings.publish(topic + "H21", value);
|
||||||
}
|
}
|
||||||
if (_PublishFull || VeDirect.veFrame.H22 != _kvFrame.H22) {
|
if (_PublishFull || VeDirectMppt.veFrame.H22 != _kvFrame.H22) {
|
||||||
value = VeDirect.veFrame.H22;
|
value = VeDirectMppt.veFrame.H22;
|
||||||
MqttSettings.publish(topic + "H22", value);
|
MqttSettings.publish(topic + "H22", value);
|
||||||
}
|
}
|
||||||
if (_PublishFull || VeDirect.veFrame.H23 != _kvFrame.H23) {
|
if (_PublishFull || VeDirectMppt.veFrame.H23 != _kvFrame.H23) {
|
||||||
value = VeDirect.veFrame.H23;
|
value = VeDirectMppt.veFrame.H23;
|
||||||
MqttSettings.publish(topic + "H23", value);
|
MqttSettings.publish(topic + "H23", value);
|
||||||
}
|
}
|
||||||
if (!_PublishFull) {
|
if (!_PublishFull) {
|
||||||
_kvFrame= VeDirect.veFrame;
|
_kvFrame= VeDirectMppt.veFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
// now calculate next points of time to publish
|
// now calculate next points of time to publish
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
#include "MqttSettings.h"
|
#include "MqttSettings.h"
|
||||||
#include "NetworkSettings.h"
|
#include "NetworkSettings.h"
|
||||||
#include "Huawei_can.h"
|
#include "Huawei_can.h"
|
||||||
#include <VeDirectFrameHandler.h>
|
#include <VeDirectMpptController.h>
|
||||||
#include "MessageOutput.h"
|
#include "MessageOutput.h"
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
@ -366,12 +366,12 @@ void PowerLimiterClass::unconditionalSolarPassthrough(std::shared_ptr<InverterAb
|
|||||||
{
|
{
|
||||||
CONFIG_T& config = Configuration.get();
|
CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
if (!config.Vedirect_Enabled || !VeDirect.isDataValid()) {
|
if (!config.Vedirect_Enabled || !VeDirectMppt.isDataValid()) {
|
||||||
shutdown(Status::NoVeDirect);
|
shutdown(Status::NoVeDirect);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t solarPower = VeDirect.veFrame.V * VeDirect.veFrame.I;
|
int32_t solarPower = VeDirectMppt.veFrame.V * VeDirectMppt.veFrame.I;
|
||||||
setNewPowerLimit(inverter, inverterPowerDcToAc(inverter, solarPower));
|
setNewPowerLimit(inverter, inverterPowerDcToAc(inverter, solarPower));
|
||||||
announceStatus(Status::UnconditionalSolarPassthrough);
|
announceStatus(Status::UnconditionalSolarPassthrough);
|
||||||
}
|
}
|
||||||
@ -407,11 +407,11 @@ bool PowerLimiterClass::canUseDirectSolarPower()
|
|||||||
if (!config.PowerLimiter_SolarPassThroughEnabled
|
if (!config.PowerLimiter_SolarPassThroughEnabled
|
||||||
|| isBelowStopThreshold()
|
|| isBelowStopThreshold()
|
||||||
|| !config.Vedirect_Enabled
|
|| !config.Vedirect_Enabled
|
||||||
|| !VeDirect.isDataValid()) {
|
|| !VeDirectMppt.isDataValid()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return VeDirect.veFrame.PPV >= 20; // enough power?
|
return VeDirectMppt.veFrame.PPV >= 20; // enough power?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -569,7 +569,7 @@ int32_t PowerLimiterClass::getSolarChargePower()
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return VeDirect.veFrame.V * VeDirect.veFrame.I;
|
return VeDirectMppt.veFrame.V * VeDirectMppt.veFrame.I;
|
||||||
}
|
}
|
||||||
|
|
||||||
float PowerLimiterClass::getLoadCorrectedVoltage()
|
float PowerLimiterClass::getLoadCorrectedVoltage()
|
||||||
|
|||||||
32
src/VictronSmartShunt.cpp
Normal file
32
src/VictronSmartShunt.cpp
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#include "VictronSmartShunt.h"
|
||||||
|
#include "Configuration.h"
|
||||||
|
#include "PinMapping.h"
|
||||||
|
#include "MessageOutput.h"
|
||||||
|
|
||||||
|
|
||||||
|
bool VictronSmartShunt::init(bool verboseLogging)
|
||||||
|
{
|
||||||
|
MessageOutput.println(F("[VictronSmartShunt] Initialize interface..."));
|
||||||
|
|
||||||
|
const PinMapping_t& pin = PinMapping.get();
|
||||||
|
MessageOutput.printf("[VictronSmartShunt] Interface rx = %d, tx = %d\r\n",
|
||||||
|
pin.battery_rx, pin.battery_tx);
|
||||||
|
|
||||||
|
if (pin.battery_rx < 0 || pin.battery_tx < 0) {
|
||||||
|
MessageOutput.println(F("[VictronSmartShunt] Invalid pin config"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto tx = static_cast<gpio_num_t>(pin.battery_tx);
|
||||||
|
auto rx = static_cast<gpio_num_t>(pin.battery_rx);
|
||||||
|
|
||||||
|
VeDirectShunt.init(rx, tx, &MessageOutput, verboseLogging);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VictronSmartShunt::loop()
|
||||||
|
{
|
||||||
|
VeDirectShunt.loop();
|
||||||
|
_stats->updateFrom(VeDirectShunt.veFrame);
|
||||||
|
}
|
||||||
@ -3,7 +3,7 @@
|
|||||||
* Copyright (C) 2022 Thomas Basler and others
|
* Copyright (C) 2022 Thomas Basler and others
|
||||||
*/
|
*/
|
||||||
#include "WebApi_vedirect.h"
|
#include "WebApi_vedirect.h"
|
||||||
#include "VeDirectFrameHandler.h"
|
#include "VeDirectMpptController.h"
|
||||||
#include "ArduinoJson.h"
|
#include "ArduinoJson.h"
|
||||||
#include "AsyncJson.h"
|
#include "AsyncJson.h"
|
||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
@ -117,7 +117,7 @@ void WebApiVedirectClass::onVedirectAdminPost(AsyncWebServerRequest* request)
|
|||||||
config.Vedirect_UpdatesOnly = root[F("vedirect_updatesonly")].as<bool>();
|
config.Vedirect_UpdatesOnly = root[F("vedirect_updatesonly")].as<bool>();
|
||||||
Configuration.write();
|
Configuration.write();
|
||||||
|
|
||||||
VeDirect.setVerboseLogging(config.Vedirect_VerboseLogging);
|
VeDirectMppt.setVerboseLogging(config.Vedirect_VerboseLogging);
|
||||||
|
|
||||||
retMsg[F("type")] = F("success");
|
retMsg[F("type")] = F("success");
|
||||||
retMsg[F("message")] = F("Settings saved!");
|
retMsg[F("message")] = F("Settings saved!");
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
#include "Battery.h"
|
#include "Battery.h"
|
||||||
#include "Huawei_can.h"
|
#include "Huawei_can.h"
|
||||||
#include "PowerMeter.h"
|
#include "PowerMeter.h"
|
||||||
#include "VeDirectFrameHandler.h"
|
#include "VeDirectMpptController.h"
|
||||||
#include "defaults.h"
|
#include "defaults.h"
|
||||||
#include <AsyncJson.h>
|
#include <AsyncJson.h>
|
||||||
|
|
||||||
@ -191,9 +191,9 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
|
|||||||
JsonObject vedirectObj = root.createNestedObject("vedirect");
|
JsonObject vedirectObj = root.createNestedObject("vedirect");
|
||||||
vedirectObj[F("enabled")] = Configuration.get().Vedirect_Enabled;
|
vedirectObj[F("enabled")] = Configuration.get().Vedirect_Enabled;
|
||||||
JsonObject totalVeObj = vedirectObj.createNestedObject("total");
|
JsonObject totalVeObj = vedirectObj.createNestedObject("total");
|
||||||
addTotalField(totalVeObj, "Power", VeDirect.veFrame.PPV, "W", 1);
|
addTotalField(totalVeObj, "Power", VeDirectMppt.veFrame.PPV, "W", 1);
|
||||||
addTotalField(totalVeObj, "YieldDay", VeDirect.veFrame.H20 * 1000, "Wh", 0);
|
addTotalField(totalVeObj, "YieldDay", VeDirectMppt.veFrame.H20 * 1000, "Wh", 0);
|
||||||
addTotalField(totalVeObj, "YieldTotal", VeDirect.veFrame.H19, "kWh", 2);
|
addTotalField(totalVeObj, "YieldTotal", VeDirectMppt.veFrame.H19, "kWh", 2);
|
||||||
|
|
||||||
JsonObject huaweiObj = root.createNestedObject("huawei");
|
JsonObject huaweiObj = root.createNestedObject("huawei");
|
||||||
huaweiObj[F("enabled")] = Configuration.get().Huawei_Enabled;
|
huaweiObj[F("enabled")] = Configuration.get().Huawei_Enabled;
|
||||||
|
|||||||
@ -50,8 +50,8 @@ void WebApiWsVedirectLiveClass::loop()
|
|||||||
_lastVedirectUpdateCheck = millis();
|
_lastVedirectUpdateCheck = millis();
|
||||||
|
|
||||||
uint32_t maxTimeStamp = 0;
|
uint32_t maxTimeStamp = 0;
|
||||||
if (VeDirect.getLastUpdate() > maxTimeStamp) {
|
if (VeDirectMppt.getLastUpdate() > maxTimeStamp) {
|
||||||
maxTimeStamp = VeDirect.getLastUpdate();
|
maxTimeStamp = VeDirectMppt.getLastUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update on ve.direct change or at least after 10 seconds
|
// Update on ve.direct change or at least after 10 seconds
|
||||||
@ -88,56 +88,56 @@ void WebApiWsVedirectLiveClass::loop()
|
|||||||
void WebApiWsVedirectLiveClass::generateJsonResponse(JsonVariant& root)
|
void WebApiWsVedirectLiveClass::generateJsonResponse(JsonVariant& root)
|
||||||
{
|
{
|
||||||
// device info
|
// device info
|
||||||
root["device"]["data_age"] = (millis() - VeDirect.getLastUpdate() ) / 1000;
|
root["device"]["data_age"] = (millis() - VeDirectMppt.getLastUpdate() ) / 1000;
|
||||||
root["device"]["age_critical"] = !VeDirect.isDataValid();
|
root["device"]["age_critical"] = !VeDirectMppt.isDataValid();
|
||||||
root["device"]["PID"] = VeDirect.getPidAsString(VeDirect.veFrame.PID);
|
root["device"]["PID"] = VeDirectMppt.getPidAsString(VeDirectMppt.veFrame.PID);
|
||||||
root["device"]["SER"] = VeDirect.veFrame.SER;
|
root["device"]["SER"] = VeDirectMppt.veFrame.SER;
|
||||||
root["device"]["FW"] = VeDirect.veFrame.FW;
|
root["device"]["FW"] = VeDirectMppt.veFrame.FW;
|
||||||
root["device"]["LOAD"] = VeDirect.veFrame.LOAD == true ? "ON" : "OFF";
|
root["device"]["LOAD"] = VeDirectMppt.veFrame.LOAD == true ? "ON" : "OFF";
|
||||||
root["device"]["CS"] = VeDirect.getCsAsString(VeDirect.veFrame.CS);
|
root["device"]["CS"] = VeDirectMppt.getCsAsString(VeDirectMppt.veFrame.CS);
|
||||||
root["device"]["ERR"] = VeDirect.getErrAsString(VeDirect.veFrame.ERR);
|
root["device"]["ERR"] = VeDirectMppt.getErrAsString(VeDirectMppt.veFrame.ERR);
|
||||||
root["device"]["OR"] = VeDirect.getOrAsString(VeDirect.veFrame.OR);
|
root["device"]["OR"] = VeDirectMppt.getOrAsString(VeDirectMppt.veFrame.OR);
|
||||||
root["device"]["MPPT"] = VeDirect.getMpptAsString(VeDirect.veFrame.MPPT);
|
root["device"]["MPPT"] = VeDirectMppt.getMpptAsString(VeDirectMppt.veFrame.MPPT);
|
||||||
root["device"]["HSDS"]["v"] = VeDirect.veFrame.HSDS;
|
root["device"]["HSDS"]["v"] = VeDirectMppt.veFrame.HSDS;
|
||||||
root["device"]["HSDS"]["u"] = "d";
|
root["device"]["HSDS"]["u"] = "d";
|
||||||
|
|
||||||
// battery info
|
// battery info
|
||||||
root["output"]["P"]["v"] = VeDirect.veFrame.P;
|
root["output"]["P"]["v"] = VeDirectMppt.veFrame.P;
|
||||||
root["output"]["P"]["u"] = "W";
|
root["output"]["P"]["u"] = "W";
|
||||||
root["output"]["P"]["d"] = 0;
|
root["output"]["P"]["d"] = 0;
|
||||||
root["output"]["V"]["v"] = VeDirect.veFrame.V;
|
root["output"]["V"]["v"] = VeDirectMppt.veFrame.V;
|
||||||
root["output"]["V"]["u"] = "V";
|
root["output"]["V"]["u"] = "V";
|
||||||
root["output"]["V"]["d"] = 2;
|
root["output"]["V"]["d"] = 2;
|
||||||
root["output"]["I"]["v"] = VeDirect.veFrame.I;
|
root["output"]["I"]["v"] = VeDirectMppt.veFrame.I;
|
||||||
root["output"]["I"]["u"] = "A";
|
root["output"]["I"]["u"] = "A";
|
||||||
root["output"]["I"]["d"] = 2;
|
root["output"]["I"]["d"] = 2;
|
||||||
root["output"]["E"]["v"] = VeDirect.veFrame.E;
|
root["output"]["E"]["v"] = VeDirectMppt.veFrame.E;
|
||||||
root["output"]["E"]["u"] = "%";
|
root["output"]["E"]["u"] = "%";
|
||||||
root["output"]["E"]["d"] = 1;
|
root["output"]["E"]["d"] = 1;
|
||||||
|
|
||||||
// panel info
|
// panel info
|
||||||
root["input"]["PPV"]["v"] = VeDirect.veFrame.PPV;
|
root["input"]["PPV"]["v"] = VeDirectMppt.veFrame.PPV;
|
||||||
root["input"]["PPV"]["u"] = "W";
|
root["input"]["PPV"]["u"] = "W";
|
||||||
root["input"]["PPV"]["d"] = 0;
|
root["input"]["PPV"]["d"] = 0;
|
||||||
root["input"]["VPV"]["v"] = VeDirect.veFrame.VPV;
|
root["input"]["VPV"]["v"] = VeDirectMppt.veFrame.VPV;
|
||||||
root["input"]["VPV"]["u"] = "V";
|
root["input"]["VPV"]["u"] = "V";
|
||||||
root["input"]["VPV"]["d"] = 2;
|
root["input"]["VPV"]["d"] = 2;
|
||||||
root["input"]["IPV"]["v"] = VeDirect.veFrame.IPV;
|
root["input"]["IPV"]["v"] = VeDirectMppt.veFrame.IPV;
|
||||||
root["input"]["IPV"]["u"] = "A";
|
root["input"]["IPV"]["u"] = "A";
|
||||||
root["input"]["IPV"]["d"] = 2;
|
root["input"]["IPV"]["d"] = 2;
|
||||||
root["input"]["YieldToday"]["v"] = VeDirect.veFrame.H20;
|
root["input"]["YieldToday"]["v"] = VeDirectMppt.veFrame.H20;
|
||||||
root["input"]["YieldToday"]["u"] = "kWh";
|
root["input"]["YieldToday"]["u"] = "kWh";
|
||||||
root["input"]["YieldToday"]["d"] = 3;
|
root["input"]["YieldToday"]["d"] = 3;
|
||||||
root["input"]["YieldYesterday"]["v"] = VeDirect.veFrame.H22;
|
root["input"]["YieldYesterday"]["v"] = VeDirectMppt.veFrame.H22;
|
||||||
root["input"]["YieldYesterday"]["u"] = "kWh";
|
root["input"]["YieldYesterday"]["u"] = "kWh";
|
||||||
root["input"]["YieldYesterday"]["d"] = 3;
|
root["input"]["YieldYesterday"]["d"] = 3;
|
||||||
root["input"]["YieldTotal"]["v"] = VeDirect.veFrame.H19;
|
root["input"]["YieldTotal"]["v"] = VeDirectMppt.veFrame.H19;
|
||||||
root["input"]["YieldTotal"]["u"] = "kWh";
|
root["input"]["YieldTotal"]["u"] = "kWh";
|
||||||
root["input"]["YieldTotal"]["d"] = 3;
|
root["input"]["YieldTotal"]["d"] = 3;
|
||||||
root["input"]["MaximumPowerToday"]["v"] = VeDirect.veFrame.H21;
|
root["input"]["MaximumPowerToday"]["v"] = VeDirectMppt.veFrame.H21;
|
||||||
root["input"]["MaximumPowerToday"]["u"] = "W";
|
root["input"]["MaximumPowerToday"]["u"] = "W";
|
||||||
root["input"]["MaximumPowerToday"]["d"] = 0;
|
root["input"]["MaximumPowerToday"]["d"] = 0;
|
||||||
root["input"]["MaximumPowerYesterday"]["v"] = VeDirect.veFrame.H23;
|
root["input"]["MaximumPowerYesterday"]["v"] = VeDirectMppt.veFrame.H23;
|
||||||
root["input"]["MaximumPowerYesterday"]["u"] = "W";
|
root["input"]["MaximumPowerYesterday"]["u"] = "W";
|
||||||
root["input"]["MaximumPowerYesterday"]["d"] = 0;
|
root["input"]["MaximumPowerYesterday"]["d"] = 0;
|
||||||
|
|
||||||
@ -147,8 +147,8 @@ void WebApiWsVedirectLiveClass::generateJsonResponse(JsonVariant& root)
|
|||||||
root["dpl"]["PLSTATE"] = PowerLimiter.getPowerLimiterState();
|
root["dpl"]["PLSTATE"] = PowerLimiter.getPowerLimiterState();
|
||||||
root["dpl"]["PLLIMIT"] = PowerLimiter.getLastRequestedPowerLimit();
|
root["dpl"]["PLLIMIT"] = PowerLimiter.getLastRequestedPowerLimit();
|
||||||
|
|
||||||
if (VeDirect.getLastUpdate() > _newestVedirectTimestamp) {
|
if (VeDirectMppt.getLastUpdate() > _newestVedirectTimestamp) {
|
||||||
_newestVedirectTimestamp = VeDirect.getLastUpdate();
|
_newestVedirectTimestamp = VeDirectMppt.getLastUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
#include "InverterSettings.h"
|
#include "InverterSettings.h"
|
||||||
#include "Led_Single.h"
|
#include "Led_Single.h"
|
||||||
#include "MessageOutput.h"
|
#include "MessageOutput.h"
|
||||||
#include "VeDirectFrameHandler.h"
|
#include "VeDirectMpptController.h"
|
||||||
#include "Battery.h"
|
#include "Battery.h"
|
||||||
#include "Huawei_can.h"
|
#include "Huawei_can.h"
|
||||||
#include "MqttHandleDtu.h"
|
#include "MqttHandleDtu.h"
|
||||||
@ -165,7 +165,7 @@ void setup()
|
|||||||
MessageOutput.println(F("Initialize ve.direct interface... "));
|
MessageOutput.println(F("Initialize ve.direct interface... "));
|
||||||
if (PinMapping.isValidVictronConfig()) {
|
if (PinMapping.isValidVictronConfig()) {
|
||||||
MessageOutput.printf("ve.direct rx = %d, tx = %d\r\n", pin.victron_rx, pin.victron_tx);
|
MessageOutput.printf("ve.direct rx = %d, tx = %d\r\n", pin.victron_rx, pin.victron_tx);
|
||||||
VeDirect.init(pin.victron_rx, pin.victron_tx,
|
VeDirectMppt.init(pin.victron_rx, pin.victron_tx,
|
||||||
&MessageOutput, config.Vedirect_VerboseLogging);
|
&MessageOutput, config.Vedirect_VerboseLogging);
|
||||||
MessageOutput.println(F("done"));
|
MessageOutput.println(F("done"));
|
||||||
} else {
|
} else {
|
||||||
@ -204,7 +204,7 @@ void loop()
|
|||||||
yield();
|
yield();
|
||||||
// Vedirect_Enabled is unknown to lib. Therefor check has to be done here
|
// Vedirect_Enabled is unknown to lib. Therefor check has to be done here
|
||||||
if (Configuration.get().Vedirect_Enabled) {
|
if (Configuration.get().Vedirect_Enabled) {
|
||||||
VeDirect.loop();
|
VeDirectMppt.loop();
|
||||||
yield();
|
yield();
|
||||||
}
|
}
|
||||||
MqttSettings.loop();
|
MqttSettings.loop();
|
||||||
|
|||||||
@ -599,6 +599,7 @@
|
|||||||
"Provider": "Datenanbieter",
|
"Provider": "Datenanbieter",
|
||||||
"ProviderPylontechCan": "Pylontech per CAN-Bus",
|
"ProviderPylontechCan": "Pylontech per CAN-Bus",
|
||||||
"ProviderJkBmsSerial": "Jikong (JK) BMS per serieller Verbindung",
|
"ProviderJkBmsSerial": "Jikong (JK) BMS per serieller Verbindung",
|
||||||
|
"ProviderVictron": "Victron SmartShunt per VE.Direct Schnittstelle",
|
||||||
"JkBmsConfiguration": "JK BMS Einstellungen",
|
"JkBmsConfiguration": "JK BMS Einstellungen",
|
||||||
"JkBmsInterface": "Schnittstellentyp",
|
"JkBmsInterface": "Schnittstellentyp",
|
||||||
"JkBmsInterfaceUart": "TTL-UART an der MCU",
|
"JkBmsInterfaceUart": "TTL-UART an der MCU",
|
||||||
@ -821,10 +822,14 @@
|
|||||||
"underTemperature": "Untertemperatur",
|
"underTemperature": "Untertemperatur",
|
||||||
"highTemperature": "Hohe Temperatur",
|
"highTemperature": "Hohe Temperatur",
|
||||||
"overTemperature": "Übertemperatur",
|
"overTemperature": "Übertemperatur",
|
||||||
|
"lowSOC": "Geringer Ladezustand",
|
||||||
"lowVoltage": "Niedrige Spannung",
|
"lowVoltage": "Niedrige Spannung",
|
||||||
"underVoltage": "Unterspannung",
|
"underVoltage": "Unterspannung",
|
||||||
"highVoltage": "Hohe Spannung",
|
"highVoltage": "Hohe Spannung",
|
||||||
"overVoltage": "Überspannung",
|
"overVoltage": "Überspannung",
|
||||||
"bmsInternal": "BMS intern"
|
"bmsInternal": "BMS intern",
|
||||||
|
"chargeCycles": "Ladezyklen",
|
||||||
|
"chargedEnergy": "Geladene Energie",
|
||||||
|
"dischargedEnergy": "Entladene Energie"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -608,6 +608,7 @@
|
|||||||
"Provider": "Data Provider",
|
"Provider": "Data Provider",
|
||||||
"ProviderPylontechCan": "Pylontech using CAN bus",
|
"ProviderPylontechCan": "Pylontech using CAN bus",
|
||||||
"ProviderJkBmsSerial": "Jikong (JK) BMS using serial connection",
|
"ProviderJkBmsSerial": "Jikong (JK) BMS using serial connection",
|
||||||
|
"ProviderVictron": "Victron SmartShunt using VE.Direct interface",
|
||||||
"JkBmsConfiguration": "JK BMS Settings",
|
"JkBmsConfiguration": "JK BMS Settings",
|
||||||
"JkBmsInterface": "Interface Type",
|
"JkBmsInterface": "Interface Type",
|
||||||
"JkBmsInterfaceUart": "TTL-UART on MCU",
|
"JkBmsInterfaceUart": "TTL-UART on MCU",
|
||||||
@ -832,9 +833,13 @@
|
|||||||
"highTemperature": "High temperature",
|
"highTemperature": "High temperature",
|
||||||
"overTemperature": "Overtemperature",
|
"overTemperature": "Overtemperature",
|
||||||
"lowVoltage": "Low voltage",
|
"lowVoltage": "Low voltage",
|
||||||
|
"lowSOC": "Low state of charge",
|
||||||
"underVoltage": "Undervoltage",
|
"underVoltage": "Undervoltage",
|
||||||
"highVoltage": "High voltage",
|
"highVoltage": "High voltage",
|
||||||
"overVoltage": "Overvoltage",
|
"overVoltage": "Overvoltage",
|
||||||
"bmsInternal": "BMS internal"
|
"bmsInternal": "BMS internal",
|
||||||
|
"chargeCycles": "Charge cycles",
|
||||||
|
"chargedEnergy": "Charged energy",
|
||||||
|
"dischargedEnergy": "Discharged energy"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -514,6 +514,23 @@
|
|||||||
"UpdatesOnly": "Publish values to MQTT only when they change",
|
"UpdatesOnly": "Publish values to MQTT only when they change",
|
||||||
"Save": "@:dtuadmin.Save"
|
"Save": "@:dtuadmin.Save"
|
||||||
},
|
},
|
||||||
|
"batteryadmin": {
|
||||||
|
"BatterySettings": "Battery Settings",
|
||||||
|
"BatteryConfiguration": "General Interface Settings",
|
||||||
|
"EnableBattery": "Enable Interface",
|
||||||
|
"VerboseLogging": "@:base.VerboseLogging",
|
||||||
|
"Provider": "Data Provider",
|
||||||
|
"ProviderPylontechCan": "Pylontech using CAN bus",
|
||||||
|
"ProviderJkBmsSerial": "Jikong (JK) BMS using serial connection",
|
||||||
|
"ProviderVictron": "Victron SmartShunt using VE.Direct interface",
|
||||||
|
"JkBmsConfiguration": "JK BMS Settings",
|
||||||
|
"JkBmsInterface": "Interface Type",
|
||||||
|
"JkBmsInterfaceUart": "TTL-UART on MCU",
|
||||||
|
"JkBmsInterfaceTransceiver": "RS-485 Transceiver on MCU",
|
||||||
|
"PollingInterval": "Polling Interval",
|
||||||
|
"Seconds": "@:dtuadmin.Seconds",
|
||||||
|
"Save": "@:dtuadmin.Save"
|
||||||
|
},
|
||||||
"inverteradmin": {
|
"inverteradmin": {
|
||||||
"InverterSettings": "Paramètres des onduleurs",
|
"InverterSettings": "Paramètres des onduleurs",
|
||||||
"AddInverter": "Ajouter un nouvel onduleur",
|
"AddInverter": "Ajouter un nouvel onduleur",
|
||||||
@ -769,9 +786,13 @@
|
|||||||
"highTemperature": "High temperature",
|
"highTemperature": "High temperature",
|
||||||
"overTemperature": "Overtemperature",
|
"overTemperature": "Overtemperature",
|
||||||
"lowVoltage": "Low voltage",
|
"lowVoltage": "Low voltage",
|
||||||
|
"lowSOC": "Low state of charge",
|
||||||
"underVoltage": "Undervoltage",
|
"underVoltage": "Undervoltage",
|
||||||
"highVoltage": "High voltage",
|
"highVoltage": "High voltage",
|
||||||
"overVoltage": "Overvoltage",
|
"overVoltage": "Overvoltage",
|
||||||
"bmsInternal": "BMS internal"
|
"bmsInternal": "BMS internal",
|
||||||
|
"chargeCycles": "Charge cycles",
|
||||||
|
"chargedEnergy": "Charged energy",
|
||||||
|
"dischargedEnergy": "Discharged energy"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -80,6 +80,7 @@ export default defineComponent({
|
|||||||
providerTypeList: [
|
providerTypeList: [
|
||||||
{ key: 0, value: 'PylontechCan' },
|
{ key: 0, value: 'PylontechCan' },
|
||||||
{ key: 1, value: 'JkBmsSerial' },
|
{ key: 1, value: 'JkBmsSerial' },
|
||||||
|
{ key: 3, value: 'Victron' },
|
||||||
],
|
],
|
||||||
jkBmsInterfaceTypeList: [
|
jkBmsInterfaceTypeList: [
|
||||||
{ key: 0, value: 'Uart' },
|
{ key: 0, value: 'Uart' },
|
||||||
|
|||||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user