polish VE.Direct HEX support

* show charge controller temperature in live view
* send hex requests right after decoding a frame. this seems to have the
  best chance of getting an answer to all requests.
* deem 0xFFFFFFFF value of network total DC power as invalid indicator.
  neither network state, nor network info, nor network mode seem to
  indicate that the charge controller is part of a VE.Smart network. for
  that reason, we revert to always querying the network total DC power
  value, but testing it for max(uin32_t) value, which seems to indicate
  that the charge controller is not part of a VE.Smart network.
* improve (verbose) logging, e.g., use _logId, and print names of
  response codes and known registers, always print error messages,
  add additional tests to prevent overly verbose messages.
* move hex protocol definitions to VeDirectData.h header
  and use enum classes
* define register addresses in enum class
* move values retrieved through hex protocol into main MPPT data struct
* do not send HEX requests if the serial interface cannot send data
* detect whether smart battery sense temperature is available
* web app: make all VE.Direct sub-cards iterable. this makes addind more
  values much simpler and saves a bunch of code in the web app.
* make VeDirectFrameHandler state a type-safe enum class
* unindent MPPT controller loop()
* whitespace cleanup
This commit is contained in:
Bernhard Kirchen
2024-03-30 20:14:11 +01:00
committed by Bernhard Kirchen
parent aadd7303ac
commit 6b8c93d2e6
13 changed files with 454 additions and 426 deletions

View File

