From 4e4cdf5e28feb86b8ae6cb3345baf2b0928d4866 Mon Sep 17 00:00:00 2001 From: SW-Nico Date: Mon, 29 Jul 2024 20:49:03 +0200 Subject: [PATCH 1/7] get absorption- and float-voltage from MPPTs --- include/VictronMppt.h | 10 ++++ lib/VeDirectFrameHandler/VeDirectData.cpp | 15 ++++-- lib/VeDirectFrameHandler/VeDirectData.h | 13 +++++- .../VeDirectFrameHexHandler.cpp | 12 ++--- .../VeDirectMpptController.cpp | 46 +++++++++++++++---- .../VeDirectMpptController.h | 6 +++ src/VictronMppt.cpp | 42 +++++++++++++++++ 7 files changed, 125 insertions(+), 19 deletions(-) diff --git a/include/VictronMppt.h b/include/VictronMppt.h index 963a46cd9..4ef9d0c69 100644 --- a/include/VictronMppt.h +++ b/include/VictronMppt.h @@ -42,6 +42,16 @@ class VictronMpptClass { // minimum of all MPPT charge controllers' output voltages in V float getOutputVoltage() const; + // returns the state of operation from the first available controller + int16_t getStateOfOperation() const; + + // the configured value from the first available controller in V + enum class MPPTVoltage : uint8_t { + ABSORPTION = 0, + FLOAT = 1, + }; + float getVoltage(MPPTVoltage kindOf) const; + private: void loop(); VictronMpptClass(VictronMpptClass const& other) = delete; diff --git a/lib/VeDirectFrameHandler/VeDirectData.cpp b/lib/VeDirectFrameHandler/VeDirectData.cpp index a67175fde..74e4bc484 100644 --- a/lib/VeDirectFrameHandler/VeDirectData.cpp +++ b/lib/VeDirectFrameHandler/VeDirectData.cpp @@ -191,7 +191,7 @@ frozen::string const& veMpptStruct::getCsAsString() const { 0, "OFF" }, { 2, "Fault" }, { 3, "Bulk" }, - { 4, "Absorbtion" }, + { 4, "Absorption" }, { 5, "Float" }, { 7, "Equalize (manual)" }, { 245, "Starting-up" }, @@ -287,18 +287,27 @@ frozen::string const& VeDirectHexData::getResponseAsString() const frozen::string const& VeDirectHexData::getRegisterAsString() const { using Register = VeDirectHexRegister; - static constexpr frozen::map values = { + static constexpr frozen::map values = { { Register::DeviceMode, "Device Mode" }, { Register::DeviceState, "Device State" }, { Register::RemoteControlUsed, "Remote Control Used" }, { Register::PanelVoltage, "Panel Voltage" }, + { Register::PanelPower, "Panel Power" }, { Register::ChargerVoltage, "Charger Voltage" }, + { Register::ChargerCurrent, "Charger Current" }, { Register::NetworkTotalDcInputPower, "Network Total DC Input Power" }, { Register::ChargeControllerTemperature, "Charger Controller Temperature" }, { Register::SmartBatterySenseTemperature, "Smart Battery Sense Temperature" }, { Register::NetworkInfo, "Network Info" }, { 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); diff --git a/lib/VeDirectFrameHandler/VeDirectData.h b/lib/VeDirectFrameHandler/VeDirectData.h index 34c6b8991..9cd97ad61 100644 --- a/lib/VeDirectFrameHandler/VeDirectData.h +++ b/lib/VeDirectFrameHandler/VeDirectData.h @@ -45,6 +45,8 @@ struct veMpptStruct : veStruct { std::pair MpptTemperatureMilliCelsius; std::pair SmartBatterySenseTemperatureMilliCelsius; std::pair NetworkTotalDcInputPowerMilliWatts; + std::pair BatteryAbsorptionMilliVolt; + std::pair BatteryFloatMilliVolt; std::pair NetworkInfo; std::pair NetworkMode; std::pair NetworkStatus; @@ -121,7 +123,9 @@ enum class VeDirectHexRegister : uint16_t { DeviceState = 0x0201, RemoteControlUsed = 0x0202, PanelVoltage = 0xEDBB, + PanelPower = 0xEDBC, ChargerVoltage = 0xEDD5, + ChargerCurrent = 0xEDD7, NetworkTotalDcInputPower = 0x2027, ChargeControllerTemperature = 0xEDDB, SmartBatterySenseTemperature = 0xEDEC, @@ -129,7 +133,14 @@ enum class VeDirectHexRegister : uint16_t { NetworkMode = 0x200E, NetworkStatus = 0x200F, HistoryTotal = 0x104F, - HistoryMPPTD30 = 0x10BE + HistoryMPPTD30 = 0x10BE, + BatteryAbsorptionVoltage = 0xEDF7, + BatteryFloatVoltage = 0xEDF6, + TotalChargeCurrent = 0x2013, + ChargeStateElapsedTime= 0x2007, + BatteryVoltageSense = 0x2002, + LoadCurrent = 0xEDAD, + LoadOutputVoltage = 0xEDA9 }; struct VeDirectHexData { diff --git a/lib/VeDirectFrameHandler/VeDirectFrameHexHandler.cpp b/lib/VeDirectFrameHandler/VeDirectFrameHexHandler.cpp index 392d2f8a9..8256f1eae 100644 --- a/lib/VeDirectFrameHandler/VeDirectFrameHexHandler.cpp +++ b/lib/VeDirectFrameHandler/VeDirectFrameHexHandler.cpp @@ -8,7 +8,7 @@ HexHandler.cpp * 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 * 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. * * 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() * analysis the hex message and extract: response, address, flags and value/text * buffer: pointer to message (ascii hex little endian format) - * data: disassembeled message - * return: true = successful disassembeld, false = hex sum fault or message - * do not aligin with VE.Diekt syntax + * data: disassembled message + * return: true = successful disassembled, false = hex sum fault or message + * do not align with VE.Direct syntax */ template bool VeDirectFrameHandler::disassembleHexData(VeDirectHexData &data) { @@ -164,14 +164,14 @@ static String Int2HexLEString(uint32_t value, uint8_t anz) { * addr: register address, default 0 * value: value to write into a register, 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), * read total DC input power sendHexCommand(GET, 0xEDEC) * 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 * 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 bool VeDirectFrameHandler::sendHexCommand(VeDirectHexCommand cmd, VeDirectHexRegister addr, uint32_t value, uint8_t valsize) { diff --git a/lib/VeDirectFrameHandler/VeDirectMpptController.cpp b/lib/VeDirectFrameHandler/VeDirectMpptController.cpp index e0f386db6..cbefb710a 100644 --- a/lib/VeDirectFrameHandler/VeDirectMpptController.cpp +++ b/lib/VeDirectFrameHandler/VeDirectMpptController.cpp @@ -111,15 +111,18 @@ void VeDirectMpptController::frameValidEvent() { // 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 + // --> We just use hex commands 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); + // 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. + // Now we send only one command after every text-mode-frame. + // We need a better solution if we add more commands + // Don't worry about the NetworkTotalDcInputPower. We get anyway asynchronous messages on every value change + sendHexCommand(VeDirectHexCommand::GET, _slotRegister[_slotNr++]); + if (_slotNr >= _slotRegister.size()) + _slotNr = 0; #ifdef PROCESS_NETWORK_STATE sendHexCommand(Command::GET, Register::NetworkInfo); @@ -142,6 +145,8 @@ void VeDirectMpptController::loop() resetTimestamp(_tmpFrame.MpptTemperatureMilliCelsius); resetTimestamp(_tmpFrame.SmartBatterySenseTemperatureMilliCelsius); resetTimestamp(_tmpFrame.NetworkTotalDcInputPowerMilliWatts); + resetTimestamp(_tmpFrame.BatteryFloatMilliVolt); + resetTimestamp(_tmpFrame.BatteryAbsorptionMilliVolt); #ifdef PROCESS_NETWORK_STATE resetTimestamp(_tmpFrame.NetworkInfo); @@ -153,8 +158,8 @@ void VeDirectMpptController::loop() /* * hexDataHandler() - * analyse the content of VE.Direct hex messages - * Handels the received hex data from the MPPT + * analyze the content of VE.Direct hex messages + * handel's the received hex data from the MPPT */ bool VeDirectMpptController::hexDataHandler(VeDirectHexData const &data) { if (data.rsp != VeDirectHexResponse::GET && @@ -215,6 +220,29 @@ bool VeDirectMpptController::hexDataHandler(VeDirectHexData const &data) { return true; break; + case VeDirectHexRegister::BatteryAbsorptionVoltage: + _tmpFrame.BatteryAbsorptionMilliVolt = + { millis(), static_cast(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(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 case VeDirectHexRegister::NetworkInfo: _tmpFrame.NetworkInfo = diff --git a/lib/VeDirectFrameHandler/VeDirectMpptController.h b/lib/VeDirectFrameHandler/VeDirectMpptController.h index b8bd4c72f..44c97d6a5 100644 --- a/lib/VeDirectFrameHandler/VeDirectMpptController.h +++ b/lib/VeDirectFrameHandler/VeDirectMpptController.h @@ -52,4 +52,10 @@ class VeDirectMpptController : public VeDirectFrameHandler { bool processTextDataDerived(std::string const& name, std::string const& value) final; void frameValidEvent() final; MovingAverage _efficiency; + int8_t _slotNr = 0; + std::array _slotRegister { VeDirectHexRegister::NetworkTotalDcInputPower, + VeDirectHexRegister::ChargeControllerTemperature, + VeDirectHexRegister::SmartBatterySenseTemperature, + VeDirectHexRegister::BatteryFloatVoltage, + VeDirectHexRegister::BatteryAbsorptionVoltage }; }; diff --git a/src/VictronMppt.cpp b/src/VictronMppt.cpp index 4e084974c..e2160387b 100644 --- a/src/VictronMppt.cpp +++ b/src/VictronMppt.cpp @@ -85,6 +85,7 @@ bool VictronMpptClass::isDataValid() const for (auto const& upController: _controllers) { if (upController->isDataValid()) { return true; } + if (upController->isDataValid()) { return true; } } return !_controllers.empty(); @@ -226,3 +227,44 @@ float VictronMpptClass::getOutputVoltage() const return min; } + +/* + * getStateOfOperation() + * return: the state from the first available controller or + * -1 if data is not available + */ +int16_t VictronMpptClass::getStateOfOperation() const +{ + for (const auto& upController : _controllers) { + if (upController->isDataValid()) + return static_cast(upController->getData().currentState_CS); + } + + return -1; +} + +/* + * getVoltage() + * return: the configured value from the first available controller in V or + * -1V if data is not available + */ +float VictronMpptClass::getVoltage(MPPTVoltage kindOf) const +{ + std::pair voltX; + + for (const auto& upController : _controllers) { + switch (kindOf) { + case MPPTVoltage::ABSORPTION: + voltX = upController->getData().BatteryAbsorptionMilliVolt; + break; + case MPPTVoltage::FLOAT: + voltX = upController->getData().BatteryFloatMilliVolt; + break; + } + if (voltX.first > 0) { + return static_cast(voltX.second / 1000.0); + } + } + + return -1.0f; +} From eb60db8970e8e6085a9e5ab28c63de9cb744e841 Mon Sep 17 00:00:00 2001 From: SW-Nico Date: Thu, 1 Aug 2024 14:33:30 +0200 Subject: [PATCH 2/7] fix of RAII fault --- src/VictronMppt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VictronMppt.cpp b/src/VictronMppt.cpp index e2160387b..aedc7b272 100644 --- a/src/VictronMppt.cpp +++ b/src/VictronMppt.cpp @@ -250,7 +250,7 @@ int16_t VictronMpptClass::getStateOfOperation() const */ float VictronMpptClass::getVoltage(MPPTVoltage kindOf) const { - std::pair voltX; + std::pair voltX {0,0}; for (const auto& upController : _controllers) { switch (kindOf) { From 2f15160a98793d3cccbf38a9388432e83cf8e607 Mon Sep 17 00:00:00 2001 From: SW-Nico Date: Thu, 1 Aug 2024 14:42:09 +0200 Subject: [PATCH 3/7] remove of double if() --- src/VictronMppt.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/VictronMppt.cpp b/src/VictronMppt.cpp index aedc7b272..d123a72e5 100644 --- a/src/VictronMppt.cpp +++ b/src/VictronMppt.cpp @@ -85,7 +85,6 @@ bool VictronMpptClass::isDataValid() const for (auto const& upController: _controllers) { if (upController->isDataValid()) { return true; } - if (upController->isDataValid()) { return true; } } return !_controllers.empty(); From 7cdb681afd779b302de3f79f9211acff3907ff2e Mon Sep 17 00:00:00 2001 From: SW-Nico Date: Thu, 8 Aug 2024 22:45:26 +0200 Subject: [PATCH 4/7] Send hex commands in pre defined periods --- .../VeDirectFrameHandler.h | 3 +- .../VeDirectMpptController.cpp | 59 ++++++++++++------- .../VeDirectMpptController.h | 23 ++++++-- 3 files changed, 57 insertions(+), 28 deletions(-) diff --git a/lib/VeDirectFrameHandler/VeDirectFrameHandler.h b/lib/VeDirectFrameHandler/VeDirectFrameHandler.h index 244caf3f3..86636e87c 100644 --- a/lib/VeDirectFrameHandler/VeDirectFrameHandler.h +++ b/lib/VeDirectFrameHandler/VeDirectFrameHandler.h @@ -27,12 +27,13 @@ class VeDirectFrameHandler { bool isDataValid() const; // return true if data valid and not outdated T const& getData() const { return _tmpFrame; } bool sendHexCommand(VeDirectHexCommand cmd, VeDirectHexRegister addr, uint32_t value = 0, uint8_t valsize = 0); + bool isStateIdle() const { return (_state == State::IDLE); } protected: VeDirectFrameHandler(); void init(char const* who, int8_t rx, int8_t tx, Print* msgOut, 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; Print* _msgOut; diff --git a/lib/VeDirectFrameHandler/VeDirectMpptController.cpp b/lib/VeDirectFrameHandler/VeDirectMpptController.cpp index cbefb710a..398ef5c74 100644 --- a/lib/VeDirectFrameHandler/VeDirectMpptController.cpp +++ b/lib/VeDirectFrameHandler/VeDirectMpptController.cpp @@ -104,38 +104,51 @@ void VeDirectMpptController::frameValidEvent() { } else { _tmpFrame.mpptEfficiency_Percent = 0.0f; } +} - if (!_canSend) { return; } +void VeDirectMpptController::loop() +{ // 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 - if (_tmpFrame.getFwVersionAsInteger() < 153) { return; } - - // 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. - // Now we send only one command after every text-mode-frame. - // We need a better solution if we add more commands - // Don't worry about the NetworkTotalDcInputPower. We get anyway asynchronous messages on every value change - sendHexCommand(VeDirectHexCommand::GET, _slotRegister[_slotNr++]); - if (_slotNr >= _slotRegister.size()) - _slotNr = 0; - -#ifdef PROCESS_NETWORK_STATE - sendHexCommand(Command::GET, Register::NetworkInfo); - sendHexCommand(Command::GET, Register::NetworkMode); - sendHexCommand(Command::GET, Register::NetworkStatus); -#endif // PROCESS_NETWORK_STATE -} + // Note: First we send queries (timing improvement) + if (_canSend && (_tmpFrame.getFwVersionAsInteger() >= 153)) { + + // 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 - _sendTimeStamp) > _sendTimeout)) { + + for (auto idx = 0; idx < _hexQueue.size(); ++idx) { + + // we check if it is time to update a value + if ((millisTime - _hexQueue[idx]._lastSendTime) > (_hexQueue[idx]._readPeriod * 1000)) { + sendHexCommand(VeDirectHexCommand::GET, _hexQueue[idx]._hexRegister); + _hexQueue[idx]._lastSendTime = millisTime; + _sendTimeStamp = millisTime; + + // we need this information to check if we get an answer, see hexDataHandler() + _sendTimeout = 500; + _sendQueueNr = idx; + break; + } + } + } + } -void VeDirectMpptController::loop() -{ + // Second we read the messages VeDirectFrameHandler::loop(); + // Third we check if hex data is outdated + // Note: Room for improvement, longer data valid time for slow changing values auto resetTimestamp = [this](auto& pair) { if (pair.first > 0 && (millis() - pair.first) > (10 * 1000)) { pair.first = 0; @@ -167,6 +180,10 @@ bool VeDirectMpptController::hexDataHandler(VeDirectHexData const &data) { auto regLog = static_cast(data.addr); + // we check if we get we right answer to our query + if ((data.rsp == VeDirectHexResponse::GET) && (data.addr == _hexQueue[_sendQueueNr]._hexRegister)) + _sendTimeout = 0; + switch (data.addr) { case VeDirectHexRegister::ChargeControllerTemperature: _tmpFrame.MpptTemperatureMilliCelsius = diff --git a/lib/VeDirectFrameHandler/VeDirectMpptController.h b/lib/VeDirectFrameHandler/VeDirectMpptController.h index 44c97d6a5..f04c7a889 100644 --- a/lib/VeDirectFrameHandler/VeDirectMpptController.h +++ b/lib/VeDirectFrameHandler/VeDirectMpptController.h @@ -36,6 +36,12 @@ class MovingAverage { size_t _count; }; +struct VeDirectHexQueue { + VeDirectHexRegister _hexRegister; // hex register + int8_t _readPeriod; // time period in sec until we send the command again + int32_t _lastSendTime; // time stamp in milli sec of last send +}; + class VeDirectMpptController : public VeDirectFrameHandler { public: VeDirectMpptController() = default; @@ -52,10 +58,15 @@ class VeDirectMpptController : public VeDirectFrameHandler { bool processTextDataDerived(std::string const& name, std::string const& value) final; void frameValidEvent() final; MovingAverage _efficiency; - int8_t _slotNr = 0; - std::array _slotRegister { VeDirectHexRegister::NetworkTotalDcInputPower, - VeDirectHexRegister::ChargeControllerTemperature, - VeDirectHexRegister::SmartBatterySenseTemperature, - VeDirectHexRegister::BatteryFloatVoltage, - VeDirectHexRegister::BatteryAbsorptionVoltage }; + + int32_t _sendTimeStamp = 0; // timestamp of last command + int32_t _sendTimeout = 0; // timeout until we send the next command from the queue + int8_t _sendQueueNr = 0; // actual queue position; + + // for slow changing values we use a send time period of 4 sec + std::array _hexQueue { VeDirectHexRegister::NetworkTotalDcInputPower, 1, 0, + VeDirectHexRegister::ChargeControllerTemperature, 4, 0, + VeDirectHexRegister::SmartBatterySenseTemperature, 4, 0, + VeDirectHexRegister::BatteryFloatVoltage, 4, 0, + VeDirectHexRegister::BatteryAbsorptionVoltage, 4, 0 }; }; From edd9d6d5b90c6171419c75d5dd926f6bc0784c51 Mon Sep 17 00:00:00 2001 From: SW-Nico Date: Thu, 15 Aug 2024 18:56:19 +0200 Subject: [PATCH 5/7] add BatteryVoltage to function getVoltage --- include/VictronMppt.h | 1 + src/VictronMppt.cpp | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/include/VictronMppt.h b/include/VictronMppt.h index 4ef9d0c69..c43fbb3cc 100644 --- a/include/VictronMppt.h +++ b/include/VictronMppt.h @@ -49,6 +49,7 @@ class VictronMpptClass { enum class MPPTVoltage : uint8_t { ABSORPTION = 0, FLOAT = 1, + BATTERY = 2 }; float getVoltage(MPPTVoltage kindOf) const; diff --git a/src/VictronMppt.cpp b/src/VictronMppt.cpp index d123a72e5..9058d9d72 100644 --- a/src/VictronMppt.cpp +++ b/src/VictronMppt.cpp @@ -259,6 +259,12 @@ float VictronMpptClass::getVoltage(MPPTVoltage kindOf) const case MPPTVoltage::FLOAT: voltX = upController->getData().BatteryFloatMilliVolt; break; + case MPPTVoltage::BATTERY: + if (upController->isDataValid()) { + voltX.first = 1; + voltX.second = upController->getData().batteryVoltage_V_mV; + } + break; } if (voltX.first > 0) { return static_cast(voltX.second / 1000.0); From 5436cab7e3f51b097e43d8d37fdf3bf4b9d92def Mon Sep 17 00:00:00 2001 From: SW-Nico Date: Sat, 7 Sep 2024 13:41:40 +0200 Subject: [PATCH 6/7] improved send algorithm --- .../VeDirectMpptController.cpp | 114 ++++++++++++------ .../VeDirectMpptController.h | 14 ++- src/VictronMppt.cpp | 3 +- 3 files changed, 86 insertions(+), 45 deletions(-) diff --git a/lib/VeDirectFrameHandler/VeDirectMpptController.cpp b/lib/VeDirectFrameHandler/VeDirectMpptController.cpp index 398ef5c74..a3b261a94 100644 --- a/lib/VeDirectFrameHandler/VeDirectMpptController.cpp +++ b/lib/VeDirectFrameHandler/VeDirectMpptController.cpp @@ -109,46 +109,17 @@ void VeDirectMpptController::frameValidEvent() { void VeDirectMpptController::loop() { - // 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 - // Note: First we send queries (timing improvement) - if (_canSend && (_tmpFrame.getFwVersionAsInteger() >= 153)) { - - // 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 - _sendTimeStamp) > _sendTimeout)) { - - for (auto idx = 0; idx < _hexQueue.size(); ++idx) { - - // we check if it is time to update a value - if ((millisTime - _hexQueue[idx]._lastSendTime) > (_hexQueue[idx]._readPeriod * 1000)) { - sendHexCommand(VeDirectHexCommand::GET, _hexQueue[idx]._hexRegister); - _hexQueue[idx]._lastSendTime = millisTime; - _sendTimeStamp = millisTime; - - // we need this information to check if we get an answer, see hexDataHandler() - _sendTimeout = 500; - _sendQueueNr = idx; - break; - } - } - - } + // First we send HEX-Commands (timing improvement) + if (isHexCommandPossible()) { + sendNextHexCommandFromQueue(); } - // Second we read the messages + // Second we read Text- and HEX-Messages VeDirectFrameHandler::loop(); - // Third we check if hex data is outdated - // Note: Room for improvement, longer data valid time for slow changing values + // 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) { if (pair.first > 0 && (millis() - pair.first) > (10 * 1000)) { pair.first = 0; @@ -180,9 +151,10 @@ bool VeDirectMpptController::hexDataHandler(VeDirectHexData const &data) { auto regLog = static_cast(data.addr); - // we check if we get we right answer to our query - if ((data.rsp == VeDirectHexResponse::GET) && (data.addr == _hexQueue[_sendQueueNr]._hexRegister)) + // 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) { case VeDirectHexRegister::ChargeControllerTemperature: @@ -302,3 +274,69 @@ bool VeDirectMpptController::hexDataHandler(VeDirectHexData const &data) { 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 + // Note: First we send queries (timing improvement) + //return (_canSend && (_tmpFrame.getFwVersionAsInteger() >= 153)); + return true; +} + + +/* + * sendNextHexCommandFromQueue() + * send one Hex Commands from the Hex Command Queue + * handel's 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 + } + } +} \ No newline at end of file diff --git a/lib/VeDirectFrameHandler/VeDirectMpptController.h b/lib/VeDirectFrameHandler/VeDirectMpptController.h index f04c7a889..ac3b9122a 100644 --- a/lib/VeDirectFrameHandler/VeDirectMpptController.h +++ b/lib/VeDirectFrameHandler/VeDirectMpptController.h @@ -38,8 +38,8 @@ class MovingAverage { struct VeDirectHexQueue { VeDirectHexRegister _hexRegister; // hex register - int8_t _readPeriod; // time period in sec until we send the command again - int32_t _lastSendTime; // time stamp in milli sec of last send + 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 { @@ -57,14 +57,16 @@ class VeDirectMpptController : public VeDirectFrameHandler { bool hexDataHandler(VeDirectHexData const &data) final; bool processTextDataDerived(std::string const& name, std::string const& value) final; void frameValidEvent() final; + void sendNextHexCommandFromQueue(void); + bool isHexCommandPossible(void); MovingAverage _efficiency; - int32_t _sendTimeStamp = 0; // timestamp of last command - int32_t _sendTimeout = 0; // timeout until we send the next command from the queue - int8_t _sendQueueNr = 0; // actual queue position; + 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 - std::array _hexQueue { VeDirectHexRegister::NetworkTotalDcInputPower, 1, 0, + #define HIGH_PRIO_COMMAND 1 + std::array _hexQueue { VeDirectHexRegister::NetworkTotalDcInputPower, HIGH_PRIO_COMMAND, 0, VeDirectHexRegister::ChargeControllerTemperature, 4, 0, VeDirectHexRegister::SmartBatterySenseTemperature, 4, 0, VeDirectHexRegister::BatteryFloatVoltage, 4, 0, diff --git a/src/VictronMppt.cpp b/src/VictronMppt.cpp index 9058d9d72..4d1ccc0fb 100644 --- a/src/VictronMppt.cpp +++ b/src/VictronMppt.cpp @@ -235,8 +235,9 @@ float VictronMpptClass::getOutputVoltage() const int16_t VictronMpptClass::getStateOfOperation() const { for (const auto& upController : _controllers) { - if (upController->isDataValid()) + if (upController->isDataValid()) { return static_cast(upController->getData().currentState_CS); + } } return -1; From 483ae35930253456623af11dc5b3bf5bdcffdb6e Mon Sep 17 00:00:00 2001 From: SW-Nico Date: Thu, 3 Oct 2024 10:41:57 +0200 Subject: [PATCH 7/7] bugfix: reactivate MPPT FW version check --- lib/VeDirectFrameHandler/VeDirectMpptController.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/VeDirectFrameHandler/VeDirectMpptController.cpp b/lib/VeDirectFrameHandler/VeDirectMpptController.cpp index a3b261a94..d5b7e479b 100644 --- a/lib/VeDirectFrameHandler/VeDirectMpptController.cpp +++ b/lib/VeDirectFrameHandler/VeDirectMpptController.cpp @@ -287,9 +287,7 @@ bool VeDirectMpptController::isHexCommandPossible(void) { // 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 - // Note: First we send queries (timing improvement) - //return (_canSend && (_tmpFrame.getFwVersionAsInteger() >= 153)); - return true; + return (_canSend && (_tmpFrame.getFwVersionAsInteger() >= 153)); }