Skip to content

Commit

Permalink
polish VE.Direct HEX support
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
schlimmchen committed Apr 3, 2024
1 parent aadd730 commit 6b8c93d
Show file tree
Hide file tree
Showing 13 changed files with 463 additions and 435 deletions.
36 changes: 36 additions & 0 deletions lib/VeDirectFrameHandler/VeDirectData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,39 @@ frozen::string const& veMpptStruct::getOrAsString() const

return getAsString(values, OR);
}

frozen::string const& VeDirectHexData::getResponseAsString() const
{
using Response = VeDirectHexResponse;
static constexpr frozen::map<Response, frozen::string, 7> values = {
{ Response::DONE, "Done" },
{ Response::UNKNOWN, "Unknown" },
{ Response::ERROR, "Error" },
{ Response::PING, "Ping" },
{ Response::GET, "Get" },
{ Response::SET, "Set" },
{ Response::ASYNC, "Async" }
};

return getAsString(values, rsp);
}

frozen::string const& VeDirectHexData::getRegisterAsString() const
{
using Register = VeDirectHexRegister;
static constexpr frozen::map<Register, frozen::string, 11> values = {
{ Register::DeviceMode, "Device Mode" },
{ Register::DeviceState, "Device State" },
{ Register::RemoteControlUsed, "Remote Control Used" },
{ Register::PanelVoltage, "Panel Voltage" },
{ Register::ChargerVoltage, "Charger Voltage" },
{ 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" }
};

return getAsString(values, addr);
}
65 changes: 65 additions & 0 deletions lib/VeDirectFrameHandler/VeDirectData.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@ struct veMpptStruct : veStruct {
float H22; // yield yesterday kWh
int32_t H23; // maximum power yesterday W

// these are values communicated through the HEX protocol. the pair's first
// value is the timestamp the respective info was last received. if it is
// zero, the value is deemed invalid. the timestamp is reset if no current
// value could be retrieved.
std::pair<uint32_t, int32_t> MpptTemperatureMilliCelsius;
std::pair<uint32_t, int32_t> SmartBatterySenseTemperatureMilliCelsius;
std::pair<uint32_t, uint32_t> NetworkTotalDcInputPowerMilliWatts;
std::pair<uint32_t, uint8_t> NetworkInfo;
std::pair<uint32_t, uint8_t> NetworkMode;
std::pair<uint32_t, uint8_t> NetworkStatus;

frozen::string const& getMpptAsString() const; // state of mppt as string
frozen::string const& getCsAsString() const; // current state as string
frozen::string const& getErrAsString() const; // error state as string
Expand Down Expand Up @@ -68,3 +79,57 @@ struct veShuntStruct : veStruct {
int32_t H17; // Amount of discharged energy
int32_t H18; // Amount of charged energy
};

enum class VeDirectHexCommand : uint8_t {
ENTER_BOOT = 0x0,
PING = 0x1,
RSV1 = 0x2,
APP_VERSION = 0x3,
PRODUCT_ID = 0x4,
RSV2 = 0x5,
RESTART = 0x6,
GET = 0x7,
SET = 0x8,
RSV3 = 0x9,
ASYNC = 0xA,
RSV4 = 0xB,
RSV5 = 0xC,
RSV6 = 0xD,
RSV7 = 0xE,
RSV8 = 0xF
};

enum class VeDirectHexResponse : uint8_t {
DONE = 0x1,
UNKNOWN = 0x3,
ERROR = 0x4,
PING = 0x5,
GET = 0x7,
SET = 0x8,
ASYNC = 0xA
};

enum class VeDirectHexRegister : uint16_t {
DeviceMode = 0x0200,
DeviceState = 0x0201,
RemoteControlUsed = 0x0202,
PanelVoltage = 0xEDBB,
ChargerVoltage = 0xEDD5,
NetworkTotalDcInputPower = 0x2027,
ChargeControllerTemperature = 0xEDDB,
SmartBatterySenseTemperature = 0xEDEC,
NetworkInfo = 0x200D,
NetworkMode = 0x200E,
NetworkStatus = 0x200F
};

struct VeDirectHexData {
VeDirectHexResponse rsp; // hex response code
VeDirectHexRegister addr; // register address
uint8_t flags; // flags
uint32_t value; // integer value of register
char text[VE_MAX_HEX_LEN]; // text/string response

frozen::string const& getResponseAsString() const;
frozen::string const& getRegisterAsString() const;
};
66 changes: 31 additions & 35 deletions lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,6 @@
// The name of the record that contains the checksum.
static constexpr char checksumTagName[] = "CHECKSUM";

// state machine
enum States {
IDLE = 1,
RECORD_BEGIN = 2,
RECORD_NAME = 3,
RECORD_VALUE = 4,
CHECKSUM = 5,
RECORD_HEX = 6
};



class Silent : public Print {
public:
size_t write(uint8_t c) final { return 0; }
Expand All @@ -62,7 +50,7 @@ template<typename T>
VeDirectFrameHandler<T>::VeDirectFrameHandler() :
_msgOut(&MessageOutputDummy),
_lastUpdate(0),
_state(IDLE),
_state(State::IDLE),
_checksum(0),
_textPointer(0),
_hexSize(0),
Expand All @@ -79,6 +67,7 @@ void VeDirectFrameHandler<T>::init(char const* who, int8_t rx, int8_t tx, Print*
_vedirectSerial = std::make_unique<HardwareSerial>(hwSerialPort);
_vedirectSerial->begin(19200, SERIAL_8N1, rx, tx);
_vedirectSerial->flush();
_canSend = (tx != -1);
_msgOut = msgOut;
_verboseLogging = verboseLogging;
_debugIn = 0;
Expand All @@ -103,7 +92,7 @@ template<typename T>
void VeDirectFrameHandler<T>::reset()
{
_checksum = 0;
_state = IDLE;
_state = State::IDLE;
_textData.clear();
}

Expand All @@ -118,8 +107,9 @@ void VeDirectFrameHandler<T>::loop()
// there will never be a large gap between two bytes of the same frame.
// if such a large gap is observed, reset the state machine so it tries
// to decode a new frame once more data arrives.
if (IDLE != _state && (millis() - _lastByteMillis) > 500) {
_msgOut->printf("%s Resetting state machine (was %d) after timeout\r\n", _logId, _state);
if (State::IDLE != _state && (millis() - _lastByteMillis) > 500) {
_msgOut->printf("%s Resetting state machine (was %d) after timeout\r\n",
_logId, static_cast<unsigned>(_state));
if (_verboseLogging) { dumpDebugBuffer(); }
reset();
}
Expand All @@ -141,47 +131,47 @@ void VeDirectFrameHandler<T>::rxData(uint8_t inbyte)
}
}

if ( (inbyte == ':') && (_state != CHECKSUM) ) {
if ( (inbyte == ':') && (_state != State::CHECKSUM) ) {
_prevState = _state; //hex frame can interrupt TEXT
_state = RECORD_HEX;
_state = State::RECORD_HEX;
_hexSize = 0;
}
if (_state != RECORD_HEX) {
if (_state != State::RECORD_HEX) {
_checksum += inbyte;
}
inbyte = toupper(inbyte);

switch(_state) {
case IDLE:
case State::IDLE:
/* wait for \n of the start of an record */
switch(inbyte) {
case '\n':
_state = RECORD_BEGIN;
_state = State::RECORD_BEGIN;
break;
case '\r': /* Skip */
default:
break;
}
break;
case RECORD_BEGIN:
case State::RECORD_BEGIN:
_textPointer = _name;
*_textPointer++ = inbyte;
_state = RECORD_NAME;
_state = State::RECORD_NAME;
break;
case RECORD_NAME:
case State::RECORD_NAME:
// The record name is being received, terminated by a \t
switch(inbyte) {
case '\t':
// the Checksum record indicates a EOR
if ( _textPointer < (_name + sizeof(_name)) ) {
*_textPointer = 0; /* Zero terminate */
if (strcmp(_name, checksumTagName) == 0) {
_state = CHECKSUM;
_state = State::CHECKSUM;
break;
}
}
_textPointer = _value; /* Reset value pointer */
_state = RECORD_VALUE;
_state = State::RECORD_VALUE;
break;
case '#': /* Ignore # from serial number*/
break;
Expand All @@ -192,15 +182,15 @@ void VeDirectFrameHandler<T>::rxData(uint8_t inbyte)
break;
}
break;
case RECORD_VALUE:
case State::RECORD_VALUE:
// The record value is being received. The \r indicates a new record.
switch(inbyte) {
case '\n':
if ( _textPointer < (_value + sizeof(_value)) ) {
*_textPointer = 0; // make zero ended
_textData.push_back({_name, _value});
}
_state = RECORD_BEGIN;
_state = State::RECORD_BEGIN;
break;
case '\r': /* Skip */
break;
Expand All @@ -211,7 +201,7 @@ void VeDirectFrameHandler<T>::rxData(uint8_t inbyte)
break;
}
break;
case CHECKSUM:
case State::CHECKSUM:
{
if (_verboseLogging) { dumpDebugBuffer(); }
if (_checksum == 0) {
Expand All @@ -227,7 +217,7 @@ void VeDirectFrameHandler<T>::rxData(uint8_t inbyte)
reset();
break;
}
case RECORD_HEX:
case State::RECORD_HEX:
_state = hexRxEvent(inbyte);
break;
}
Expand Down Expand Up @@ -279,17 +269,23 @@ void VeDirectFrameHandler<T>::processTextData(std::string const& name, std::stri
* This function records hex answers or async messages
*/
template<typename T>
int VeDirectFrameHandler<T>::hexRxEvent(uint8_t inbyte)
typename VeDirectFrameHandler<T>::State VeDirectFrameHandler<T>::hexRxEvent(uint8_t inbyte)
{
int ret=RECORD_HEX; // default - continue recording until end of frame
State ret = State::RECORD_HEX; // default - continue recording until end of frame

switch (inbyte) {
case '\n':
// now we can analyse the hex message
_hexBuffer[_hexSize] = '\0';
VeDirectHexData data;
if (disassembleHexData(data))
hexDataHandler(data);
if (disassembleHexData(data) && !hexDataHandler(data) && _verboseLogging) {
_msgOut->printf("%s Unhandled Hex %s Response, addr: 0x%04X (%s), "
"value: 0x%08X, flags: 0x%02X\r\n", _logId,
data.getResponseAsString().data(),
static_cast<unsigned>(data.addr),
data.getRegisterAsString().data(),
data.value, data.flags);
}

// restore previous state
ret=_prevState;
Expand All @@ -301,7 +297,7 @@ int VeDirectFrameHandler<T>::hexRxEvent(uint8_t inbyte)
if (_hexSize>=VE_MAX_HEX_LEN) { // oops -buffer overflow - something went wrong, we abort
_msgOut->printf("%s hexRx buffer overflow - aborting read\r\n", _logId);
_hexSize=0;
ret=IDLE;
ret = State::IDLE;
}
}

Expand Down
Loading

0 comments on commit 6b8c93d

Please sign in to comment.