mirror of
https://github.com/tbnobody/OpenDTU.git
synced 2026-05-18 13:17:21 +02:00
VE.Direct: Fix design issues and prepare support for multiple instances (#505)
* introduce VictronMpptClass this solves a design issue where the loop() method of a static instance of VeDirectMpptController, which is part of library code, is called as part of the main loop() implementation. that is a problem because the call to this loop() must be handled differently from all other calls: the lib does not know whether or not the feature is enabled at all. also, the instance would not be initialized when enabling the feature during normal operation. that would even lead to a nullptr exception since the pointer to the serial implementation is still uninitialized. this new intermediate class is implemented with the support for multiple Victron charge controllers in mind. adding support for more charge controllers should be more viable than ever. fixes #481. related to #397 #129. * VE.Direct: move get.*AsString methods to respective structs those structs, which hold the data to be translated into strings, know best how to translate them. this change also simplifies access to those translation, as no parameter must be handed to the respective methods: they now act upon the data of the instance they are called for. adds constness to those methods. * VE.Direct: simplify and clean up get.*AsString methods use a map, which is much easier to maintain and which reads much easier. move the strings to flash memory to save RAM. * DPL: use VictronMpptClass::getPowerOutputWatts method remove redundant calculation of output power from DPL. consider separation of concern: VictronMpptClass will provide the total solar output power. the DPL shall not concern itself about how that value is calculated and it certainly should be unaware about how many MPPT charge controllers there actually are. * VE.Direct: avoid shadowing struct member "P" P was part of the base struct for both MPPT and SmartShunt controller. however, P was also part of the SmartShunt controller data struct, shadowing the member in the base struct. since P has slightly different meaning in MPPT versus SmartShunt, and since P is calculated for MPPT controllers but read from SmartShunts, P now lives in both derived structs, but not in the base struct. * VE.Direct: isDataValid(): avoid copying data structs pass a const reference to the base class implementation of isDataValid() rather than a copy of the whole struct. * VE.Direct: unify logging of text events * VE.Direct: stop processing text event if handled by base in case the base class processed a text event, do not try to match it against values that are only valid in the derived class -- none will match. * VE.Direct MPPT: manage data in a shared_ptr instead of handing out a reference to a struct which is part of a class instance that may disappear, e.g., on a config change, we now manage the lifetime of said data structure using a shared_ptr and hand out copies of that shared_ptr. this makes sure that users have a valid copy of the data as long as they hold the shared_ptr. * VE.Direct MPPT: implement getDataAgeMillis() this works even if millis() wraps around. * VE.Direct: process frame end event only for valid frames save a parameters, save a level of indention, save a function call for invalid frames.
This commit is contained in:
@@ -1,25 +1,24 @@
|
||||
#include <Arduino.h>
|
||||
#include <map>
|
||||
#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);
|
||||
_spData = std::make_shared<veMpptStruct>();
|
||||
if (_verboseLogging) { _msgOut->println("Finished init MPPTController"); }
|
||||
}
|
||||
|
||||
bool VeDirectMpptController::isDataValid() {
|
||||
return VeDirectFrameHandler::isDataValid(veFrame);
|
||||
bool VeDirectMpptController::isDataValid() const {
|
||||
return VeDirectFrameHandler::isDataValid(*_spData);
|
||||
}
|
||||
|
||||
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);
|
||||
void VeDirectMpptController::textRxEvent(char* name, char* value)
|
||||
{
|
||||
if (VeDirectFrameHandler::textRxEvent("MPPT", name, value, _tmpFrame)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(name, "LOAD") == 0) {
|
||||
if (strcmp(value, "ON") == 0)
|
||||
_tmpFrame.LOAD = true;
|
||||
@@ -65,139 +64,114 @@ void VeDirectMpptController::textRxEvent(char * name, char * 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.
|
||||
* frameValidEvent
|
||||
* This function is called at the end of the received frame.
|
||||
*/
|
||||
void VeDirectMpptController::frameEndEvent(bool valid) {
|
||||
if (valid) {
|
||||
_tmpFrame.P = _tmpFrame.V * _tmpFrame.I;
|
||||
void VeDirectMpptController::frameValidEvent() {
|
||||
_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();
|
||||
_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();
|
||||
}
|
||||
|
||||
_spData = std::make_shared<veMpptStruct>(_tmpFrame);
|
||||
_tmpFrame = {};
|
||||
_lastUpdate = millis();
|
||||
}
|
||||
|
||||
/*
|
||||
* getCsAsString
|
||||
* This function returns the state of operations (CS) as readable text.
|
||||
*/
|
||||
String VeDirectMpptController::getCsAsString(uint8_t cs)
|
||||
String VeDirectMpptController::veMpptStruct::getCsAsString() const
|
||||
{
|
||||
String strCS ="";
|
||||
static const std::map<uint8_t, String> values = {
|
||||
{ 0, F("OFF") },
|
||||
{ 2, F("Fault") },
|
||||
{ 3, F("Bulk") },
|
||||
{ 4, F("Absorbtion") },
|
||||
{ 5, F("Float") },
|
||||
{ 7, F("Equalize (manual)") },
|
||||
{ 245, F("Starting-up") },
|
||||
{ 247, F("Auto equalize / Recondition") },
|
||||
{ 252, F("External Control") }
|
||||
};
|
||||
|
||||
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;
|
||||
return getAsString(values, CS);
|
||||
}
|
||||
|
||||
/*
|
||||
* getMpptAsString
|
||||
* This function returns the state of MPPT (MPPT) as readable text.
|
||||
*/
|
||||
String VeDirectMpptController::getMpptAsString(uint8_t mppt)
|
||||
String VeDirectMpptController::veMpptStruct::getMpptAsString() const
|
||||
{
|
||||
String strMPPT ="";
|
||||
static const std::map<uint8_t, String> values = {
|
||||
{ 0, F("OFF") },
|
||||
{ 1, F("Voltage or current limited") },
|
||||
{ 2, F("MPP Tracker active") }
|
||||
};
|
||||
|
||||
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;
|
||||
return getAsString(values, MPPT);
|
||||
}
|
||||
|
||||
/*
|
||||
* getErrAsString
|
||||
* This function returns error state (ERR) as readable text.
|
||||
*/
|
||||
String VeDirectMpptController::veMpptStruct::getErrAsString() const
|
||||
{
|
||||
static const std::map<uint8_t, String> values = {
|
||||
{ 0, F("No error") },
|
||||
{ 2, F("Battery voltage too high") },
|
||||
{ 17, F("Charger temperature too high") },
|
||||
{ 18, F("Charger over current") },
|
||||
{ 19, F("Charger current reversed") },
|
||||
{ 20, F("Bulk time limit exceeded") },
|
||||
{ 21, F("Current sensor issue(sensor bias/sensor broken)") },
|
||||
{ 26, F("Terminals overheated") },
|
||||
{ 28, F("Converter issue (dual converter models only)") },
|
||||
{ 33, F("Input voltage too high (solar panel)") },
|
||||
{ 34, F("Input current too high (solar panel)") },
|
||||
{ 38, F("Input shutdown (due to excessive battery voltage)") },
|
||||
{ 39, F("Input shutdown (due to current flow during off mode)") },
|
||||
{ 40, F("Input") },
|
||||
{ 65, F("Lost communication with one of devices") },
|
||||
{ 67, F("Synchronisedcharging device configuration issue") },
|
||||
{ 68, F("BMS connection lost") },
|
||||
{ 116, F("Factory calibration data lost") },
|
||||
{ 117, F("Invalid/incompatible firmware") },
|
||||
{ 118, F("User settings invalid") }
|
||||
};
|
||||
|
||||
return getAsString(values, ERR);
|
||||
}
|
||||
|
||||
/*
|
||||
* getOrAsString
|
||||
* This function returns the off reason (OR) as readable text.
|
||||
*/
|
||||
String VeDirectMpptController::getOrAsString(uint32_t offReason)
|
||||
String VeDirectMpptController::veMpptStruct::getOrAsString() const
|
||||
{
|
||||
String strOR ="";
|
||||
static const std::map<uint32_t, String> values = {
|
||||
{ 0x00000000, F("Not off") },
|
||||
{ 0x00000001, F("No input power") },
|
||||
{ 0x00000002, F("Switched off (power switch)") },
|
||||
{ 0x00000004, F("Switched off (device moderegister)") },
|
||||
{ 0x00000008, F("Remote input") },
|
||||
{ 0x00000010, F("Protection active") },
|
||||
{ 0x00000020, F("Paygo") },
|
||||
{ 0x00000040, F("BMS") },
|
||||
{ 0x00000080, F("Engine shutdown detection") },
|
||||
{ 0x00000100, F("Analysing input voltage") }
|
||||
};
|
||||
|
||||
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;
|
||||
return getAsString(values, OR);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user