First try to implement Alarm Log fetching

This commit is contained in:
Thomas Basler 2022-07-06 19:17:52 +02:00
parent 4f2d705314
commit b9bb753906
9 changed files with 339 additions and 2 deletions

View File

@ -27,6 +27,9 @@ void HoymilesClass::loop()
Serial.println(iv->serial(), HEX); Serial.println(iv->serial(), HEX);
iv->sendStatsRequest(_radio.get()); iv->sendStatsRequest(_radio.get());
// Fetch event log
iv->sendAlarmLogRequest(_radio.get());
} }
if (++inverterPos >= getNumInverters()) { if (++inverterPos >= getNumInverters()) {

View File

@ -120,6 +120,7 @@ void HoymilesRadio::loop()
inverter_transaction_t* t = _txBuffer.getBack(); inverter_transaction_t* t = _txBuffer.getBack();
auto inv = Hoymiles.getInverterBySerial(t->target.u64); auto inv = Hoymiles.getInverterBySerial(t->target.u64);
inv->setLastRequest(t->requestType); inv->setLastRequest(t->requestType);
inv->clearRxFragmentBuffer();
sendEsbPacket(t->target, t->mainCmd, t->subCmd, t->payload, t->len, t->timeout); sendEsbPacket(t->target, t->mainCmd, t->subCmd, t->payload, t->len, t->timeout);
_txBuffer.popBack(); _txBuffer.popBack();
} }

View File

@ -37,7 +37,49 @@ bool HM_Abstract::sendStatsRequest(HoymilesRadio* radio)
payload.requestType = RequestType::Stats; 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); radio->enqueTransaction(&payload);
return true; return true;
} }

View File

@ -6,4 +6,8 @@ class HM_Abstract : public InverterAbstract {
public: public:
HM_Abstract(uint64_t serial); HM_Abstract(uint64_t serial);
bool sendStatsRequest(HoymilesRadio* radio); bool sendStatsRequest(HoymilesRadio* radio);
bool sendAlarmLogRequest(HoymilesRadio* radio);
private:
uint8_t _lastAlarmLogCnt = 0;
}; };

View File

@ -5,6 +5,7 @@
InverterAbstract::InverterAbstract(uint64_t serial) InverterAbstract::InverterAbstract(uint64_t serial)
{ {
_serial.u64 = serial; _serial.u64 = serial;
_alarmLogParser.reset(new AlarmLogParser());
memset(_payloadStats, 0, MAX_RF_FRAGMENT_COUNT * MAX_RF_PAYLOAD_SIZE); memset(_payloadStats, 0, MAX_RF_FRAGMENT_COUNT * MAX_RF_PAYLOAD_SIZE);
} }
@ -28,6 +29,11 @@ const char* InverterAbstract::name()
return _name; return _name;
} }
AlarmLogParser* InverterAbstract::EventLog()
{
return _alarmLogParser.get();
}
void InverterAbstract::clearRxFragmentBuffer() void InverterAbstract::clearRxFragmentBuffer()
{ {
memset(_rxFragmentBuffer, 0, MAX_RF_FRAGMENT_COUNT * MAX_RF_PAYLOAD_SIZE); memset(_rxFragmentBuffer, 0, MAX_RF_FRAGMENT_COUNT * MAX_RF_PAYLOAD_SIZE);
@ -113,6 +119,17 @@ uint8_t InverterAbstract::verifyAllFragments()
offs += (_rxFragmentBuffer[i].len); offs += (_rxFragmentBuffer[i].len);
} }
_lastStatsUpdate = millis(); _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 { } else {
Serial.println("Unkown response received"); Serial.println("Unkown response received");
} }

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "../parser/AlarmLogParser.h"
#include "HoymilesRadio.h" #include "HoymilesRadio.h"
#include "types.h" #include "types.h"
#include <Arduino.h> #include <Arduino.h>
@ -130,10 +131,13 @@ public:
const char* getChannelFieldName(uint8_t channel, uint8_t fieldId); const char* getChannelFieldName(uint8_t channel, uint8_t fieldId);
virtual bool sendStatsRequest(HoymilesRadio* radio) = 0; virtual bool sendStatsRequest(HoymilesRadio* radio) = 0;
virtual bool sendAlarmLogRequest(HoymilesRadio* radio) = 0;
uint32_t getLastStatsUpdate(); uint32_t getLastStatsUpdate();
void setLastRequest(RequestType request); void setLastRequest(RequestType request);
AlarmLogParser* EventLog();
protected: protected:
RequestType getLastRequest(); RequestType getLastRequest();
@ -147,7 +151,10 @@ private:
uint8_t _payloadStats[MAX_RF_FRAGMENT_COUNT * MAX_RF_PAYLOAD_SIZE]; uint8_t _payloadStats[MAX_RF_FRAGMENT_COUNT * MAX_RF_PAYLOAD_SIZE];
uint32_t _lastStatsUpdate = 0; uint32_t _lastStatsUpdate = 0;
uint32_t _lastAlarmLogUpdate = 0;
uint16_t _chanMaxPower[CH4]; uint16_t _chanMaxPower[CH4];
RequestType _lastRequest = RequestType::None; RequestType _lastRequest = RequestType::None;
std::unique_ptr<AlarmLogParser> _alarmLogParser;
}; };

View File

@ -0,0 +1,236 @@
#include "AlarmLogParser.h"
#include <cstring>
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;
}
}

View File

@ -0,0 +1,26 @@
#pragma once
#include <cstdint>
#include <Arduino.h>
#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;
};

View File

@ -17,7 +17,8 @@ typedef struct {
enum class RequestType { enum class RequestType {
None, None,
Stats Stats,
AlarmLog
}; };
typedef struct { typedef struct {