Refactored Hoymiles Lib: Move statistics parser into separate class
This commit is contained in:
parent
96e66dde47
commit
5bb9acdbc6
@ -48,13 +48,13 @@ bool HM_Abstract::sendAlarmLogRequest(HoymilesRadio* radio)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasChannelFieldValue(CH0, FLD_EVT_LOG)) {
|
||||
if ((uint8_t)getChannelFieldValue(CH0, FLD_EVT_LOG) == _lastAlarmLogCnt) {
|
||||
if (Statistics()->hasChannelFieldValue(CH0, FLD_EVT_LOG)) {
|
||||
if ((uint8_t)Statistics()->getChannelFieldValue(CH0, FLD_EVT_LOG) == _lastAlarmLogCnt) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_lastAlarmLogCnt = (uint8_t)getChannelFieldValue(CH0, FLD_EVT_LOG);
|
||||
_lastAlarmLogCnt = (uint8_t)Statistics()->getChannelFieldValue(CH0, FLD_EVT_LOG);
|
||||
|
||||
time_t now;
|
||||
time(&now);
|
||||
|
||||
@ -6,7 +6,7 @@ InverterAbstract::InverterAbstract(uint64_t serial)
|
||||
{
|
||||
_serial.u64 = serial;
|
||||
_alarmLogParser.reset(new AlarmLogParser());
|
||||
memset(_payloadStats, 0, MAX_RF_FRAGMENT_COUNT * MAX_RF_PAYLOAD_SIZE);
|
||||
_statisticsParser.reset(new StatisticsParser());
|
||||
}
|
||||
|
||||
uint64_t InverterAbstract::serial()
|
||||
@ -34,6 +34,11 @@ AlarmLogParser* InverterAbstract::EventLog()
|
||||
return _alarmLogParser.get();
|
||||
}
|
||||
|
||||
StatisticsParser* InverterAbstract::Statistics()
|
||||
{
|
||||
return _statisticsParser.get();
|
||||
}
|
||||
|
||||
void InverterAbstract::clearRxFragmentBuffer()
|
||||
{
|
||||
memset(_rxFragmentBuffer, 0, MAX_RF_FRAGMENT_COUNT * MAX_RF_PAYLOAD_SIZE);
|
||||
@ -112,10 +117,11 @@ uint8_t InverterAbstract::verifyAllFragments()
|
||||
|
||||
if (getLastRequest() == RequestType::Stats) {
|
||||
// Move all fragments into target buffer
|
||||
memset(_payloadStats, 0, MAX_RF_FRAGMENT_COUNT * MAX_RF_PAYLOAD_SIZE);
|
||||
uint8_t offs = 0;
|
||||
_statisticsParser.get()->setByteAssignment(getByteAssignment(), getAssignmentCount());
|
||||
_statisticsParser.get()->clearBuffer();
|
||||
for (uint8_t i = 0; i < _rxFragmentMaxPacketId; i++) {
|
||||
memcpy(&_payloadStats[offs], _rxFragmentBuffer[i].fragment, _rxFragmentBuffer[i].len);
|
||||
_statisticsParser.get()->appendFragment(offs, _rxFragmentBuffer[i].fragment, _rxFragmentBuffer[i].len);
|
||||
offs += (_rxFragmentBuffer[i].len);
|
||||
}
|
||||
_lastStatsUpdate = millis();
|
||||
@ -151,152 +157,4 @@ void InverterAbstract::setLastRequest(RequestType request)
|
||||
RequestType InverterAbstract::getLastRequest()
|
||||
{
|
||||
return _lastRequest;
|
||||
}
|
||||
|
||||
uint8_t InverterAbstract::getChannelCount()
|
||||
{
|
||||
const byteAssign_t* b = getByteAssignment();
|
||||
uint8_t cnt = 0;
|
||||
for (uint8_t pos = 0; pos < getAssignmentCount(); pos++) {
|
||||
if (b[pos].ch > cnt) {
|
||||
cnt = b[pos].ch;
|
||||
}
|
||||
}
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
uint16_t InverterAbstract::getChannelMaxPower(uint8_t channel)
|
||||
{
|
||||
return _chanMaxPower[channel];
|
||||
}
|
||||
|
||||
void InverterAbstract::setChannelMaxPower(uint8_t channel, uint16_t power)
|
||||
{
|
||||
if (channel < CH4) {
|
||||
_chanMaxPower[channel] = power;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t InverterAbstract::getAssignIdxByChannelField(uint8_t channel, uint8_t fieldId)
|
||||
{
|
||||
const byteAssign_t* b = getByteAssignment();
|
||||
|
||||
uint8_t pos;
|
||||
for (pos = 0; pos < getAssignmentCount(); pos++) {
|
||||
if (b[pos].ch == channel && b[pos].fieldId == fieldId) {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
float InverterAbstract::getChannelFieldValue(uint8_t channel, uint8_t fieldId)
|
||||
{
|
||||
uint8_t pos = getAssignIdxByChannelField(channel, fieldId);
|
||||
if (pos == 0xff) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const byteAssign_t* b = getByteAssignment();
|
||||
|
||||
uint8_t ptr = b[pos].start;
|
||||
uint8_t end = ptr + b[pos].num;
|
||||
uint16_t div = b[pos].div;
|
||||
|
||||
if (CMD_CALC != div) {
|
||||
// Value is a static value
|
||||
uint32_t val = 0;
|
||||
do {
|
||||
val <<= 8;
|
||||
val |= _payloadStats[ptr];
|
||||
} while (++ptr != end);
|
||||
|
||||
return (float)(val) / (float)(div);
|
||||
} else {
|
||||
// Value has to be calculated
|
||||
return calcFunctions[b[pos].start].func(this, b[pos].num);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool InverterAbstract::hasChannelFieldValue(uint8_t channel, uint8_t fieldId)
|
||||
{
|
||||
uint8_t pos = getAssignIdxByChannelField(channel, fieldId);
|
||||
return pos != 0xff;
|
||||
}
|
||||
|
||||
const char* InverterAbstract::getChannelFieldUnit(uint8_t channel, uint8_t fieldId)
|
||||
{
|
||||
uint8_t pos = getAssignIdxByChannelField(channel, fieldId);
|
||||
const byteAssign_t* b = getByteAssignment();
|
||||
|
||||
return units[b[pos].unitId];
|
||||
}
|
||||
|
||||
const char* InverterAbstract::getChannelFieldName(uint8_t channel, uint8_t fieldId)
|
||||
{
|
||||
uint8_t pos = getAssignIdxByChannelField(channel, fieldId);
|
||||
const byteAssign_t* b = getByteAssignment();
|
||||
|
||||
return fields[b[pos].fieldId];
|
||||
}
|
||||
|
||||
static float calcYieldTotalCh0(InverterAbstract* iv, uint8_t arg0)
|
||||
{
|
||||
float yield = 0;
|
||||
for (uint8_t i = 1; i <= iv->getChannelCount(); i++) {
|
||||
yield += iv->getChannelFieldValue(i, FLD_YT);
|
||||
}
|
||||
return yield;
|
||||
}
|
||||
|
||||
static float calcYieldDayCh0(InverterAbstract* iv, uint8_t arg0)
|
||||
{
|
||||
float yield = 0;
|
||||
for (uint8_t i = 1; i <= iv->getChannelCount(); i++) {
|
||||
yield += iv->getChannelFieldValue(i, FLD_YD);
|
||||
}
|
||||
return yield;
|
||||
}
|
||||
|
||||
// arg0 = channel of source
|
||||
static float calcUdcCh(InverterAbstract* iv, uint8_t arg0)
|
||||
{
|
||||
return iv->getChannelFieldValue(arg0, FLD_UDC);
|
||||
}
|
||||
|
||||
static float calcPowerDcCh0(InverterAbstract* iv, uint8_t arg0)
|
||||
{
|
||||
float dcPower = 0;
|
||||
for (uint8_t i = 1; i <= iv->getChannelCount(); i++) {
|
||||
dcPower += iv->getChannelFieldValue(i, FLD_PDC);
|
||||
}
|
||||
return dcPower;
|
||||
}
|
||||
|
||||
// arg0 = channel
|
||||
static float calcEffiencyCh0(InverterAbstract* iv, uint8_t arg0)
|
||||
{
|
||||
float acPower = iv->getChannelFieldValue(CH0, FLD_PAC);
|
||||
float dcPower = 0;
|
||||
for (uint8_t i = 1; i <= iv->getChannelCount(); i++) {
|
||||
dcPower += iv->getChannelFieldValue(i, FLD_PDC);
|
||||
}
|
||||
if (dcPower > 0) {
|
||||
return acPower / dcPower * 100.0f;
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// arg0 = channel
|
||||
static float calcIrradiation(InverterAbstract* iv, uint8_t arg0)
|
||||
{
|
||||
if (NULL != iv) {
|
||||
if (iv->getChannelMaxPower(arg0 - 1) > 0)
|
||||
return iv->getChannelFieldValue(arg0, FLD_PDC) / iv->getChannelMaxPower(arg0 - 1) * 100.0f;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "../parser/AlarmLogParser.h"
|
||||
#include "../parser/StatisticsParser.h"
|
||||
#include "HoymilesRadio.h"
|
||||
#include "types.h"
|
||||
#include <Arduino.h>
|
||||
@ -8,60 +9,6 @@
|
||||
|
||||
#define MAX_NAME_LENGTH 32
|
||||
|
||||
// units
|
||||
enum {
|
||||
UNIT_V = 0,
|
||||
UNIT_A,
|
||||
UNIT_W,
|
||||
UNIT_WH,
|
||||
UNIT_KWH,
|
||||
UNIT_HZ,
|
||||
UNIT_C,
|
||||
UNIT_PCT,
|
||||
UNIT_CNT
|
||||
};
|
||||
const char* const units[] = { "V", "A", "W", "Wh", "kWh", "Hz", "°C", "%", "" };
|
||||
|
||||
// field types
|
||||
enum {
|
||||
FLD_UDC = 0,
|
||||
FLD_IDC,
|
||||
FLD_PDC,
|
||||
FLD_YD,
|
||||
FLD_YT,
|
||||
FLD_UAC,
|
||||
FLD_IAC,
|
||||
FLD_PAC,
|
||||
FLD_F,
|
||||
FLD_T,
|
||||
FLD_PCT,
|
||||
FLD_EFF,
|
||||
FLD_IRR,
|
||||
FLD_EVT_LOG
|
||||
};
|
||||
const char* const fields[] = { "Voltage", "Current", "Power", "YieldDay", "YieldTotal",
|
||||
"Voltage", "Current", "Power", "Frequency", "Temperature", "PowerFactor", "Effiency", "Irradiation", "EventLogCount" };
|
||||
|
||||
// indices to calculation functions, defined in hmInverter.h
|
||||
enum {
|
||||
CALC_YT_CH0 = 0,
|
||||
CALC_YD_CH0,
|
||||
CALC_UDC_CH,
|
||||
CALC_PDC_CH0,
|
||||
CALC_EFF_CH0,
|
||||
CALC_IRR_CH
|
||||
};
|
||||
enum { CMD_CALC = 0xffff };
|
||||
|
||||
// CH0 is default channel (freq, ac, temp)
|
||||
enum {
|
||||
CH0 = 0,
|
||||
CH1,
|
||||
CH2,
|
||||
CH3,
|
||||
CH4
|
||||
};
|
||||
|
||||
enum {
|
||||
FRAGMENT_ALL_MISSING = 255,
|
||||
FRAGMENT_RETRANSMIT_TIMEOUT = 254,
|
||||
@ -69,44 +16,9 @@ enum {
|
||||
FRAGMENT_OK = 0
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t fieldId; // field id
|
||||
uint8_t unitId; // uint id
|
||||
uint8_t ch; // channel 0 - 4
|
||||
uint8_t start; // pos of first byte in buffer
|
||||
uint8_t num; // number of bytes in buffer
|
||||
uint16_t div; // divisor / calc command
|
||||
} byteAssign_t;
|
||||
|
||||
#define MAX_RF_FRAGMENT_COUNT 5
|
||||
#define MAX_RETRANSMIT_COUNT 5
|
||||
|
||||
class InverterAbstract;
|
||||
|
||||
// prototypes
|
||||
static float calcYieldTotalCh0(InverterAbstract* iv, uint8_t arg0);
|
||||
static float calcYieldDayCh0(InverterAbstract* iv, uint8_t arg0);
|
||||
static float calcUdcCh(InverterAbstract* iv, uint8_t arg0);
|
||||
static float calcPowerDcCh0(InverterAbstract* iv, uint8_t arg0);
|
||||
static float calcEffiencyCh0(InverterAbstract* iv, uint8_t arg0);
|
||||
static float calcIrradiation(InverterAbstract* iv, uint8_t arg0);
|
||||
|
||||
using func_t = float(InverterAbstract*, uint8_t);
|
||||
|
||||
struct calcFunc_t {
|
||||
uint8_t funcId; // unique id
|
||||
func_t* func; // function pointer
|
||||
};
|
||||
|
||||
const calcFunc_t calcFunctions[] = {
|
||||
{ CALC_YT_CH0, &calcYieldTotalCh0 },
|
||||
{ CALC_YD_CH0, &calcYieldDayCh0 },
|
||||
{ CALC_UDC_CH, &calcUdcCh },
|
||||
{ CALC_PDC_CH0, &calcPowerDcCh0 },
|
||||
{ CALC_EFF_CH0, &calcEffiencyCh0 },
|
||||
{ CALC_IRR_CH, &calcIrradiation }
|
||||
};
|
||||
|
||||
class InverterAbstract {
|
||||
public:
|
||||
InverterAbstract(uint64_t serial);
|
||||
@ -116,20 +28,11 @@ public:
|
||||
virtual String typeName() = 0;
|
||||
virtual const byteAssign_t* getByteAssignment() = 0;
|
||||
virtual const uint8_t getAssignmentCount() = 0;
|
||||
uint8_t getChannelCount();
|
||||
uint16_t getChannelMaxPower(uint8_t channel);
|
||||
void setChannelMaxPower(uint8_t channel, uint16_t power);
|
||||
|
||||
void clearRxFragmentBuffer();
|
||||
void addRxFragment(uint8_t fragment[], uint8_t len);
|
||||
uint8_t verifyAllFragments();
|
||||
|
||||
uint8_t getAssignIdxByChannelField(uint8_t channel, uint8_t fieldId);
|
||||
float getChannelFieldValue(uint8_t channel, uint8_t fieldId);
|
||||
bool hasChannelFieldValue(uint8_t channel, uint8_t fieldId);
|
||||
const char* getChannelFieldUnit(uint8_t channel, uint8_t fieldId);
|
||||
const char* getChannelFieldName(uint8_t channel, uint8_t fieldId);
|
||||
|
||||
virtual bool sendStatsRequest(HoymilesRadio* radio) = 0;
|
||||
virtual bool sendAlarmLogRequest(HoymilesRadio* radio) = 0;
|
||||
uint32_t getLastStatsUpdate();
|
||||
@ -137,6 +40,7 @@ public:
|
||||
void setLastRequest(RequestType request);
|
||||
|
||||
AlarmLogParser* EventLog();
|
||||
StatisticsParser* Statistics();
|
||||
|
||||
protected:
|
||||
RequestType getLastRequest();
|
||||
@ -149,12 +53,11 @@ private:
|
||||
uint8_t _rxFragmentLastPacketId = 0;
|
||||
uint8_t _rxFragmentRetransmitCnt = 0;
|
||||
|
||||
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> _alarmLogParser;
|
||||
std::unique_ptr<StatisticsParser> _statisticsParser;
|
||||
};
|
||||
167
lib/Hoymiles/src/parser/StatisticsParser.cpp
Normal file
167
lib/Hoymiles/src/parser/StatisticsParser.cpp
Normal file
@ -0,0 +1,167 @@
|
||||
#include "StatisticsParser.h"
|
||||
|
||||
void StatisticsParser::setByteAssignment(const byteAssign_t* byteAssignment, const uint8_t count)
|
||||
{
|
||||
_byteAssignment = byteAssignment;
|
||||
_byteAssignmentCount = count;
|
||||
}
|
||||
|
||||
void StatisticsParser::clearBuffer()
|
||||
{
|
||||
memset(_payloadStatistic, 0, STATISTIC_PACKET_SIZE);
|
||||
_statisticLength = 0;
|
||||
}
|
||||
|
||||
void StatisticsParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t len)
|
||||
{
|
||||
memcpy(&_payloadStatistic[offset], payload, len);
|
||||
_statisticLength += len;
|
||||
}
|
||||
|
||||
uint8_t StatisticsParser::getAssignIdxByChannelField(uint8_t channel, uint8_t fieldId)
|
||||
{
|
||||
const byteAssign_t* b = _byteAssignment;
|
||||
|
||||
uint8_t pos;
|
||||
for (pos = 0; pos < _byteAssignmentCount; pos++) {
|
||||
if (b[pos].ch == channel && b[pos].fieldId == fieldId) {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
float StatisticsParser::getChannelFieldValue(uint8_t channel, uint8_t fieldId)
|
||||
{
|
||||
uint8_t pos = getAssignIdxByChannelField(channel, fieldId);
|
||||
if (pos == 0xff) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const byteAssign_t* b = _byteAssignment;
|
||||
|
||||
uint8_t ptr = b[pos].start;
|
||||
uint8_t end = ptr + b[pos].num;
|
||||
uint16_t div = b[pos].div;
|
||||
|
||||
if (CMD_CALC != div) {
|
||||
// Value is a static value
|
||||
uint32_t val = 0;
|
||||
do {
|
||||
val <<= 8;
|
||||
val |= _payloadStatistic[ptr];
|
||||
} while (++ptr != end);
|
||||
|
||||
return (float)(val) / (float)(div);
|
||||
} else {
|
||||
// Value has to be calculated
|
||||
return calcFunctions[b[pos].start].func(this, b[pos].num);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool StatisticsParser::hasChannelFieldValue(uint8_t channel, uint8_t fieldId)
|
||||
{
|
||||
uint8_t pos = getAssignIdxByChannelField(channel, fieldId);
|
||||
return pos != 0xff;
|
||||
}
|
||||
|
||||
const char* StatisticsParser::getChannelFieldUnit(uint8_t channel, uint8_t fieldId)
|
||||
{
|
||||
uint8_t pos = getAssignIdxByChannelField(channel, fieldId);
|
||||
const byteAssign_t* b = _byteAssignment;
|
||||
|
||||
return units[b[pos].unitId];
|
||||
}
|
||||
|
||||
const char* StatisticsParser::getChannelFieldName(uint8_t channel, uint8_t fieldId)
|
||||
{
|
||||
uint8_t pos = getAssignIdxByChannelField(channel, fieldId);
|
||||
const byteAssign_t* b = _byteAssignment;
|
||||
|
||||
return fields[b[pos].fieldId];
|
||||
}
|
||||
|
||||
uint8_t StatisticsParser::getChannelCount()
|
||||
{
|
||||
const byteAssign_t* b = _byteAssignment;
|
||||
uint8_t cnt = 0;
|
||||
for (uint8_t pos = 0; pos < _byteAssignmentCount; pos++) {
|
||||
if (b[pos].ch > cnt) {
|
||||
cnt = b[pos].ch;
|
||||
}
|
||||
}
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
uint16_t StatisticsParser::getChannelMaxPower(uint8_t channel)
|
||||
{
|
||||
return _chanMaxPower[channel];
|
||||
}
|
||||
|
||||
void StatisticsParser::setChannelMaxPower(uint8_t channel, uint16_t power)
|
||||
{
|
||||
if (channel < CH4) {
|
||||
_chanMaxPower[channel] = power;
|
||||
}
|
||||
}
|
||||
|
||||
static float calcYieldTotalCh0(StatisticsParser* iv, uint8_t arg0)
|
||||
{
|
||||
float yield = 0;
|
||||
for (uint8_t i = 1; i <= iv->getChannelCount(); i++) {
|
||||
yield += iv->getChannelFieldValue(i, FLD_YT);
|
||||
}
|
||||
return yield;
|
||||
}
|
||||
|
||||
static float calcYieldDayCh0(StatisticsParser* iv, uint8_t arg0)
|
||||
{
|
||||
float yield = 0;
|
||||
for (uint8_t i = 1; i <= iv->getChannelCount(); i++) {
|
||||
yield += iv->getChannelFieldValue(i, FLD_YD);
|
||||
}
|
||||
return yield;
|
||||
}
|
||||
|
||||
// arg0 = channel of source
|
||||
static float calcUdcCh(StatisticsParser* iv, uint8_t arg0)
|
||||
{
|
||||
return iv->getChannelFieldValue(arg0, FLD_UDC);
|
||||
}
|
||||
|
||||
static float calcPowerDcCh0(StatisticsParser* iv, uint8_t arg0)
|
||||
{
|
||||
float dcPower = 0;
|
||||
for (uint8_t i = 1; i <= iv->getChannelCount(); i++) {
|
||||
dcPower += iv->getChannelFieldValue(i, FLD_PDC);
|
||||
}
|
||||
return dcPower;
|
||||
}
|
||||
|
||||
// arg0 = channel
|
||||
static float calcEffiencyCh0(StatisticsParser* iv, uint8_t arg0)
|
||||
{
|
||||
float acPower = iv->getChannelFieldValue(CH0, FLD_PAC);
|
||||
float dcPower = 0;
|
||||
for (uint8_t i = 1; i <= iv->getChannelCount(); i++) {
|
||||
dcPower += iv->getChannelFieldValue(i, FLD_PDC);
|
||||
}
|
||||
if (dcPower > 0) {
|
||||
return acPower / dcPower * 100.0f;
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// arg0 = channel
|
||||
static float calcIrradiation(StatisticsParser* iv, uint8_t arg0)
|
||||
{
|
||||
if (NULL != iv) {
|
||||
if (iv->getChannelMaxPower(arg0 - 1) > 0)
|
||||
return iv->getChannelFieldValue(arg0, FLD_PDC) / iv->getChannelMaxPower(arg0 - 1) * 100.0f;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
120
lib/Hoymiles/src/parser/StatisticsParser.h
Normal file
120
lib/Hoymiles/src/parser/StatisticsParser.h
Normal file
@ -0,0 +1,120 @@
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include <cstdint>
|
||||
|
||||
#define STATISTIC_PACKET_SIZE (4 * 16)
|
||||
|
||||
// units
|
||||
enum {
|
||||
UNIT_V = 0,
|
||||
UNIT_A,
|
||||
UNIT_W,
|
||||
UNIT_WH,
|
||||
UNIT_KWH,
|
||||
UNIT_HZ,
|
||||
UNIT_C,
|
||||
UNIT_PCT,
|
||||
UNIT_CNT
|
||||
};
|
||||
const char* const units[] = { "V", "A", "W", "Wh", "kWh", "Hz", "°C", "%", "" };
|
||||
|
||||
// field types
|
||||
enum {
|
||||
FLD_UDC = 0,
|
||||
FLD_IDC,
|
||||
FLD_PDC,
|
||||
FLD_YD,
|
||||
FLD_YT,
|
||||
FLD_UAC,
|
||||
FLD_IAC,
|
||||
FLD_PAC,
|
||||
FLD_F,
|
||||
FLD_T,
|
||||
FLD_PCT,
|
||||
FLD_EFF,
|
||||
FLD_IRR,
|
||||
FLD_EVT_LOG
|
||||
};
|
||||
const char* const fields[] = { "Voltage", "Current", "Power", "YieldDay", "YieldTotal",
|
||||
"Voltage", "Current", "Power", "Frequency", "Temperature", "PowerFactor", "Effiency", "Irradiation", "EventLogCount" };
|
||||
|
||||
// indices to calculation functions, defined in hmInverter.h
|
||||
enum {
|
||||
CALC_YT_CH0 = 0,
|
||||
CALC_YD_CH0,
|
||||
CALC_UDC_CH,
|
||||
CALC_PDC_CH0,
|
||||
CALC_EFF_CH0,
|
||||
CALC_IRR_CH
|
||||
};
|
||||
enum { CMD_CALC = 0xffff };
|
||||
|
||||
// CH0 is default channel (freq, ac, temp)
|
||||
enum {
|
||||
CH0 = 0,
|
||||
CH1,
|
||||
CH2,
|
||||
CH3,
|
||||
CH4
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t fieldId; // field id
|
||||
uint8_t unitId; // uint id
|
||||
uint8_t ch; // channel 0 - 4
|
||||
uint8_t start; // pos of first byte in buffer
|
||||
uint8_t num; // number of bytes in buffer
|
||||
uint16_t div; // divisor / calc command
|
||||
} byteAssign_t;
|
||||
|
||||
// prototypes
|
||||
class StatisticsParser;
|
||||
static float calcYieldTotalCh0(StatisticsParser* iv, uint8_t arg0);
|
||||
static float calcYieldDayCh0(StatisticsParser* iv, uint8_t arg0);
|
||||
static float calcUdcCh(StatisticsParser* iv, uint8_t arg0);
|
||||
static float calcPowerDcCh0(StatisticsParser* iv, uint8_t arg0);
|
||||
static float calcEffiencyCh0(StatisticsParser* iv, uint8_t arg0);
|
||||
static float calcIrradiation(StatisticsParser* iv, uint8_t arg0);
|
||||
|
||||
using func_t = float(StatisticsParser*, uint8_t);
|
||||
|
||||
struct calcFunc_t {
|
||||
uint8_t funcId; // unique id
|
||||
func_t* func; // function pointer
|
||||
};
|
||||
|
||||
const calcFunc_t calcFunctions[] = {
|
||||
{ CALC_YT_CH0, &calcYieldTotalCh0 },
|
||||
{ CALC_YD_CH0, &calcYieldDayCh0 },
|
||||
{ CALC_UDC_CH, &calcUdcCh },
|
||||
{ CALC_PDC_CH0, &calcPowerDcCh0 },
|
||||
{ CALC_EFF_CH0, &calcEffiencyCh0 },
|
||||
{ CALC_IRR_CH, &calcIrradiation }
|
||||
};
|
||||
|
||||
class StatisticsParser {
|
||||
public:
|
||||
void clearBuffer();
|
||||
void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len);
|
||||
|
||||
void setByteAssignment(const byteAssign_t* byteAssignment, const uint8_t count);
|
||||
|
||||
uint8_t getAssignIdxByChannelField(uint8_t channel, uint8_t fieldId);
|
||||
float getChannelFieldValue(uint8_t channel, uint8_t fieldId);
|
||||
bool hasChannelFieldValue(uint8_t channel, uint8_t fieldId);
|
||||
const char* getChannelFieldUnit(uint8_t channel, uint8_t fieldId);
|
||||
const char* getChannelFieldName(uint8_t channel, uint8_t fieldId);
|
||||
|
||||
uint8_t getChannelCount();
|
||||
|
||||
uint16_t getChannelMaxPower(uint8_t channel);
|
||||
void setChannelMaxPower(uint8_t channel, uint16_t power);
|
||||
|
||||
private:
|
||||
uint8_t _payloadStatistic[STATISTIC_PACKET_SIZE];
|
||||
uint8_t _statisticLength;
|
||||
uint16_t _chanMaxPower[CH4];
|
||||
|
||||
const byteAssign_t* _byteAssignment;
|
||||
uint8_t _byteAssignmentCount;
|
||||
};
|
||||
@ -36,7 +36,7 @@ void MqttPublishingClass::loop()
|
||||
_lastPublishStats[i] = lastUpdate;
|
||||
|
||||
// Loop all channels
|
||||
for (uint8_t c = 0; c <= inv->getChannelCount(); c++) {
|
||||
for (uint8_t c = 0; c <= inv->Statistics()->getChannelCount(); c++) {
|
||||
publishField(subtopic, inv, c, FLD_UDC);
|
||||
publishField(subtopic, inv, c, FLD_IDC);
|
||||
if (c == 0) {
|
||||
@ -66,14 +66,14 @@ void MqttPublishingClass::loop()
|
||||
|
||||
void MqttPublishingClass::publishField(String subtopic, std::shared_ptr<InverterAbstract> inv, uint8_t channel, uint8_t fieldId, String topic)
|
||||
{
|
||||
if (inv->hasChannelFieldValue(channel, fieldId)) {
|
||||
if (inv->Statistics()->hasChannelFieldValue(channel, fieldId)) {
|
||||
String chanName;
|
||||
if (topic == "") {
|
||||
chanName = inv->getChannelFieldName(channel, fieldId);
|
||||
chanName = inv->Statistics()->getChannelFieldName(channel, fieldId);
|
||||
} else {
|
||||
chanName = topic;
|
||||
}
|
||||
chanName.toLowerCase();
|
||||
MqttSettings.publish(subtopic + "/" + String(channel) + "/" + chanName, String(inv->getChannelFieldValue(channel, fieldId)));
|
||||
MqttSettings.publish(subtopic + "/" + String(channel) + "/" + chanName, String(inv->Statistics()->getChannelFieldValue(channel, fieldId)));
|
||||
}
|
||||
}
|
||||
@ -137,7 +137,7 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
|
||||
auto inv = Hoymiles.addInverter(inverter->Name, inverter->Serial);
|
||||
|
||||
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
||||
inv->setChannelMaxPower(c, inverter->MaxChannelPower[c]);
|
||||
inv->Statistics()->setChannelMaxPower(c, inverter->MaxChannelPower[c]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -244,7 +244,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
||||
}
|
||||
|
||||
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
||||
inv->setChannelMaxPower(c, inverter.MaxChannelPower[c]);
|
||||
inv->Statistics()->setChannelMaxPower(c, inverter.MaxChannelPower[c]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -47,7 +47,7 @@ void WebApiWsLiveClass::loop()
|
||||
root[i][F("age_critical")] = ((millis() - inv->getLastStatsUpdate()) / 1000) > Configuration.get().Dtu_PollInterval * 5;
|
||||
|
||||
// Loop all channels
|
||||
for (uint8_t c = 0; c <= inv->getChannelCount(); c++) {
|
||||
for (uint8_t c = 0; c <= inv->Statistics()->getChannelCount(); c++) {
|
||||
addField(root, i, inv, c, FLD_PAC);
|
||||
addField(root, i, inv, c, FLD_UAC);
|
||||
addField(root, i, inv, c, FLD_IAC);
|
||||
@ -67,7 +67,7 @@ void WebApiWsLiveClass::loop()
|
||||
addField(root, i, inv, c, FLD_IRR);
|
||||
}
|
||||
|
||||
if (inv->hasChannelFieldValue(CH0, FLD_EVT_LOG)) {
|
||||
if (inv->Statistics()->hasChannelFieldValue(CH0, FLD_EVT_LOG)) {
|
||||
root[i][F("events")] = inv->EventLog()->getEntryCount();
|
||||
} else {
|
||||
root[i][F("events")] = -1;
|
||||
@ -91,14 +91,14 @@ void WebApiWsLiveClass::loop()
|
||||
|
||||
void WebApiWsLiveClass::addField(JsonDocument& root, uint8_t idx, std::shared_ptr<InverterAbstract> inv, uint8_t channel, uint8_t fieldId, String topic)
|
||||
{
|
||||
if (inv->hasChannelFieldValue(channel, fieldId)) {
|
||||
if (inv->Statistics()->hasChannelFieldValue(channel, fieldId)) {
|
||||
String chanName;
|
||||
if (topic == "") {
|
||||
chanName = inv->getChannelFieldName(channel, fieldId);
|
||||
chanName = inv->Statistics()->getChannelFieldName(channel, fieldId);
|
||||
} else {
|
||||
chanName = topic;
|
||||
}
|
||||
root[idx][String(channel)][chanName]["v"] = inv->getChannelFieldValue(channel, fieldId);
|
||||
root[idx][String(channel)][chanName]["u"] = inv->getChannelFieldUnit(channel, fieldId);
|
||||
root[idx][String(channel)][chanName]["v"] = inv->Statistics()->getChannelFieldValue(channel, fieldId);
|
||||
root[idx][String(channel)][chanName]["u"] = inv->Statistics()->getChannelFieldUnit(channel, fieldId);
|
||||
}
|
||||
}
|
||||
@ -85,7 +85,7 @@ void setup()
|
||||
config.Inverter[i].Serial);
|
||||
|
||||
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
||||
inv->setChannelMaxPower(c, config.Inverter[i].MaxChannelPower[c]);
|
||||
inv->Statistics()->setChannelMaxPower(c, config.Inverter[i].MaxChannelPower[c]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user