Feature: retrieve absorption and float voltage from Victron MPPTs (#1140)
the absorption and float voltage setting is retrieved from connected Victron Ve.Direct MPPTs using the HEX protocol. the values are displayed in the live view, published to MQTT, and added to Home Assistent auto-discovery.
This commit is contained in:
parent
cfb5c3f550
commit
3c1d3f7207
@ -42,6 +42,17 @@ public:
|
|||||||
// minimum of all MPPT charge controllers' output voltages in V
|
// minimum of all MPPT charge controllers' output voltages in V
|
||||||
float getOutputVoltage() const;
|
float getOutputVoltage() const;
|
||||||
|
|
||||||
|
// returns the state of operation from the first available controller
|
||||||
|
std::optional<uint8_t> getStateOfOperation() const;
|
||||||
|
|
||||||
|
// returns the requested value from the first available controller in mV
|
||||||
|
enum class MPPTVoltage : uint8_t {
|
||||||
|
ABSORPTION = 0,
|
||||||
|
FLOAT = 1,
|
||||||
|
BATTERY = 2
|
||||||
|
};
|
||||||
|
std::optional<float> getVoltage(MPPTVoltage kindOf) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void loop();
|
void loop();
|
||||||
VictronMpptClass(VictronMpptClass const& other) = delete;
|
VictronMpptClass(VictronMpptClass const& other) = delete;
|
||||||
|
|||||||
@ -191,7 +191,7 @@ frozen::string const& veMpptStruct::getCsAsString() const
|
|||||||
{ 0, "OFF" },
|
{ 0, "OFF" },
|
||||||
{ 2, "Fault" },
|
{ 2, "Fault" },
|
||||||
{ 3, "Bulk" },
|
{ 3, "Bulk" },
|
||||||
{ 4, "Absorbtion" },
|
{ 4, "Absorption" },
|
||||||
{ 5, "Float" },
|
{ 5, "Float" },
|
||||||
{ 7, "Equalize (manual)" },
|
{ 7, "Equalize (manual)" },
|
||||||
{ 245, "Starting-up" },
|
{ 245, "Starting-up" },
|
||||||
@ -287,18 +287,27 @@ frozen::string const& VeDirectHexData::getResponseAsString() const
|
|||||||
frozen::string const& VeDirectHexData::getRegisterAsString() const
|
frozen::string const& VeDirectHexData::getRegisterAsString() const
|
||||||
{
|
{
|
||||||
using Register = VeDirectHexRegister;
|
using Register = VeDirectHexRegister;
|
||||||
static constexpr frozen::map<Register, frozen::string, 11> values = {
|
static constexpr frozen::map<Register, frozen::string, 20> values = {
|
||||||
{ Register::DeviceMode, "Device Mode" },
|
{ Register::DeviceMode, "Device Mode" },
|
||||||
{ Register::DeviceState, "Device State" },
|
{ Register::DeviceState, "Device State" },
|
||||||
{ Register::RemoteControlUsed, "Remote Control Used" },
|
{ Register::RemoteControlUsed, "Remote Control Used" },
|
||||||
{ Register::PanelVoltage, "Panel Voltage" },
|
{ Register::PanelVoltage, "Panel Voltage" },
|
||||||
|
{ Register::PanelPower, "Panel Power" },
|
||||||
{ Register::ChargerVoltage, "Charger Voltage" },
|
{ Register::ChargerVoltage, "Charger Voltage" },
|
||||||
|
{ Register::ChargerCurrent, "Charger Current" },
|
||||||
{ Register::NetworkTotalDcInputPower, "Network Total DC Input Power" },
|
{ Register::NetworkTotalDcInputPower, "Network Total DC Input Power" },
|
||||||
{ Register::ChargeControllerTemperature, "Charger Controller Temperature" },
|
{ Register::ChargeControllerTemperature, "Charger Controller Temperature" },
|
||||||
{ Register::SmartBatterySenseTemperature, "Smart Battery Sense Temperature" },
|
{ Register::SmartBatterySenseTemperature, "Smart Battery Sense Temperature" },
|
||||||
{ Register::NetworkInfo, "Network Info" },
|
{ Register::NetworkInfo, "Network Info" },
|
||||||
{ Register::NetworkMode, "Network Mode" },
|
{ Register::NetworkMode, "Network Mode" },
|
||||||
{ Register::NetworkStatus, "Network Status" }
|
{ Register::NetworkStatus, "Network Status" },
|
||||||
|
{ Register::BatteryAbsorptionVoltage, "Battery Absorption Voltage" },
|
||||||
|
{ Register::BatteryFloatVoltage, "Battery Float Voltage" },
|
||||||
|
{ Register::TotalChargeCurrent, "Total Charge Current" },
|
||||||
|
{ Register::ChargeStateElapsedTime, "Charge State Elapsed Time" },
|
||||||
|
{ Register::BatteryVoltageSense, "Battery Voltage Sense" },
|
||||||
|
{ Register::LoadCurrent, "Load current" },
|
||||||
|
{ Register::LoadOutputVoltage, "Load Output Voltage" }
|
||||||
};
|
};
|
||||||
|
|
||||||
return getAsString(values, addr);
|
return getAsString(values, addr);
|
||||||
|
|||||||
@ -45,6 +45,8 @@ struct veMpptStruct : veStruct {
|
|||||||
std::pair<uint32_t, int32_t> MpptTemperatureMilliCelsius;
|
std::pair<uint32_t, int32_t> MpptTemperatureMilliCelsius;
|
||||||
std::pair<uint32_t, int32_t> SmartBatterySenseTemperatureMilliCelsius;
|
std::pair<uint32_t, int32_t> SmartBatterySenseTemperatureMilliCelsius;
|
||||||
std::pair<uint32_t, uint32_t> NetworkTotalDcInputPowerMilliWatts;
|
std::pair<uint32_t, uint32_t> NetworkTotalDcInputPowerMilliWatts;
|
||||||
|
std::pair<uint32_t, uint32_t> BatteryAbsorptionMilliVolt;
|
||||||
|
std::pair<uint32_t, uint32_t> BatteryFloatMilliVolt;
|
||||||
std::pair<uint32_t, uint8_t> NetworkInfo;
|
std::pair<uint32_t, uint8_t> NetworkInfo;
|
||||||
std::pair<uint32_t, uint8_t> NetworkMode;
|
std::pair<uint32_t, uint8_t> NetworkMode;
|
||||||
std::pair<uint32_t, uint8_t> NetworkStatus;
|
std::pair<uint32_t, uint8_t> NetworkStatus;
|
||||||
@ -121,7 +123,9 @@ enum class VeDirectHexRegister : uint16_t {
|
|||||||
DeviceState = 0x0201,
|
DeviceState = 0x0201,
|
||||||
RemoteControlUsed = 0x0202,
|
RemoteControlUsed = 0x0202,
|
||||||
PanelVoltage = 0xEDBB,
|
PanelVoltage = 0xEDBB,
|
||||||
|
PanelPower = 0xEDBC,
|
||||||
ChargerVoltage = 0xEDD5,
|
ChargerVoltage = 0xEDD5,
|
||||||
|
ChargerCurrent = 0xEDD7,
|
||||||
NetworkTotalDcInputPower = 0x2027,
|
NetworkTotalDcInputPower = 0x2027,
|
||||||
ChargeControllerTemperature = 0xEDDB,
|
ChargeControllerTemperature = 0xEDDB,
|
||||||
SmartBatterySenseTemperature = 0xEDEC,
|
SmartBatterySenseTemperature = 0xEDEC,
|
||||||
@ -129,7 +133,14 @@ enum class VeDirectHexRegister : uint16_t {
|
|||||||
NetworkMode = 0x200E,
|
NetworkMode = 0x200E,
|
||||||
NetworkStatus = 0x200F,
|
NetworkStatus = 0x200F,
|
||||||
HistoryTotal = 0x104F,
|
HistoryTotal = 0x104F,
|
||||||
HistoryMPPTD30 = 0x10BE
|
HistoryMPPTD30 = 0x10BE,
|
||||||
|
BatteryAbsorptionVoltage = 0xEDF7,
|
||||||
|
BatteryFloatVoltage = 0xEDF6,
|
||||||
|
TotalChargeCurrent = 0x2013,
|
||||||
|
ChargeStateElapsedTime= 0x2007,
|
||||||
|
BatteryVoltageSense = 0x2002,
|
||||||
|
LoadCurrent = 0xEDAD,
|
||||||
|
LoadOutputVoltage = 0xEDA9
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VeDirectHexData {
|
struct VeDirectHexData {
|
||||||
|
|||||||
@ -27,12 +27,13 @@ public:
|
|||||||
bool isDataValid() const; // return true if data valid and not outdated
|
bool isDataValid() const; // return true if data valid and not outdated
|
||||||
T const& getData() const { return _tmpFrame; }
|
T const& getData() const { return _tmpFrame; }
|
||||||
bool sendHexCommand(VeDirectHexCommand cmd, VeDirectHexRegister addr, uint32_t value = 0, uint8_t valsize = 0);
|
bool sendHexCommand(VeDirectHexCommand cmd, VeDirectHexRegister addr, uint32_t value = 0, uint8_t valsize = 0);
|
||||||
|
bool isStateIdle() const { return (_state == State::IDLE); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
VeDirectFrameHandler();
|
VeDirectFrameHandler();
|
||||||
void init(char const* who, int8_t rx, int8_t tx, Print* msgOut,
|
void init(char const* who, int8_t rx, int8_t tx, Print* msgOut,
|
||||||
bool verboseLogging, uint8_t hwSerialPort);
|
bool verboseLogging, uint8_t hwSerialPort);
|
||||||
virtual bool hexDataHandler(VeDirectHexData const &data) { return false; } // handles the disassembeled hex response
|
virtual bool hexDataHandler(VeDirectHexData const &data) { return false; } // handles the disassembled hex response
|
||||||
|
|
||||||
bool _verboseLogging;
|
bool _verboseLogging;
|
||||||
Print* _msgOut;
|
Print* _msgOut;
|
||||||
|
|||||||
@ -8,7 +8,7 @@ HexHandler.cpp
|
|||||||
* 1. Use sendHexCommand() to send hex messages. Use the Victron documentation to find the parameter.
|
* 1. Use sendHexCommand() to send hex messages. Use the Victron documentation to find the parameter.
|
||||||
* 2. The from class "VeDirectFrameHandler" derived class X must overwrite the function
|
* 2. The from class "VeDirectFrameHandler" derived class X must overwrite the function
|
||||||
* void VeDirectFrameHandler::hexDataHandler(VeDirectHexData const &data)
|
* void VeDirectFrameHandler::hexDataHandler(VeDirectHexData const &data)
|
||||||
* to handle the received hex messages. All hex messages will be forwarted to function hexDataHandler()
|
* to handle the received hex messages. All hex messages will be forwarded to function hexDataHandler()
|
||||||
* 3. Analyse the content of data (struct VeDirectHexData) to check if a message fits.
|
* 3. Analyse the content of data (struct VeDirectHexData) to check if a message fits.
|
||||||
*
|
*
|
||||||
* 2024.03.08 - 0.4 - adds the ability to send hex commands and to parse hex messages
|
* 2024.03.08 - 0.4 - adds the ability to send hex commands and to parse hex messages
|
||||||
@ -63,9 +63,9 @@ static uint32_t AsciiHexLE2Int(const char *ascii, const uint8_t anz) {
|
|||||||
* disassembleHexData()
|
* disassembleHexData()
|
||||||
* analysis the hex message and extract: response, address, flags and value/text
|
* analysis the hex message and extract: response, address, flags and value/text
|
||||||
* buffer: pointer to message (ascii hex little endian format)
|
* buffer: pointer to message (ascii hex little endian format)
|
||||||
* data: disassembeled message
|
* data: disassembled message
|
||||||
* return: true = successful disassembeld, false = hex sum fault or message
|
* return: true = successful disassembled, false = hex sum fault or message
|
||||||
* do not aligin with VE.Diekt syntax
|
* do not align with VE.Direct syntax
|
||||||
*/
|
*/
|
||||||
template<typename T>
|
template<typename T>
|
||||||
bool VeDirectFrameHandler<T>::disassembleHexData(VeDirectHexData &data) {
|
bool VeDirectFrameHandler<T>::disassembleHexData(VeDirectHexData &data) {
|
||||||
@ -164,14 +164,14 @@ static String Int2HexLEString(uint32_t value, uint8_t anz) {
|
|||||||
* addr: register address, default 0
|
* addr: register address, default 0
|
||||||
* value: value to write into a register, default 0
|
* value: value to write into a register, default 0
|
||||||
* valsize: size of the value, 8, 16 or 32 bit, default 0
|
* valsize: size of the value, 8, 16 or 32 bit, default 0
|
||||||
* return: true = message assembeld and send, false = it was not possible to put the message together
|
* return: true = message assembled and send, false = it was not possible to put the message together
|
||||||
* SAMPLE: ping command: sendHexCommand(PING),
|
* SAMPLE: ping command: sendHexCommand(PING),
|
||||||
* read total DC input power sendHexCommand(GET, 0xEDEC)
|
* read total DC input power sendHexCommand(GET, 0xEDEC)
|
||||||
* set Charge current limit 10A sendHexCommand(SET, 0x2015, 64, 16)
|
* set Charge current limit 10A sendHexCommand(SET, 0x2015, 64, 16)
|
||||||
*
|
*
|
||||||
* WARNING: some values are stored in non-volatile memory. Continuous writing, for example from a control loop, will
|
* WARNING: some values are stored in non-volatile memory. Continuous writing, for example from a control loop, will
|
||||||
* lead to early failure.
|
* lead to early failure.
|
||||||
* On MPPT for example 0xEDE0 - 0xEDFF. Check the Vivtron doc "BlueSolar-HEX-protocol.pdf"
|
* On MPPT for example 0xEDE0 - 0xEDFF. Check the Victron doc "BlueSolar-HEX-protocol.pdf"
|
||||||
*/
|
*/
|
||||||
template<typename T>
|
template<typename T>
|
||||||
bool VeDirectFrameHandler<T>::sendHexCommand(VeDirectHexCommand cmd, VeDirectHexRegister addr, uint32_t value, uint8_t valsize) {
|
bool VeDirectFrameHandler<T>::sendHexCommand(VeDirectHexCommand cmd, VeDirectHexRegister addr, uint32_t value, uint8_t valsize) {
|
||||||
|
|||||||
@ -104,35 +104,23 @@ void VeDirectMpptController::frameValidEvent() {
|
|||||||
} else {
|
} else {
|
||||||
_tmpFrame.mpptEfficiency_Percent = 0.0f;
|
_tmpFrame.mpptEfficiency_Percent = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_canSend) { return; }
|
|
||||||
|
|
||||||
// Copy from the "VE.Direct Protocol" documentation
|
|
||||||
// For firmware version v1.52 and below, when no VE.Direct queries are sent to the device, the
|
|
||||||
// charger periodically sends human readable (TEXT) data to the serial port. For firmware
|
|
||||||
// versions v1.53 and above, the charger always periodically sends TEXT data to the serial port.
|
|
||||||
// --> We just use hex commandes for firmware >= 1.53 to keep text messages alive
|
|
||||||
if (_tmpFrame.getFwVersionAsInteger() < 153) { return; }
|
|
||||||
|
|
||||||
using Command = VeDirectHexCommand;
|
|
||||||
using Register = VeDirectHexRegister;
|
|
||||||
|
|
||||||
sendHexCommand(Command::GET, Register::ChargeControllerTemperature);
|
|
||||||
sendHexCommand(Command::GET, Register::SmartBatterySenseTemperature);
|
|
||||||
sendHexCommand(Command::GET, Register::NetworkTotalDcInputPower);
|
|
||||||
|
|
||||||
#ifdef PROCESS_NETWORK_STATE
|
|
||||||
sendHexCommand(Command::GET, Register::NetworkInfo);
|
|
||||||
sendHexCommand(Command::GET, Register::NetworkMode);
|
|
||||||
sendHexCommand(Command::GET, Register::NetworkStatus);
|
|
||||||
#endif // PROCESS_NETWORK_STATE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void VeDirectMpptController::loop()
|
void VeDirectMpptController::loop()
|
||||||
{
|
{
|
||||||
|
// First we send HEX-Commands (timing improvement)
|
||||||
|
if (isHexCommandPossible()) {
|
||||||
|
sendNextHexCommandFromQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second we read Text- and HEX-Messages
|
||||||
VeDirectFrameHandler::loop();
|
VeDirectFrameHandler::loop();
|
||||||
|
|
||||||
|
// Third we check if HEX-Data is outdated
|
||||||
|
// Note: Room for improvement, longer data valid time for slow changing values?
|
||||||
|
if (!isHexCommandPossible()) { return; }
|
||||||
|
|
||||||
auto resetTimestamp = [this](auto& pair) {
|
auto resetTimestamp = [this](auto& pair) {
|
||||||
if (pair.first > 0 && (millis() - pair.first) > (10 * 1000)) {
|
if (pair.first > 0 && (millis() - pair.first) > (10 * 1000)) {
|
||||||
pair.first = 0;
|
pair.first = 0;
|
||||||
@ -142,6 +130,8 @@ void VeDirectMpptController::loop()
|
|||||||
resetTimestamp(_tmpFrame.MpptTemperatureMilliCelsius);
|
resetTimestamp(_tmpFrame.MpptTemperatureMilliCelsius);
|
||||||
resetTimestamp(_tmpFrame.SmartBatterySenseTemperatureMilliCelsius);
|
resetTimestamp(_tmpFrame.SmartBatterySenseTemperatureMilliCelsius);
|
||||||
resetTimestamp(_tmpFrame.NetworkTotalDcInputPowerMilliWatts);
|
resetTimestamp(_tmpFrame.NetworkTotalDcInputPowerMilliWatts);
|
||||||
|
resetTimestamp(_tmpFrame.BatteryFloatMilliVolt);
|
||||||
|
resetTimestamp(_tmpFrame.BatteryAbsorptionMilliVolt);
|
||||||
|
|
||||||
#ifdef PROCESS_NETWORK_STATE
|
#ifdef PROCESS_NETWORK_STATE
|
||||||
resetTimestamp(_tmpFrame.NetworkInfo);
|
resetTimestamp(_tmpFrame.NetworkInfo);
|
||||||
@ -153,8 +143,8 @@ void VeDirectMpptController::loop()
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* hexDataHandler()
|
* hexDataHandler()
|
||||||
* analyse the content of VE.Direct hex messages
|
* analyze the content of VE.Direct hex messages
|
||||||
* Handels the received hex data from the MPPT
|
* handles the received hex data from the MPPT
|
||||||
*/
|
*/
|
||||||
bool VeDirectMpptController::hexDataHandler(VeDirectHexData const &data) {
|
bool VeDirectMpptController::hexDataHandler(VeDirectHexData const &data) {
|
||||||
if (data.rsp != VeDirectHexResponse::GET &&
|
if (data.rsp != VeDirectHexResponse::GET &&
|
||||||
@ -162,6 +152,11 @@ bool VeDirectMpptController::hexDataHandler(VeDirectHexData const &data) {
|
|||||||
|
|
||||||
auto regLog = static_cast<uint16_t>(data.addr);
|
auto regLog = static_cast<uint16_t>(data.addr);
|
||||||
|
|
||||||
|
// we check whether the answer matches a previously asked query
|
||||||
|
if ((data.rsp == VeDirectHexResponse::GET) && (data.addr == _hexQueue[_sendQueueNr]._hexRegister)) {
|
||||||
|
_sendTimeout = 0;
|
||||||
|
}
|
||||||
|
|
||||||
switch (data.addr) {
|
switch (data.addr) {
|
||||||
case VeDirectHexRegister::ChargeControllerTemperature:
|
case VeDirectHexRegister::ChargeControllerTemperature:
|
||||||
_tmpFrame.MpptTemperatureMilliCelsius =
|
_tmpFrame.MpptTemperatureMilliCelsius =
|
||||||
@ -215,6 +210,29 @@ bool VeDirectMpptController::hexDataHandler(VeDirectHexData const &data) {
|
|||||||
return true;
|
return true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case VeDirectHexRegister::BatteryAbsorptionVoltage:
|
||||||
|
_tmpFrame.BatteryAbsorptionMilliVolt =
|
||||||
|
{ millis(), static_cast<uint32_t>(data.value) * 10 };
|
||||||
|
if (_verboseLogging) {
|
||||||
|
_msgOut->printf("%s Hex Data: MPPT Absorption Voltage (0x%04X): %.2fV\r\n",
|
||||||
|
_logId, regLog,
|
||||||
|
_tmpFrame.BatteryAbsorptionMilliVolt.second / 1000.0);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VeDirectHexRegister::BatteryFloatVoltage:
|
||||||
|
_tmpFrame.BatteryFloatMilliVolt =
|
||||||
|
{ millis(), static_cast<uint32_t>(data.value) * 10 };
|
||||||
|
|
||||||
|
if (_verboseLogging) {
|
||||||
|
_msgOut->printf("%s Hex Data: MPPT Float Voltage (0x%04X): %.2fV\r\n",
|
||||||
|
_logId, regLog,
|
||||||
|
_tmpFrame.BatteryFloatMilliVolt.second / 1000.0);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
|
||||||
#ifdef PROCESS_NETWORK_STATE
|
#ifdef PROCESS_NETWORK_STATE
|
||||||
case VeDirectHexRegister::NetworkInfo:
|
case VeDirectHexRegister::NetworkInfo:
|
||||||
_tmpFrame.NetworkInfo =
|
_tmpFrame.NetworkInfo =
|
||||||
@ -257,3 +275,66 @@ bool VeDirectMpptController::hexDataHandler(VeDirectHexData const &data) {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* isHexCommandPossible()
|
||||||
|
* return: true = yes we can use Hex-Commands
|
||||||
|
*/
|
||||||
|
bool VeDirectMpptController::isHexCommandPossible(void) {
|
||||||
|
// Copy from the "VE.Direct Protocol" documentation
|
||||||
|
// For firmware version v1.52 and below, when no VE.Direct queries are sent to the device, the
|
||||||
|
// charger periodically sends human readable (TEXT) data to the serial port. For firmware
|
||||||
|
// versions v1.53 and above, the charger always periodically sends TEXT data to the serial port.
|
||||||
|
// --> We just use hex commands for firmware >= 1.53 to keep text messages alive
|
||||||
|
return (_canSend && (_tmpFrame.getFwVersionAsInteger() >= 153));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* sendNextHexCommandFromQueue()
|
||||||
|
* send one Hex Commands from the Hex Command Queue
|
||||||
|
* handles the received hex data from the MPPT
|
||||||
|
*/
|
||||||
|
void VeDirectMpptController::sendNextHexCommandFromQueue(void) {
|
||||||
|
// It seems some commands get lost if we send to fast the next command.
|
||||||
|
// maybe we produce an overflow on the MPPT receive buffer or we have to
|
||||||
|
// wait for the MPPT answer before we can send the next command. We only
|
||||||
|
// send a new query in VE.Direct idle state and if no query is pending. In
|
||||||
|
// case we do not get an answer we send the next query from the queue after
|
||||||
|
// a timeout of 500ms. NOTE: _sendTimeout will be set to 0 after receiving
|
||||||
|
// an answer, see function hexDataHandler().
|
||||||
|
auto millisTime = millis();
|
||||||
|
if (isStateIdle() && ((millisTime - _hexQueue[_sendQueueNr]._lastSendTime) > _sendTimeout)) {
|
||||||
|
|
||||||
|
// we do 2 loops, first for high prio commands and second for low prio commands
|
||||||
|
bool prio = true;
|
||||||
|
for (auto idy = 0; idy < 2; ++idy) {
|
||||||
|
|
||||||
|
// we start searching the queue with the next queue index
|
||||||
|
auto idx = _sendQueueNr + 1;
|
||||||
|
if (idx >= _hexQueue.size()) { idx = 0; }
|
||||||
|
|
||||||
|
do {
|
||||||
|
// we check if it is time to send the command again
|
||||||
|
if (((prio && (_hexQueue[idx]._readPeriod == HIGH_PRIO_COMMAND)) ||
|
||||||
|
(!prio && (_hexQueue[idx]._readPeriod != HIGH_PRIO_COMMAND))) &&
|
||||||
|
(millisTime - _hexQueue[idx]._lastSendTime) > (_hexQueue[idx]._readPeriod * 1000)) {
|
||||||
|
|
||||||
|
sendHexCommand(VeDirectHexCommand::GET, _hexQueue[idx]._hexRegister);
|
||||||
|
_hexQueue[idx]._lastSendTime = millisTime;
|
||||||
|
|
||||||
|
// we need this information to check if we get an answer, see hexDataHandler()
|
||||||
|
_sendTimeout = 500;
|
||||||
|
_sendQueueNr = idx;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
++idx;
|
||||||
|
if (idx == _hexQueue.size()) { idx = 0; }
|
||||||
|
} while (idx != _sendQueueNr);
|
||||||
|
|
||||||
|
prio = false; // second loop for low prio commands
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -36,6 +36,12 @@ private:
|
|||||||
size_t _count;
|
size_t _count;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct VeDirectHexQueue {
|
||||||
|
VeDirectHexRegister _hexRegister; // hex register
|
||||||
|
uint8_t _readPeriod; // time period in sec until we send the command again
|
||||||
|
uint32_t _lastSendTime; // time stamp in milli sec of last send
|
||||||
|
};
|
||||||
|
|
||||||
class VeDirectMpptController : public VeDirectFrameHandler<veMpptStruct> {
|
class VeDirectMpptController : public VeDirectFrameHandler<veMpptStruct> {
|
||||||
public:
|
public:
|
||||||
VeDirectMpptController() = default;
|
VeDirectMpptController() = default;
|
||||||
@ -51,5 +57,18 @@ private:
|
|||||||
bool hexDataHandler(VeDirectHexData const &data) final;
|
bool hexDataHandler(VeDirectHexData const &data) final;
|
||||||
bool processTextDataDerived(std::string const& name, std::string const& value) final;
|
bool processTextDataDerived(std::string const& name, std::string const& value) final;
|
||||||
void frameValidEvent() final;
|
void frameValidEvent() final;
|
||||||
|
void sendNextHexCommandFromQueue(void);
|
||||||
|
bool isHexCommandPossible(void);
|
||||||
MovingAverage<float, 5> _efficiency;
|
MovingAverage<float, 5> _efficiency;
|
||||||
|
|
||||||
|
uint32_t _sendTimeout = 0; // timeout until we send the next command from the queue
|
||||||
|
size_t _sendQueueNr = 0; // actual queue position;
|
||||||
|
|
||||||
|
// for slow changing values we use a send time period of 4 sec
|
||||||
|
#define HIGH_PRIO_COMMAND 1
|
||||||
|
std::array<VeDirectHexQueue, 5> _hexQueue { VeDirectHexRegister::NetworkTotalDcInputPower, HIGH_PRIO_COMMAND, 0,
|
||||||
|
VeDirectHexRegister::ChargeControllerTemperature, 4, 0,
|
||||||
|
VeDirectHexRegister::SmartBatterySenseTemperature, 4, 0,
|
||||||
|
VeDirectHexRegister::BatteryFloatVoltage, 4, 0,
|
||||||
|
VeDirectHexRegister::BatteryAbsorptionVoltage, 4, 0 };
|
||||||
};
|
};
|
||||||
|
|||||||
@ -138,6 +138,8 @@ void MqttHandleVedirectClass::publish_mppt_data(const VeDirectMpptController::da
|
|||||||
|
|
||||||
PUBLISH_OPT(NetworkTotalDcInputPowerMilliWatts, "NetworkTotalDcInputPower", currentData.NetworkTotalDcInputPowerMilliWatts.second / 1000.0);
|
PUBLISH_OPT(NetworkTotalDcInputPowerMilliWatts, "NetworkTotalDcInputPower", currentData.NetworkTotalDcInputPowerMilliWatts.second / 1000.0);
|
||||||
PUBLISH_OPT(MpptTemperatureMilliCelsius, "MpptTemperature", currentData.MpptTemperatureMilliCelsius.second / 1000.0);
|
PUBLISH_OPT(MpptTemperatureMilliCelsius, "MpptTemperature", currentData.MpptTemperatureMilliCelsius.second / 1000.0);
|
||||||
|
PUBLISH_OPT(BatteryAbsorptionMilliVolt, "BatteryAbsorption", currentData.BatteryAbsorptionMilliVolt.second / 1000.0);
|
||||||
|
PUBLISH_OPT(BatteryFloatMilliVolt, "BatteryFloat", currentData.BatteryFloatMilliVolt.second / 1000.0);
|
||||||
PUBLISH_OPT(SmartBatterySenseTemperatureMilliCelsius, "SmartBatterySenseTemperature", currentData.SmartBatterySenseTemperatureMilliCelsius.second / 1000.0);
|
PUBLISH_OPT(SmartBatterySenseTemperatureMilliCelsius, "SmartBatterySenseTemperature", currentData.SmartBatterySenseTemperatureMilliCelsius.second / 1000.0);
|
||||||
#undef PUBLILSH_OPT
|
#undef PUBLILSH_OPT
|
||||||
}
|
}
|
||||||
|
|||||||
@ -95,6 +95,12 @@ void MqttHandleVedirectHassClass::publishConfig()
|
|||||||
if (optMpptData->MpptTemperatureMilliCelsius.first != 0) {
|
if (optMpptData->MpptTemperatureMilliCelsius.first != 0) {
|
||||||
publishSensor("MPPT temperature", "mdi:temperature-celsius", "MpptTemperature", "temperature", "measurement", "°C", *optMpptData);
|
publishSensor("MPPT temperature", "mdi:temperature-celsius", "MpptTemperature", "temperature", "measurement", "°C", *optMpptData);
|
||||||
}
|
}
|
||||||
|
if (optMpptData->BatteryAbsorptionMilliVolt.first != 0) {
|
||||||
|
publishSensor("Battery absorption voltage", "mdi:battery-charging-90", "BatteryAbsorption", "voltage", "measurement", "V", *optMpptData);
|
||||||
|
}
|
||||||
|
if (optMpptData->BatteryFloatMilliVolt.first != 0) {
|
||||||
|
publishSensor("Battery float voltage", "mdi:battery-charging-100", "BatteryFloat", "voltage", "measurement", "V", *optMpptData);
|
||||||
|
}
|
||||||
if (optMpptData->SmartBatterySenseTemperatureMilliCelsius.first != 0) {
|
if (optMpptData->SmartBatterySenseTemperatureMilliCelsius.first != 0) {
|
||||||
publishSensor("Smart Battery Sense temperature", "mdi:temperature-celsius", "SmartBatterySenseTemperature", "temperature", "measurement", "°C", *optMpptData);
|
publishSensor("Smart Battery Sense temperature", "mdi:temperature-celsius", "SmartBatterySenseTemperature", "temperature", "measurement", "°C", *optMpptData);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -226,3 +226,39 @@ float VictronMpptClass::getOutputVoltage() const
|
|||||||
|
|
||||||
return min;
|
return min;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<uint8_t> VictronMpptClass::getStateOfOperation() const
|
||||||
|
{
|
||||||
|
for (const auto& upController : _controllers) {
|
||||||
|
if (upController->isDataValid()) {
|
||||||
|
return upController->getData().currentState_CS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<float> VictronMpptClass::getVoltage(MPPTVoltage kindOf) const
|
||||||
|
{
|
||||||
|
for (const auto& upController : _controllers) {
|
||||||
|
switch (kindOf) {
|
||||||
|
case MPPTVoltage::ABSORPTION: {
|
||||||
|
auto const& absorptionVoltage = upController->getData().BatteryAbsorptionMilliVolt;
|
||||||
|
if (absorptionVoltage.first > 0) { return absorptionVoltage.second; }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MPPTVoltage::FLOAT: {
|
||||||
|
auto const& floatVoltage = upController->getData().BatteryFloatMilliVolt;
|
||||||
|
if (floatVoltage.first > 0) { return floatVoltage.second; }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MPPTVoltage::BATTERY: {
|
||||||
|
auto const& batteryVoltage = upController->getData().batteryVoltage_V_mV;
|
||||||
|
if (upController->isDataValid()) { return batteryVoltage; }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|||||||
@ -195,6 +195,16 @@ void WebApiWsVedirectLiveClass::populateJson(const JsonObject &root, const VeDir
|
|||||||
output["SBSTemperature"]["u"] = "°C";
|
output["SBSTemperature"]["u"] = "°C";
|
||||||
output["SBSTemperature"]["d"] = "0";
|
output["SBSTemperature"]["d"] = "0";
|
||||||
}
|
}
|
||||||
|
if (mpptData.BatteryAbsorptionMilliVolt.first > 0) {
|
||||||
|
output["AbsorptionVoltage"]["v"] = mpptData.BatteryAbsorptionMilliVolt.second / 1000.0;
|
||||||
|
output["AbsorptionVoltage"]["u"] = "V";
|
||||||
|
output["AbsorptionVoltage"]["d"] = "2";
|
||||||
|
}
|
||||||
|
if (mpptData.BatteryFloatMilliVolt.first > 0) {
|
||||||
|
output["FloatVoltage"]["v"] = mpptData.BatteryFloatMilliVolt.second / 1000.0;
|
||||||
|
output["FloatVoltage"]["u"] = "V";
|
||||||
|
output["FloatVoltage"]["d"] = "2";
|
||||||
|
}
|
||||||
|
|
||||||
const JsonObject input = values["input"].to<JsonObject>();
|
const JsonObject input = values["input"].to<JsonObject>();
|
||||||
if (mpptData.NetworkTotalDcInputPowerMilliWatts.first > 0) {
|
if (mpptData.NetworkTotalDcInputPowerMilliWatts.first > 0) {
|
||||||
|
|||||||
@ -188,6 +188,8 @@
|
|||||||
"V": "Spannung",
|
"V": "Spannung",
|
||||||
"I": "Strom",
|
"I": "Strom",
|
||||||
"E": "Effizienz (berechnet)",
|
"E": "Effizienz (berechnet)",
|
||||||
|
"AbsorptionVoltage": "Absorptionsspannung",
|
||||||
|
"FloatVoltage": "Erhaltungsspannung",
|
||||||
"SBSTemperature": "SBS Temperatur"
|
"SBSTemperature": "SBS Temperatur"
|
||||||
},
|
},
|
||||||
"section_input": "Eingang (Solarpanele)",
|
"section_input": "Eingang (Solarpanele)",
|
||||||
|
|||||||
@ -188,6 +188,8 @@
|
|||||||
"V": "Voltage",
|
"V": "Voltage",
|
||||||
"I": "Current",
|
"I": "Current",
|
||||||
"E": "Efficiency (calculated)",
|
"E": "Efficiency (calculated)",
|
||||||
|
"AbsorptionVoltage": "Absorption voltage",
|
||||||
|
"FloatVoltage": "Float voltage",
|
||||||
"SBSTemperature": "SBS temperature"
|
"SBSTemperature": "SBS temperature"
|
||||||
},
|
},
|
||||||
"section_input": "Input (Solar Panels)",
|
"section_input": "Input (Solar Panels)",
|
||||||
|
|||||||
@ -188,6 +188,8 @@
|
|||||||
"V": "Voltage",
|
"V": "Voltage",
|
||||||
"I": "Current",
|
"I": "Current",
|
||||||
"E": "Efficiency (calculated)",
|
"E": "Efficiency (calculated)",
|
||||||
|
"AbsorptionVoltage": "Absorption voltage",
|
||||||
|
"FloatVoltage": "Float voltage",
|
||||||
"SBSTemperature": "SBS temperature"
|
"SBSTemperature": "SBS temperature"
|
||||||
},
|
},
|
||||||
"section_input": "Input (Solar Panels)",
|
"section_input": "Input (Solar Panels)",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user