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; +}