@@ -1,6 +1,6 @@
/* VeDirectMpptController.cpp
*
*
*
* 2020.08.20 - 0.0 - ???
* 2024.03.18 - 0.1 - add of: - temperature from "Smart Battery Sense" connected over VE.Smart network
* - temperature from internal MPPT sensor
@@ -10,10 +10,7 @@
#include <Arduino.h>
#include "VeDirectMpptController.h"
// support for debugging, 0=without extended logging, 1=with extended logging
constexpr int MODUL_DEBUG = 0;
//#define PROCESS_NETWORK_STATE
void VeDirectMpptController::init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging, uint16_t hwSerialPort)
{
@@ -93,32 +90,50 @@ void VeDirectMpptController::frameValidEvent() {
_efficiency.addNumber(static_cast<float>(_tmpFrame.P * 100) / _tmpFrame.PPV);
_tmpFrame.E = _efficiency.getAverage();
}
}
if (!_canSend) { return; }
/*
// loop()
// send hex commands to MPPT every 5 seconds
*/
void VeDirectMpptController::loop()
{
VeDirectFrameHandler::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 commandes for firmware >= 1.53 to keep text messages alive
if (atoi(_tmpFrame.FW) >= 153 ) {
if ((millis() - _lastPingTime) > 5000) {
if (atoi(_tmpFrame.FW) < 153) { return; }
sendHexCommand(GET, 0x2027); // MPPT total DC input power
sendHexCommand(GET, 0xEDDB); // MPPT internal temperature
sendHexCommand(GET, 0xEDEC); // "Smart Battery Sense" temperature
sendHexCommand(GET, 0x200F); // Network info
_lastPingTime = millis();
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()
{
VeDirectFrameHandler::loop();
auto resetTimestamp = [this](auto& pair) {
if (pair.first > 0 && (millis() - pair.first) > (10 * 1000)) {
pair.first = 0;
}
}
};
resetTimestamp(_tmpFrame.MpptTemperatureMilliCelsius);
resetTimestamp(_tmpFrame.SmartBatterySenseTemperatureMilliCelsius);
resetTimestamp(_tmpFrame.NetworkTotalDcInputPowerMilliWatts);
#ifdef PROCESS_NETWORK_STATE
resetTimestamp(_tmpFrame.NetworkInfo);
resetTimestamp(_tmpFrame.NetworkMode);
resetTimestamp(_tmpFrame.NetworkStatus);
#endif // PROCESS_NETWORK_STATE
}
@@ -127,62 +142,104 @@ void VeDirectMpptController::loop()
* analyse the content of VE.Direct hex messages
* Handels the received hex data from the MPPT
*/
void VeDirectMpptController::hexDataHandler(VeDirectHexData const &data) {
bool state = false;
bool VeDirectMpptController::hexDataHandler(VeDirectHexData const &data) {
if (data.rsp != VeDirectHexResponse::GET &&
data.rsp != VeDirectHexResponse::ASYNC) { return false; }
switch (data.rsp) {
case R_GET:
case R_ASYNC:
auto regLog = static_cast<uint16_t>(data.addr);
// check if MPPT internal temperature is available
if(data.id == 0xEDDB) {
_ExData.T = static_cast<int32_t>(data.value) * 10; // conversion from unit [0.01°C] to unit [m°C]
_ExData.Tts = millis();
state = true;
if constexpr(MODUL_DEBUG == 1)
_msgOut->printf("[VE.Direct] debug: hexDataHandler(), MTTP Temperature: %.2f°C\r\n", _ExData.T/1000.0);
}
switch (data.addr) {
case VeDirectHexRegister::ChargeControllerTemperature:
_tmpFrame.MpptTemperatureMilliCelsius =
{ millis(), static_cast<int32_t>(data.value) * 10 };
// check if temperature from "Smart Battery Sense" is available
if(data.id == 0xEDEC) {
_ExData.TSBS = static_cast<int32_t>(data.value) * 10 - 272150; // conversion from unit [0.01K] to unit [m°C]
_ExData.TSBSts = millis();
state = true;
if constexpr(MODUL_DEBUG == 1)
_msgOut->printf("[VE.Direct] debug: hexDataHandler(), Battery Temperature: %.2f°C\r\n", _ExData.TSBS/1000.0);
}
if (_verboseLogging) {
_msgOut->printf("%s Hex Data: MPPT Temperature (0x%04X): %.2f°C\r\n",
_logId, regLog,
_tmpFrame.MpptTemperatureMilliCelsius.second / 1000.0);
}
return true;
break;
// check if "Total DC power" is available
if(data.id == 0x2027) {
_ExData.TDCP = data.value * 10; // conversion from unit [0.01W] to unit [mW]
_ExData.TDCPts = millis();
state = true;
case VeDirectHexRegister::SmartBatterySenseTemperature:
if (data.value == 0xFFFF) {
if (_verboseLogging) {
_msgOut->printf("%s Hex Data: Smart Battery Sense Temperature is not available\r\n", _logId);
}
return true; // we know what to do with it, and we decided to ignore the value
}
if constexpr(MODUL_DEBUG == 1)
_msgOut->printf("[VE.Direct] debug: hexDataHandler(), Total Power: %.2fW\r\n", _ExData.TDCP/1000.0);
}
_tmpFrame.SmartBatterySenseTemperatureMilliCelsius =
{ millis(), static_cast<int32_t>(data.value) * 10 - 272150 };
// check if connected MPPT is charge instance master
// Hint: not used right now but maybe necessary for future extensions
if(data.id == 0x200F) {
_veMaster = ((data.value & 0x0F) == 0x02) ? true : false;
state = true;
if (_verboseLogging) {
_msgOut->printf("%s Hex Data: Smart Battery Sense Temperature (0x%04X): %.2f°C\r\n",
_logId, regLog,
_tmpFrame.SmartBatterySenseTemperatureMilliCelsius.second / 1000.0);
}
return true;
break;
if constexpr(MODUL_DEBUG == 1)
_msgOut->printf("[VE.Direct] debug: hexDataHandler(), Networkmode: 0x%X\r\n", data.value);
}
break;
default:
break;
case VeDirectHexRegister::NetworkTotalDcInputPower:
if (data.value == 0xFFFFFFFF) {
if (_verboseLogging) {
_msgOut->printf("%s Hex Data: Network total DC power value "
"indicates non-networked controller\r\n", _logId);
}
_tmpFrame.NetworkTotalDcInputPowerMilliWatts = { 0, 0 };
return true; // we know what to do with it, and we decided to ignore the value
}
_tmpFrame.NetworkTotalDcInputPowerMilliWatts =
{ millis(), data.value * 10 };
if (_verboseLogging) {
_msgOut->printf("%s Hex Data: Network Total DC Power (0x%04X): %.2fW\r\n",
_logId, regLog,
_tmpFrame.NetworkTotalDcInputPowerMilliWatts.second / 1000.0);
}
return true;
break;
#ifdef PROCESS_NETWORK_STATE
case VeDirectHexRegister::NetworkInfo:
_tmpFrame.NetworkInfo =
{ millis(), static_cast<uint8_t>(data.value) };
if (_verboseLogging) {
_msgOut->printf("%s Hex Data: Network Info (0x%04X): 0x%X\r\n",
_logId, regLog, data.value);
}
return true;
break;
case VeDirectHexRegister::NetworkMode:
_tmpFrame.NetworkMode =
{ millis(), static_cast<uint8_t>(data.value) };
if (_verboseLogging) {
_msgOut->printf("%s Hex Data: Network Mode (0x%04X): 0x%X\r\n",
_logId, regLog, data.value);
}
return true;
break;
case VeDirectHexRegister::NetworkStatus:
_tmpFrame.NetworkStatus =
{ millis(), static_cast<uint8_t>(data.value) };
if (_verboseLogging) {
_msgOut->printf("%s Hex Data: Network Status (0x%04X): 0x%X\r\n",
_logId, regLog, data.value);
}
return true;
break;
#endif // PROCESS_NETWORK_STATE
default:
return false;
break;
}
if constexpr(MODUL_DEBUG == 1)
_msgOut->printf("[VE.Direct] debug: hexDataHandler(): rsp: %i, id: 0x%04X, value: %i[0x%08X], text: %s\r\n",
data.rsp, data.id, data.value, data.value, data.text);
if (_verboseLogging && state)
_msgOut->printf("[VE.Direct] MPPT hex message: rsp: %i, id: 0x%04X, value: %i[0x%08X], text: %s\r\n",
data.rsp, data.id, data.value, data.value, data.text);
}
return false;
}