From 6b8c93d2e6b4033bae6d3a7cda9504d12fc13d8f Mon Sep 17 00:00:00 2001 From: Bernhard Kirchen Date: Sat, 30 Mar 2024 20:14:11 +0100 Subject: [PATCH] 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 --- lib/VeDirectFrameHandler/VeDirectData.cpp | 36 +++ lib/VeDirectFrameHandler/VeDirectData.h | 65 ++++++ .../VeDirectFrameHandler.cpp | 66 +++--- .../VeDirectFrameHandler.h | 62 ++--- .../VeDirectFrameHexHandler.cpp | 117 +++++----- .../VeDirectMpptController.cpp | 217 +++++++++++------- .../VeDirectMpptController.h | 17 +- src/WebApi_ws_vedirect_live.cpp | 109 +++++---- webapp/src/components/VedirectView.vue | 108 ++------- webapp/src/locales/de.json | 21 +- webapp/src/locales/en.json | 21 +- webapp/src/locales/fr.json | 21 +- webapp/src/types/VedirectLiveDataStatus.ts | 38 +-- 13 files changed, 463 insertions(+), 435 deletions(-) diff --git a/lib/VeDirectFrameHandler/VeDirectData.cpp b/lib/VeDirectFrameHandler/VeDirectData.cpp index 20c2cdf63..012ec29ea 100644 --- a/lib/VeDirectFrameHandler/VeDirectData.cpp +++ b/lib/VeDirectFrameHandler/VeDirectData.cpp @@ -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 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 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); +} diff --git a/lib/VeDirectFrameHandler/VeDirectData.h b/lib/VeDirectFrameHandler/VeDirectData.h index f651193b0..b1158d776 100644 --- a/lib/VeDirectFrameHandler/VeDirectData.h +++ b/lib/VeDirectFrameHandler/VeDirectData.h @@ -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 MpptTemperatureMilliCelsius; + std::pair SmartBatterySenseTemperatureMilliCelsius; + std::pair NetworkTotalDcInputPowerMilliWatts; + std::pair NetworkInfo; + std::pair NetworkMode; + std::pair 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 @@ -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; +}; diff --git a/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp b/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp index c26c97055..3e07169a8 100644 --- a/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp +++ b/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp @@ -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; } @@ -62,7 +50,7 @@ template VeDirectFrameHandler::VeDirectFrameHandler() : _msgOut(&MessageOutputDummy), _lastUpdate(0), - _state(IDLE), + _state(State::IDLE), _checksum(0), _textPointer(0), _hexSize(0), @@ -79,6 +67,7 @@ void VeDirectFrameHandler::init(char const* who, int8_t rx, int8_t tx, Print* _vedirectSerial = std::make_unique(hwSerialPort); _vedirectSerial->begin(19200, SERIAL_8N1, rx, tx); _vedirectSerial->flush(); + _canSend = (tx != -1); _msgOut = msgOut; _verboseLogging = verboseLogging; _debugIn = 0; @@ -103,7 +92,7 @@ template void VeDirectFrameHandler::reset() { _checksum = 0; - _state = IDLE; + _state = State::IDLE; _textData.clear(); } @@ -118,8 +107,9 @@ void VeDirectFrameHandler::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(_state)); if (_verboseLogging) { dumpDebugBuffer(); } reset(); } @@ -141,34 +131,34 @@ void VeDirectFrameHandler::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': @@ -176,12 +166,12 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte) 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; @@ -192,7 +182,7 @@ void VeDirectFrameHandler::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': @@ -200,7 +190,7 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte) *_textPointer = 0; // make zero ended _textData.push_back({_name, _value}); } - _state = RECORD_BEGIN; + _state = State::RECORD_BEGIN; break; case '\r': /* Skip */ break; @@ -211,7 +201,7 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte) break; } break; - case CHECKSUM: + case State::CHECKSUM: { if (_verboseLogging) { dumpDebugBuffer(); } if (_checksum == 0) { @@ -227,7 +217,7 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte) reset(); break; } - case RECORD_HEX: + case State::RECORD_HEX: _state = hexRxEvent(inbyte); break; } @@ -279,17 +269,23 @@ void VeDirectFrameHandler::processTextData(std::string const& name, std::stri * This function records hex answers or async messages */ template -int VeDirectFrameHandler::hexRxEvent(uint8_t inbyte) +typename VeDirectFrameHandler::State VeDirectFrameHandler::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(data.addr), + data.getRegisterAsString().data(), + data.value, data.flags); + } // restore previous state ret=_prevState; @@ -301,7 +297,7 @@ int VeDirectFrameHandler::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; } } diff --git a/lib/VeDirectFrameHandler/VeDirectFrameHandler.h b/lib/VeDirectFrameHandler/VeDirectFrameHandler.h index a2cd28348..c2d660884 100644 --- a/lib/VeDirectFrameHandler/VeDirectFrameHandler.h +++ b/lib/VeDirectFrameHandler/VeDirectFrameHandler.h @@ -19,39 +19,6 @@ #include #include "VeDirectData.h" -// hex send commands -enum VeDirectHexCommand { - ENTER_BOOT = 0x00, - PING = 0x01, - APP_VERSION = 0x02, - PRODUCT_ID = 0x04, - RESTART = 0x06, - GET = 0x07, - SET = 0x08, - ASYNC = 0x0A, - UNKNOWN = 0x0F -}; - -// hex receive responses -enum VeDirectHexResponse { - R_DONE = 0x01, - R_UNKNOWN = 0x03, - R_ERROR = 0x04, - R_PING = 0x05, - R_GET = 0x07, - R_SET = 0x08, - R_ASYNC = 0x0A, -}; - -// hex response data, contains the disassembeled hex message, forwarded to virtual funktion hexDataHandler() -struct VeDirectHexData { - VeDirectHexResponse rsp; // hex response type - uint16_t id; // register address - uint8_t flag; // flag - uint32_t value; // value from register - char text[VE_MAX_HEX_LEN]; // text/string response -}; - template class VeDirectFrameHandler { public: @@ -59,12 +26,12 @@ class VeDirectFrameHandler { uint32_t getLastUpdate() const; // timestamp of last successful frame read bool isDataValid() const; // return true if data valid and not outdated T const& getData() const { return _tmpFrame; } - bool sendHexCommand(VeDirectHexCommand cmd, uint16_t id = 0, uint32_t value = 0, uint8_t valunibble = 0); // send hex commands via ve.direct + bool sendHexCommand(VeDirectHexCommand cmd, VeDirectHexRegister addr, uint32_t value = 0, uint8_t valsize = 0); protected: VeDirectFrameHandler(); void init(char const* who, int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging, uint16_t hwSerialPort); - virtual void hexDataHandler(VeDirectHexData const &data) { } // handles the disassembeled hex response + virtual bool hexDataHandler(VeDirectHexData const &data) { return false; } // handles the disassembeled hex response bool _verboseLogging; Print* _msgOut; @@ -72,6 +39,9 @@ class VeDirectFrameHandler { T _tmpFrame; + bool _canSend; + char _logId[32]; + private: void reset(); void dumpDebugBuffer(); @@ -79,22 +49,32 @@ class VeDirectFrameHandler { void processTextData(std::string const& name, std::string const& value); virtual bool processTextDataDerived(std::string const& name, std::string const& value) = 0; virtual void frameValidEvent() { } - int hexRxEvent(uint8_t); bool disassembleHexData(VeDirectHexData &data); //return true if disassembling was possible std::unique_ptr _vedirectSerial; - int _state; // current state - int _prevState; // previous state + + enum class State { + IDLE = 1, + RECORD_BEGIN = 2, + RECORD_NAME = 3, + RECORD_VALUE = 4, + CHECKSUM = 5, + RECORD_HEX = 6 + }; + State _state; + State _prevState; + + State hexRxEvent(uint8_t inbyte); + uint8_t _checksum; // checksum value char * _textPointer; // pointer to the private buffer we're writing to, name or value - int _hexSize; // length of hex buffer - char _hexBuffer[VE_MAX_HEX_LEN] = { }; // buffer for received hex frames + int _hexSize; // length of hex buffer + char _hexBuffer[VE_MAX_HEX_LEN]; // buffer for received hex frames char _name[VE_MAX_VALUE_LEN]; // buffer for the field name char _value[VE_MAX_VALUE_LEN]; // buffer for the field value std::array _debugBuffer; unsigned _debugIn; uint32_t _lastByteMillis; - char _logId[32]; /** * not every frame contains every value the device is communicating, i.e., diff --git a/lib/VeDirectFrameHandler/VeDirectFrameHexHandler.cpp b/lib/VeDirectFrameHandler/VeDirectFrameHexHandler.cpp index b8cd0f277..f7f44b5b0 100644 --- a/lib/VeDirectFrameHandler/VeDirectFrameHexHandler.cpp +++ b/lib/VeDirectFrameHandler/VeDirectFrameHexHandler.cpp @@ -6,22 +6,17 @@ HexHandler.cpp * * How to use: * 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) * to handle the received hex messages. All hex messages will be forwarted 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 * */ #include #include "VeDirectFrameHandler.h" - -// support for debugging, 0=without extended logging, 1=with extended logging -constexpr int MODUL_DEBUG = 0; - - /* * calcHexFrameCheckSum() * help function to calculate the hex checksum @@ -30,7 +25,7 @@ constexpr int MODUL_DEBUG = 0; #define hex2byte(b) (ascii2hex(*(b)))*16+((ascii2hex(*(b+1)))) static uint8_t calcHexFrameCheckSum(const char* buffer, int size) { uint8_t checksum=0x55-ascii2hex(buffer[1]); - for (int i=2; i(strtoul(help, nullptr, 16))); } /* * disassembleHexData() - * analysis the hex message and extract: response, id, flag and value/text + * 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 @@ -76,29 +71,30 @@ template bool VeDirectFrameHandler::disassembleHexData(VeDirectHexData &data) { bool state = false; char * buffer = _hexBuffer; - auto len = strlen(buffer); + auto len = strlen(buffer); // reset hex data first - data = {}; + data = {}; if ((len > 3) && (calcHexFrameCheckSum(buffer, len) == 0x00)) { data.rsp = static_cast(AsciiHexLE2Int(buffer+1, 1)); + using Response = VeDirectHexResponse; switch (data.rsp) { - case R_DONE: - case R_ERROR: - case R_PING: - case R_UNKNOWN: + case Response::DONE: + case Response::ERROR: + case Response::PING: + case Response::UNKNOWN: strncpy(data.text, buffer+2, len-4); state = true; break; - case R_GET: - case R_SET: - case R_ASYNC: - data.id = AsciiHexLE2Int(buffer+2, 4); + case Response::GET: + case Response::SET: + case Response::ASYNC: + data.addr = static_cast(AsciiHexLE2Int(buffer+2, 4)); - // future option: to analyse the flag here? - data.flag = AsciiHexLE2Int(buffer+6, 2); + // future option: to analyse the flags here? + data.flags = AsciiHexLE2Int(buffer+6, 2); if (len == 12) { // 8bit value data.value = AsciiHexLE2Int(buffer+8, 2); @@ -120,13 +116,8 @@ bool VeDirectFrameHandler::disassembleHexData(VeDirectHexData &data) { } } - if constexpr(MODUL_DEBUG == 1) { - _msgOut->printf("[VE.Direct] debug: disassembleHexData(), rsp: %i, id: 0x%04X, value: 0x%X, Flag: 0x%02X\r\n", - data.rsp, data.id, data.value, data.flag); - } - - if (_verboseLogging && !state) - _msgOut->printf("[VE.Direct] failed to disassemble the hex message: %s\r\n", buffer); + if (!state) + _msgOut->printf("%s failed to disassemble the hex message: %s\r\n", _logId, buffer); return (state); } @@ -156,7 +147,7 @@ static String Int2HexLEString(uint32_t value, uint8_t anz) { default: ; } - return String(help); + return String(help); } @@ -164,40 +155,41 @@ static String Int2HexLEString(uint32_t value, uint8_t anz) { * sendHexCommand() * send the hex commend after assembling the command string * cmd: command - * id: id/register, default 0 - * value: value to write into a id/register, default 0 - * valsize: size of the value/id, 8, 16 or 32 bit, default 0 + * 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 - * SAMPLE: ping command: sendHexCommand(PING), + * 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. + * lead to early failure. * On MPPT for example 0xEDE0 - 0xEDFF. Check the Vivtron doc "BlueSolar-HEX-protocol.pdf" */ template -bool VeDirectFrameHandler::sendHexCommand(VeDirectHexCommand cmd, uint16_t id, uint32_t value, uint8_t valsize) { +bool VeDirectFrameHandler::sendHexCommand(VeDirectHexCommand cmd, VeDirectHexRegister addr, uint32_t value, uint8_t valsize) { bool ret = false; - uint8_t flag = 0x00; // always 0x00 + uint8_t flags = 0x00; // always 0x00 - String txData = ":" + Int2HexLEString(cmd, 1); // add the command nibble + String txData = ":" + Int2HexLEString(static_cast(cmd), 1); // add the command nibble + using Command = VeDirectHexCommand; switch (cmd) { - case PING: - case APP_VERSION: - case PRODUCT_ID: - ret = true; - break; - case GET: - case ASYNC: - txData += Int2HexLEString(id, 4); // add the id/register (4 nibble) - txData += Int2HexLEString(flag, 2); // add the flag (2 nibble) + case Command::PING: + case Command::APP_VERSION: + case Command::PRODUCT_ID: + ret = true; + break; + case Command::GET: + case Command::ASYNC: + txData += Int2HexLEString(static_cast(addr), 4); + txData += Int2HexLEString(flags, 2); // add the flags (2 nibble) ret = true; break; - case SET: - txData += Int2HexLEString(id, 4); // add the id/register (4 nibble) - txData += Int2HexLEString(flag, 2); // add the flag (2 nibble) + case Command::SET: + txData += Int2HexLEString(static_cast(addr), 4); + txData += Int2HexLEString(flags, 2); // add the flags (2 nibble) if ((valsize == 8) || (valsize == 16) || (valsize == 32)) { txData += Int2HexLEString(value, valsize/4); // add value (2-8 nibble) ret = true; @@ -205,7 +197,7 @@ bool VeDirectFrameHandler::sendHexCommand(VeDirectHexCommand cmd, uint16_t id break; default: ret = false; - break; + break; } if (ret) { @@ -213,15 +205,16 @@ bool VeDirectFrameHandler::sendHexCommand(VeDirectHexCommand cmd, uint16_t id txData += Int2HexLEString(calcHexFrameCheckSum(txData.c_str(), txData.length()), 2); String send = txData + "\n"; // hex command end byte _vedirectSerial->write(send.c_str(), send.length()); - - if constexpr(MODUL_DEBUG == 1) { - auto blen = _vedirectSerial->availableForWrite(); - _msgOut->printf("[VE.Direct] debug: sendHexCommand(): %s, Free FIFO-Buffer: %u\r\n", txData.c_str(), blen); + + if (_verboseLogging) { + auto blen = _vedirectSerial->availableForWrite(); + _msgOut->printf("%s Sending Hex Command: %s, Free FIFO-Buffer: %u\r\n", + _logId, txData.c_str(), blen); } - } + } - if (_verboseLogging && !ret) - _msgOut->println("[VE.Direct] send hex command fault:" + txData); + if (!ret) + _msgOut->printf("%s send hex command fault: %s\r\n", _logId, txData.c_str()); return (ret); } diff --git a/lib/VeDirectFrameHandler/VeDirectMpptController.cpp b/lib/VeDirectFrameHandler/VeDirectMpptController.cpp index 9e94356ae..ef3ef187b 100644 --- a/lib/VeDirectFrameHandler/VeDirectMpptController.cpp +++ b/lib/VeDirectFrameHandler/VeDirectMpptController.cpp @@ -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 #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,96 +90,156 @@ void VeDirectMpptController::frameValidEvent() { _efficiency.addNumber(static_cast(_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) { - - 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(); - } - } + if (atoi(_tmpFrame.FW) < 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 } -/* - * hexDataHandler() - * 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; - - switch (data.rsp) { - case R_GET: - case R_ASYNC: - - // check if MPPT internal temperature is available - if(data.id == 0xEDDB) { - _ExData.T = static_cast(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); - } +void VeDirectMpptController::loop() +{ + VeDirectFrameHandler::loop(); - // check if temperature from "Smart Battery Sense" is available - if(data.id == 0xEDEC) { - _ExData.TSBS = static_cast(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); + auto resetTimestamp = [this](auto& pair) { + if (pair.first > 0 && (millis() - pair.first) > (10 * 1000)) { + pair.first = 0; } + }; - // 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; + resetTimestamp(_tmpFrame.MpptTemperatureMilliCelsius); + resetTimestamp(_tmpFrame.SmartBatterySenseTemperatureMilliCelsius); + resetTimestamp(_tmpFrame.NetworkTotalDcInputPowerMilliWatts); - if constexpr(MODUL_DEBUG == 1) - _msgOut->printf("[VE.Direct] debug: hexDataHandler(), Total Power: %.2fW\r\n", _ExData.TDCP/1000.0); - } +#ifdef PROCESS_NETWORK_STATE + resetTimestamp(_tmpFrame.NetworkInfo); + resetTimestamp(_tmpFrame.NetworkMode); + resetTimestamp(_tmpFrame.NetworkStatus); +#endif // PROCESS_NETWORK_STATE +} - // 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 constexpr(MODUL_DEBUG == 1) - _msgOut->printf("[VE.Direct] debug: hexDataHandler(), Networkmode: 0x%X\r\n", data.value); - } - break; - default: - break; +/* + * hexDataHandler() + * analyse the content of VE.Direct hex messages + * Handels the received hex data from the MPPT + */ +bool VeDirectMpptController::hexDataHandler(VeDirectHexData const &data) { + if (data.rsp != VeDirectHexResponse::GET && + data.rsp != VeDirectHexResponse::ASYNC) { return false; } + + auto regLog = static_cast(data.addr); + + switch (data.addr) { + case VeDirectHexRegister::ChargeControllerTemperature: + _tmpFrame.MpptTemperatureMilliCelsius = + { millis(), static_cast(data.value) * 10 }; + + 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; + + 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 + } + + _tmpFrame.SmartBatterySenseTemperatureMilliCelsius = + { millis(), static_cast(data.value) * 10 - 272150 }; + + 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; + + 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(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(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(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; +} diff --git a/lib/VeDirectFrameHandler/VeDirectMpptController.h b/lib/VeDirectFrameHandler/VeDirectMpptController.h index ebee88513..595988985 100644 --- a/lib/VeDirectFrameHandler/VeDirectMpptController.h +++ b/lib/VeDirectFrameHandler/VeDirectMpptController.h @@ -44,24 +44,11 @@ class VeDirectMpptController : public VeDirectFrameHandler { using data_t = veMpptStruct; - virtual void loop() final; // main loop to read ve.direct data - - struct veMPPTExStruct { - int32_t T; // temperature [m°C] from internal MPPT sensor - unsigned long Tts; // time of last recieved value - int32_t TSBS; // temperature [m°C] from the "Smart Battery Sense" - unsigned long TSBSts; // time of last recieved value - uint32_t TDCP; // total DC input power [mW] - unsigned long TDCPts; // time of last recieved value - }; - veMPPTExStruct _ExData{}; - veMPPTExStruct const *getExData() const { return &_ExData; } + void loop() final; private: - void hexDataHandler(VeDirectHexData const &data) final; + bool hexDataHandler(VeDirectHexData const &data) final; bool processTextDataDerived(std::string const& name, std::string const& value) final; void frameValidEvent() final; MovingAverage _efficiency; - unsigned long _lastPingTime = 0L; // time of last device PING/GET hex command - bool _veMaster = true; // MPPT is instance master }; diff --git a/src/WebApi_ws_vedirect_live.cpp b/src/WebApi_ws_vedirect_live.cpp index d843475c4..c9c556c10 100644 --- a/src/WebApi_ws_vedirect_live.cpp +++ b/src/WebApi_ws_vedirect_live.cpp @@ -146,57 +146,64 @@ void WebApiWsVedirectLiveClass::generateJsonResponse(JsonVariant& root, bool ful } void WebApiWsVedirectLiveClass::populateJson(const JsonObject &root, const VeDirectMpptController::data_t &mpptData) { - // device info - root["device"]["PID"] = mpptData.getPidAsString(); - root["device"]["SER"] = String(mpptData.SER); - root["device"]["FW"] = String(mpptData.FW); - root["device"]["LOAD"] = mpptData.LOAD ? "ON" : "OFF"; - root["device"]["CS"] = mpptData.getCsAsString(); - root["device"]["ERR"] = mpptData.getErrAsString(); - root["device"]["OR"] = mpptData.getOrAsString(); - root["device"]["MPPT"] = mpptData.getMpptAsString(); - root["device"]["HSDS"]["v"] = mpptData.HSDS; - root["device"]["HSDS"]["u"] = "d"; - - // battery info - root["output"]["P"]["v"] = mpptData.P; - root["output"]["P"]["u"] = "W"; - root["output"]["P"]["d"] = 0; - root["output"]["V"]["v"] = mpptData.V; - root["output"]["V"]["u"] = "V"; - root["output"]["V"]["d"] = 2; - root["output"]["I"]["v"] = mpptData.I; - root["output"]["I"]["u"] = "A"; - root["output"]["I"]["d"] = 2; - root["output"]["E"]["v"] = mpptData.E; - root["output"]["E"]["u"] = "%"; - root["output"]["E"]["d"] = 1; - - // panel info - root["input"]["PPV"]["v"] = mpptData.PPV; - root["input"]["PPV"]["u"] = "W"; - root["input"]["PPV"]["d"] = 0; - root["input"]["VPV"]["v"] = mpptData.VPV; - root["input"]["VPV"]["u"] = "V"; - root["input"]["VPV"]["d"] = 2; - root["input"]["IPV"]["v"] = mpptData.IPV; - root["input"]["IPV"]["u"] = "A"; - root["input"]["IPV"]["d"] = 2; - root["input"]["YieldToday"]["v"] = mpptData.H20; - root["input"]["YieldToday"]["u"] = "kWh"; - root["input"]["YieldToday"]["d"] = 3; - root["input"]["YieldYesterday"]["v"] = mpptData.H22; - root["input"]["YieldYesterday"]["u"] = "kWh"; - root["input"]["YieldYesterday"]["d"] = 3; - root["input"]["YieldTotal"]["v"] = mpptData.H19; - root["input"]["YieldTotal"]["u"] = "kWh"; - root["input"]["YieldTotal"]["d"] = 3; - root["input"]["MaximumPowerToday"]["v"] = mpptData.H21; - root["input"]["MaximumPowerToday"]["u"] = "W"; - root["input"]["MaximumPowerToday"]["d"] = 0; - root["input"]["MaximumPowerYesterday"]["v"] = mpptData.H23; - root["input"]["MaximumPowerYesterday"]["u"] = "W"; - root["input"]["MaximumPowerYesterday"]["d"] = 0; + root["product_id"] = mpptData.getPidAsString(); + root["firmware_version"] = String(mpptData.FW); + + const JsonObject &values = root.createNestedObject("values"); + + const JsonObject &device = values.createNestedObject("device"); + device["LOAD"] = mpptData.LOAD ? "ON" : "OFF"; + device["CS"] = mpptData.getCsAsString(); + device["MPPT"] = mpptData.getMpptAsString(); + device["OR"] = mpptData.getOrAsString(); + device["ERR"] = mpptData.getErrAsString(); + device["HSDS"]["v"] = mpptData.HSDS; + device["HSDS"]["u"] = "d"; + if (mpptData.MpptTemperatureMilliCelsius.first > 0) { + device["MpptTemperature"]["v"] = mpptData.MpptTemperatureMilliCelsius.second / 1000.0; + device["MpptTemperature"]["u"] = "°C"; + device["MpptTemperature"]["d"] = "1"; + } + + const JsonObject &output = values.createNestedObject("output"); + output["P"]["v"] = mpptData.P; + output["P"]["u"] = "W"; + output["P"]["d"] = 0; + output["V"]["v"] = mpptData.V; + output["V"]["u"] = "V"; + output["V"]["d"] = 2; + output["I"]["v"] = mpptData.I; + output["I"]["u"] = "A"; + output["I"]["d"] = 2; + output["E"]["v"] = mpptData.E; + output["E"]["u"] = "%"; + output["E"]["d"] = 1; + + const JsonObject &input = values.createNestedObject("input"); + input["PPV"]["v"] = mpptData.PPV; + input["PPV"]["u"] = "W"; + input["PPV"]["d"] = 0; + input["VPV"]["v"] = mpptData.VPV; + input["VPV"]["u"] = "V"; + input["VPV"]["d"] = 2; + input["IPV"]["v"] = mpptData.IPV; + input["IPV"]["u"] = "A"; + input["IPV"]["d"] = 2; + input["YieldToday"]["v"] = mpptData.H20; + input["YieldToday"]["u"] = "kWh"; + input["YieldToday"]["d"] = 3; + input["YieldYesterday"]["v"] = mpptData.H22; + input["YieldYesterday"]["u"] = "kWh"; + input["YieldYesterday"]["d"] = 3; + input["YieldTotal"]["v"] = mpptData.H19; + input["YieldTotal"]["u"] = "kWh"; + input["YieldTotal"]["d"] = 3; + input["MaximumPowerToday"]["v"] = mpptData.H21; + input["MaximumPowerToday"]["u"] = "W"; + input["MaximumPowerToday"]["d"] = 0; + input["MaximumPowerYesterday"]["v"] = mpptData.H23; + input["MaximumPowerYesterday"]["u"] = "W"; + input["MaximumPowerYesterday"]["d"] = 0; } void WebApiWsVedirectLiveClass::onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len) diff --git a/webapp/src/components/VedirectView.vue b/webapp/src/components/VedirectView.vue index 91efeaebc..e79b265fe 100644 --- a/webapp/src/components/VedirectView.vue +++ b/webapp/src/components/VedirectView.vue @@ -18,13 +18,13 @@
- {{ item.device.PID }} + {{ item.product_id }}
- {{ $t('vedirecthome.SerialNumber') }} {{ item.device.SER }} + {{ $t('vedirecthome.SerialNumber') }} {{ serial }}
- {{ $t('vedirecthome.FirmwareNumber') }} {{ item.device.FW }} + {{ $t('vedirecthome.FirmwareNumber') }} {{ item.firmware_version }}
{{ $t('vedirecthome.DataAge') }} {{ $t('vedirecthome.Seconds', {'val': Math.floor(item.data_age_ms / 1000)}) }} @@ -55,9 +55,9 @@
-
-
-
{{ $t('vedirecthome.DeviceInfo') }}
+
+
+
{{ $t('vedirecthome.section_' + section) }}
@@ -69,95 +69,21 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{ $t('vedirecthome.LoadOutputState') }}{{item.device.LOAD}}
{{ $t('vedirecthome.StateOfOperation') }}{{item.device.CS}}
{{ $t('vedirecthome.TrackerOperationMode') }}{{item.device.MPPT}}
{{ $t('vedirecthome.OffReason') }}{{item.device.OR}}
{{ $t('vedirecthome.ErrorCode') }}{{item.device.ERR}}
{{ $t('vedirecthome.DaySequenceNumber') }}{{item.device.HSDS.v}}{{item.device.HSDS.u}}
-
-
-
-
-
-
-
{{ $t('vedirecthome.Battery') }}
-
-
- - - - - - - - - - - - - - - -
{{ $t('vedirecthome.Property') }}{{ $t('vedirecthome.Value') }}{{ $t('vedirecthome.Unit') }}
{{ $t('vedirecthome.output.' + key) }} - {{ $n(prop.v, 'decimal', { - minimumFractionDigits: prop.d, - maximumFractionDigits: prop.d}) - }} - {{prop.u}}
-
-
-
-
-
-
-
{{ $t('vedirecthome.Panel') }}
-
-
- - - - - - - - - - - + + - + +
{{ $t('vedirecthome.Property') }}{{ $t('vedirecthome.Value') }}{{ $t('vedirecthome.Unit') }}
{{ $t('vedirecthome.input.' + key) }}
{{ $t('vedirecthome.' + section + '.' + key) }} - {{ $n(prop.v, 'decimal', { + + {{prop.u}}{{prop.u}}
diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index 3ee53c03e..e911c083b 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -153,24 +153,27 @@ "FirmwareNumber": "Firmware Version: ", "DataAge": "letzte Aktualisierung: ", "Seconds": "vor {val} Sekunden", - "DeviceInfo": "Geräteinformation", "Property": "Eigenschaft", "Value": "Wert", "Unit": "Einheit", - "LoadOutputState": "Status Ladeausgang", - "StateOfOperation": "Betriebszustand", - "TrackerOperationMode": "Betriebszustand des Trackers", - "OffReason": "Grund für das Ausschalten", - "ErrorCode": "Fehlerbeschreibung", - "DaySequenceNumber": "Anzahl der Tage (0..364)", - "Battery": "Ausgang (Batterie)", + "section_device": "Geräteinformation", + "device": { + "LOAD": "Status Ladeausgang", + "CS": "Betriebszustand", + "MPPT": "Betriebszustand des Trackers", + "OR": "Grund für das Ausschalten", + "ERR": "Fehlerbeschreibung", + "HSDS": "Anzahl der Tage (0..364)", + "MpptTemperature": "Ladereglertemperatur" + }, + "section_output": "Ausgang (Batterie)", "output": { "P": "Leistung (berechnet)", "V": "Spannung", "I": "Strom", "E": "Effizienz (berechnet)" }, - "Panel": "Eingang (Solarpanele)", + "section_input": "Eingang (Solarpanele)", "input": { "PPV": "Leistung", "VPV": "Spannung", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 77c087463..25973f8db 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -153,24 +153,27 @@ "FirmwareNumber": "Firmware Number: ", "DataAge": "Data Age: ", "Seconds": "{val} seconds", - "DeviceInfo": "Device Info", "Property": "Property", "Value": "Value", "Unit": "Unit", - "LoadOutputState": "Load output state", - "StateOfOperation": "State of operation", - "TrackerOperationMode": "Tracker operation mode", - "OffReason": "Off reason", - "ErrorCode": "Error code", - "DaySequenceNumber": "Day sequence number (0..364)", - "Battery": "Output (Battery)", + "section_device": "Device Info", + "device": { + "LOAD": "Load output state", + "CS": "State of operation", + "MPPT": "Tracker operation mode", + "OR": "Off reason", + "ERR": "Error code", + "HSDS": "Day sequence number (0..364)", + "MpptTemperature": "Charge controller temperature" + }, + "section_output": "Output (Battery)", "output": { "P": "Power (calculated)", "V": "Voltage", "I": "Current", "E": "Efficiency (calculated)" }, - "Panel": "Input (Solar Panels)", + "section_input": "Input (Solar Panels)", "input": { "PPV": "Power", "VPV": "Voltage", diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index d60cb5b65..2231bc7da 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -153,24 +153,27 @@ "FirmwareNumber": "Firmware Number: ", "DataAge": "Data Age: ", "Seconds": "{val} seconds", - "DeviceInfo": "Device Info", "Property": "Property", "Value": "Value", "Unit": "Unit", - "LoadOutputState": "Load output state", - "StateOfOperation": "State of operation", - "TrackerOperationMode": "Tracker operation mode", - "OffReason": "Off reason", - "ErrorCode": "Error code", - "DaySequenceNumber": "Day sequence number (0..364)", - "Battery": "Output (Battery)", + "section_device": "Device Info", + "device": { + "LOAD": "Load output state", + "CS": "State of operation", + "MPPT": "Tracker operation mode", + "OR": "Off reason", + "ERR": "Error code", + "HSDS": "Day sequence number (0..364)", + "MpptTemperature": "Charge controller temperature" + }, + "section_output": "Output (Battery)", "output": { "P": "Power (calculated)", "V": "Voltage", "I": "Current", "E": "Efficiency (calculated)" }, - "Panel": "Input (Solar Panels)", + "section_input": "Input (Solar Panels)", "input": { "PPV": "Power", "VPV": "Voltage", diff --git a/webapp/src/types/VedirectLiveDataStatus.ts b/webapp/src/types/VedirectLiveDataStatus.ts index 73b78a454..0cdb996fb 100644 --- a/webapp/src/types/VedirectLiveDataStatus.ts +++ b/webapp/src/types/VedirectLiveDataStatus.ts @@ -10,39 +10,11 @@ export interface Vedirect { instances: { [key: string]: VedirectInstance }; } +type MpptData = (ValueObject | string)[]; + export interface VedirectInstance { data_age_ms: number; - device: VedirectDevice; - output: VedirectOutput; - input: VedirectInput; -} - -export interface VedirectDevice { - SER: string; - PID: string; - FW: string; - LOAD: ValueObject; - CS: ValueObject; - MPPT: ValueObject; - OR: ValueObject; - ERR: ValueObject; - HSDS: ValueObject; -} - -export interface VedirectOutput { - P: ValueObject; - V: ValueObject; - I: ValueObject; - E: ValueObject; -} - -export interface VedirectInput { - PPV: ValueObject; - VPV: ValueObject; - IPV: ValueObject; - H19: ValueObject; - H20: ValueObject; - H21: ValueObject; - H22: ValueObject; - H23: ValueObject; + product_id: string; + firmware_version: string; + values: { [key: string]: MpptData }; }