From b9bb753906ba11dedd06ca2ee455713dad72e9bd Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Wed, 6 Jul 2022 19:17:52 +0200 Subject: [PATCH] First try to implement Alarm Log fetching --- lib/Hoymiles/src/Hoymiles.cpp | 3 + lib/Hoymiles/src/HoymilesRadio.cpp | 1 + lib/Hoymiles/src/inverters/HM_Abstract.cpp | 44 +++- lib/Hoymiles/src/inverters/HM_Abstract.h | 4 + .../src/inverters/InverterAbstract.cpp | 17 ++ lib/Hoymiles/src/inverters/InverterAbstract.h | 7 + lib/Hoymiles/src/parser/AlarmLogParser.cpp | 236 ++++++++++++++++++ lib/Hoymiles/src/parser/AlarmLogParser.h | 26 ++ lib/Hoymiles/src/types.h | 3 +- 9 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 lib/Hoymiles/src/parser/AlarmLogParser.cpp create mode 100644 lib/Hoymiles/src/parser/AlarmLogParser.h diff --git a/lib/Hoymiles/src/Hoymiles.cpp b/lib/Hoymiles/src/Hoymiles.cpp index a8b79181..db739c72 100644 --- a/lib/Hoymiles/src/Hoymiles.cpp +++ b/lib/Hoymiles/src/Hoymiles.cpp @@ -27,6 +27,9 @@ void HoymilesClass::loop() Serial.println(iv->serial(), HEX); iv->sendStatsRequest(_radio.get()); + + // Fetch event log + iv->sendAlarmLogRequest(_radio.get()); } if (++inverterPos >= getNumInverters()) { diff --git a/lib/Hoymiles/src/HoymilesRadio.cpp b/lib/Hoymiles/src/HoymilesRadio.cpp index 102492fe..b584c93e 100644 --- a/lib/Hoymiles/src/HoymilesRadio.cpp +++ b/lib/Hoymiles/src/HoymilesRadio.cpp @@ -120,6 +120,7 @@ void HoymilesRadio::loop() inverter_transaction_t* t = _txBuffer.getBack(); auto inv = Hoymiles.getInverterBySerial(t->target.u64); inv->setLastRequest(t->requestType); + inv->clearRxFragmentBuffer(); sendEsbPacket(t->target, t->mainCmd, t->subCmd, t->payload, t->len, t->timeout); _txBuffer.popBack(); } diff --git a/lib/Hoymiles/src/inverters/HM_Abstract.cpp b/lib/Hoymiles/src/inverters/HM_Abstract.cpp index e9de51e5..2ecfd2e8 100644 --- a/lib/Hoymiles/src/inverters/HM_Abstract.cpp +++ b/lib/Hoymiles/src/inverters/HM_Abstract.cpp @@ -37,7 +37,49 @@ bool HM_Abstract::sendStatsRequest(HoymilesRadio* radio) payload.requestType = RequestType::Stats; - clearRxFragmentBuffer(); + radio->enqueTransaction(&payload); + return true; +} + +bool HM_Abstract::sendAlarmLogRequest(HoymilesRadio* radio) +{ + struct tm timeinfo; + if (!getLocalTime(&timeinfo)) { + return false; + } + + if (hasChannelFieldValue(CH0, FLD_EVT_LOG)) { + if ((uint8_t)getChannelFieldValue(CH0, FLD_EVT_LOG) == _lastAlarmLogCnt) { + return false; + } + } + + _lastAlarmLogCnt = (uint8_t)getChannelFieldValue(CH0, FLD_EVT_LOG); + + time_t now; + time(&now); + + inverter_transaction_t payload; + + memset(payload.payload, 0, MAX_RF_PAYLOAD_SIZE); + + payload.target.u64 = serial(); + payload.mainCmd = 0x15; + payload.subCmd = 0x80; + payload.timeout = 200; + payload.len = 16; + + payload.payload[0] = 0x11; + payload.payload[1] = 0x00; + + HoymilesRadio::u32CpyLittleEndian(&payload.payload[2], now); // sets the 4 following elements {2, 3, 4, 5} + + uint16_t crc = crc16(&payload.payload[0], 14); + payload.payload[14] = (crc >> 8) & 0xff; + payload.payload[15] = (crc)&0xff; + + payload.requestType = RequestType::AlarmLog; + radio->enqueTransaction(&payload); return true; } \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HM_Abstract.h b/lib/Hoymiles/src/inverters/HM_Abstract.h index 94e2941a..8c79653a 100644 --- a/lib/Hoymiles/src/inverters/HM_Abstract.h +++ b/lib/Hoymiles/src/inverters/HM_Abstract.h @@ -6,4 +6,8 @@ class HM_Abstract : public InverterAbstract { public: HM_Abstract(uint64_t serial); bool sendStatsRequest(HoymilesRadio* radio); + bool sendAlarmLogRequest(HoymilesRadio* radio); + +private: + uint8_t _lastAlarmLogCnt = 0; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/InverterAbstract.cpp b/lib/Hoymiles/src/inverters/InverterAbstract.cpp index 4ae31a70..e1505cf2 100644 --- a/lib/Hoymiles/src/inverters/InverterAbstract.cpp +++ b/lib/Hoymiles/src/inverters/InverterAbstract.cpp @@ -5,6 +5,7 @@ InverterAbstract::InverterAbstract(uint64_t serial) { _serial.u64 = serial; + _alarmLogParser.reset(new AlarmLogParser()); memset(_payloadStats, 0, MAX_RF_FRAGMENT_COUNT * MAX_RF_PAYLOAD_SIZE); } @@ -28,6 +29,11 @@ const char* InverterAbstract::name() return _name; } +AlarmLogParser* InverterAbstract::EventLog() +{ + return _alarmLogParser.get(); +} + void InverterAbstract::clearRxFragmentBuffer() { memset(_rxFragmentBuffer, 0, MAX_RF_FRAGMENT_COUNT * MAX_RF_PAYLOAD_SIZE); @@ -113,6 +119,17 @@ uint8_t InverterAbstract::verifyAllFragments() offs += (_rxFragmentBuffer[i].len); } _lastStatsUpdate = millis(); + + } else if (getLastRequest() == RequestType::AlarmLog) { + // Move all fragments into target buffer + uint8_t offs = 0; + _alarmLogParser.get()->clearBuffer(); + for (uint8_t i = 0; i < _rxFragmentMaxPacketId; i++) { + _alarmLogParser.get()->appendFragment(offs, _rxFragmentBuffer[i].fragment, _rxFragmentBuffer[i].len); + offs += (_rxFragmentBuffer[i].len); + } + _lastAlarmLogUpdate = millis(); + } else { Serial.println("Unkown response received"); } diff --git a/lib/Hoymiles/src/inverters/InverterAbstract.h b/lib/Hoymiles/src/inverters/InverterAbstract.h index d9d26d6f..e0ded0c7 100644 --- a/lib/Hoymiles/src/inverters/InverterAbstract.h +++ b/lib/Hoymiles/src/inverters/InverterAbstract.h @@ -1,5 +1,6 @@ #pragma once +#include "../parser/AlarmLogParser.h" #include "HoymilesRadio.h" #include "types.h" #include @@ -130,10 +131,13 @@ public: const char* getChannelFieldName(uint8_t channel, uint8_t fieldId); virtual bool sendStatsRequest(HoymilesRadio* radio) = 0; + virtual bool sendAlarmLogRequest(HoymilesRadio* radio) = 0; uint32_t getLastStatsUpdate(); void setLastRequest(RequestType request); + AlarmLogParser* EventLog(); + protected: RequestType getLastRequest(); @@ -147,7 +151,10 @@ private: uint8_t _payloadStats[MAX_RF_FRAGMENT_COUNT * MAX_RF_PAYLOAD_SIZE]; uint32_t _lastStatsUpdate = 0; + uint32_t _lastAlarmLogUpdate = 0; uint16_t _chanMaxPower[CH4]; RequestType _lastRequest = RequestType::None; + + std::unique_ptr _alarmLogParser; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/parser/AlarmLogParser.cpp b/lib/Hoymiles/src/parser/AlarmLogParser.cpp new file mode 100644 index 00000000..3408f1b6 --- /dev/null +++ b/lib/Hoymiles/src/parser/AlarmLogParser.cpp @@ -0,0 +1,236 @@ +#include "AlarmLogParser.h" +#include + +void AlarmLogParser::clearBuffer() +{ + memset(_payloadAlarmLog, 0, ALARM_LOG_ENTRY_COUNT * ALARM_LOG_ENTRY_SIZE); + _alarmLogLength = 0; +} + +void AlarmLogParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t len) +{ + memcpy(&_payloadAlarmLog[offset], payload, len); + _alarmLogLength += len; +} + +uint8_t AlarmLogParser::getEntryCount() +{ + return (_alarmLogLength - 2) / ALARM_LOG_ENTRY_SIZE; +} + +void AlarmLogParser::getLogEntry(uint8_t entryId, AlarmLogEntry* entry) +{ + uint8_t entryStartOffset = 2 + entryId * ALARM_LOG_ENTRY_SIZE; + + entry->MessageId = _payloadAlarmLog[entryStartOffset + 1]; + + switch (entry->MessageId) { + case 1: + entry->Message = String(F("Inverter start")); + break; + case 2: + entry->Message = String(F("DTU command failed")); + break; + case 121: + entry->Message = String(F("Over temperature protection")); + break; + case 125: + entry->Message = String(F("Grid configuration parameter error")); + break; + case 126: + entry->Message = String(F("Software error code 126")); + break; + case 127: + entry->Message = String(F("Firmware error")); + break; + case 128: + entry->Message = String(F("Software error code 128")); + break; + case 129: + entry->Message = String(F("Software error code 129")); + break; + case 130: + entry->Message = String(F("Offline")); + break; + case 141: + entry->Message = String(F("Grid overvoltage")); + break; + case 142: + entry->Message = String(F("Average grid overvoltage")); + break; + case 143: + entry->Message = String(F("Grid undervoltage")); + break; + case 144: + entry->Message = String(F("Grid overfrequency")); + break; + case 145: + entry->Message = String(F("Grid underfrequency")); + break; + case 146: + entry->Message = String(F("Rapid grid frequency change")); + break; + case 147: + entry->Message = String(F("Power grid outage")); + break; + case 148: + entry->Message = String(F("Grid disconnection")); + break; + case 149: + entry->Message = String(F("Island detected")); + break; + case 205: + entry->Message = String(F("Input port 1 & 2 overvoltage")); + break; + case 206: + entry->Message = String(F("Input port 3 & 4 overvoltage")); + break; + case 207: + entry->Message = String(F("Input port 1 & 2 undervoltage")); + break; + case 208: + entry->Message = String(F("Input port 3 & 4 undervoltage")); + break; + case 209: + entry->Message = String(F("Port 1 no input")); + break; + case 210: + entry->Message = String(F("Port 2 no input")); + break; + case 211: + entry->Message = String(F("Port 3 no input")); + break; + case 212: + entry->Message = String(F("Port 4 no input")); + break; + case 213: + entry->Message = String(F("PV-1 & PV-2 abnormal wiring")); + break; + case 214: + entry->Message = String(F("PV-3 & PV-4 abnormal wiring")); + break; + case 215: + entry->Message = String(F("PV-1 Input overvoltage")); + break; + case 216: + entry->Message = String(F("PV-1 Input undervoltage")); + break; + case 217: + entry->Message = String(F("PV-2 Input overvoltage")); + break; + case 218: + entry->Message = String(F("PV-2 Input undervoltage")); + break; + case 219: + entry->Message = String(F("PV-3 Input overvoltage")); + break; + case 220: + entry->Message = String(F("PV-3 Input undervoltage")); + break; + case 221: + entry->Message = String(F("PV-4 Input overvoltage")); + break; + case 222: + entry->Message = String(F("PV-4 Input undervoltage")); + break; + case 301: + entry->Message = String(F("Hardware error code 301")); + break; + case 302: + entry->Message = String(F("Hardware error code 302")); + break; + case 303: + entry->Message = String(F("Hardware error code 303")); + break; + case 304: + entry->Message = String(F("Hardware error code 304")); + break; + case 305: + entry->Message = String(F("Hardware error code 305")); + break; + case 306: + entry->Message = String(F("Hardware error code 306")); + break; + case 307: + entry->Message = String(F("Hardware error code 307")); + break; + case 308: + entry->Message = String(F("Hardware error code 308")); + break; + case 309: + entry->Message = String(F("Hardware error code 309")); + break; + case 310: + entry->Message = String(F("Hardware error code 310")); + break; + case 311: + entry->Message = String(F("Hardware error code 311")); + break; + case 312: + entry->Message = String(F("Hardware error code 312")); + break; + case 313: + entry->Message = String(F("Hardware error code 313")); + break; + case 314: + entry->Message = String(F("Hardware error code 314")); + break; + case 5041: + entry->Message = String(F("Error code-04 Port 1")); + break; + case 5042: + entry->Message = String(F("Error code-04 Port 2")); + break; + case 5043: + entry->Message = String(F("Error code-04 Port 3")); + break; + case 5044: + entry->Message = String(F("Error code-04 Port 4")); + break; + case 5051: + entry->Message = String(F("PV Input 1 Overvoltage/Undervoltage")); + break; + case 5052: + entry->Message = String(F("PV Input 2 Overvoltage/Undervoltage")); + break; + case 5053: + entry->Message = String(F("PV Input 3 Overvoltage/Undervoltage")); + break; + case 5054: + entry->Message = String(F("PV Input 4 Overvoltage/Undervoltage")); + break; + case 5060: + entry->Message = String(F("Abnormal bias")); + break; + case 5070: + entry->Message = String(F("Over temperature protection")); + break; + case 5080: + entry->Message = String(F("Grid Overvoltage/Undervoltage")); + break; + case 5090: + entry->Message = String(F("Grid Overfrequency/Underfrequency")); + break; + case 5100: + entry->Message = String(F("Island detected")); + break; + case 5120: + entry->Message = String(F("EEPROM reading and writing error")); + break; + case 5150: + entry->Message = String(F("10 min value grid overvoltage")); + break; + case 5200: + entry->Message = String(F("Firmware error")); + break; + case 8310: + entry->Message = String(F("Shut down")); + break; + case 9000: + entry->Message = String(F("Microinverter is suspected of being stolen")); + break; + default: + entry->Message = String(F("Unknown")); + break; + } +} \ No newline at end of file diff --git a/lib/Hoymiles/src/parser/AlarmLogParser.h b/lib/Hoymiles/src/parser/AlarmLogParser.h new file mode 100644 index 00000000..b89ec48a --- /dev/null +++ b/lib/Hoymiles/src/parser/AlarmLogParser.h @@ -0,0 +1,26 @@ +#pragma once +#include +#include + +#define ALARM_LOG_ENTRY_COUNT 15 +#define ALARM_LOG_ENTRY_SIZE 12 + +struct AlarmLogEntry { + uint16_t MessageId; + String Message; + time_t StartTime; + time_t EndTime; +}; + +class AlarmLogParser { +public: + void clearBuffer(); + void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len); + + uint8_t getEntryCount(); + void getLogEntry(uint8_t entryId, AlarmLogEntry* entry); + +private: + uint8_t _payloadAlarmLog[ALARM_LOG_ENTRY_SIZE * ALARM_LOG_ENTRY_COUNT]; + uint8_t _alarmLogLength; +}; \ No newline at end of file diff --git a/lib/Hoymiles/src/types.h b/lib/Hoymiles/src/types.h index 1d5f90fa..e74ace87 100644 --- a/lib/Hoymiles/src/types.h +++ b/lib/Hoymiles/src/types.h @@ -17,7 +17,8 @@ typedef struct { enum class RequestType { None, - Stats + Stats, + AlarmLog }; typedef struct {