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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasChannelFieldValue(CH0, FLD_EVT_LOG)) {
|
if (Statistics()->hasChannelFieldValue(CH0, FLD_EVT_LOG)) {
|
||||||
if ((uint8_t)getChannelFieldValue(CH0, FLD_EVT_LOG) == _lastAlarmLogCnt) {
|
if ((uint8_t)Statistics()->getChannelFieldValue(CH0, FLD_EVT_LOG) == _lastAlarmLogCnt) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_lastAlarmLogCnt = (uint8_t)getChannelFieldValue(CH0, FLD_EVT_LOG);
|
_lastAlarmLogCnt = (uint8_t)Statistics()->getChannelFieldValue(CH0, FLD_EVT_LOG);
|
||||||
|
|
||||||
time_t now;
|
time_t now;
|
||||||
time(&now);
|
time(&now);
|
||||||
|
|||||||
@ -6,7 +6,7 @@ InverterAbstract::InverterAbstract(uint64_t serial)
|
|||||||
{
|
{
|
||||||
_serial.u64 = serial;
|
_serial.u64 = serial;
|
||||||
_alarmLogParser.reset(new AlarmLogParser());
|
_alarmLogParser.reset(new AlarmLogParser());
|
||||||
memset(_payloadStats, 0, MAX_RF_FRAGMENT_COUNT * MAX_RF_PAYLOAD_SIZE);
|
_statisticsParser.reset(new StatisticsParser());
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t InverterAbstract::serial()
|
uint64_t InverterAbstract::serial()
|
||||||
@ -34,6 +34,11 @@ AlarmLogParser* InverterAbstract::EventLog()
|
|||||||
return _alarmLogParser.get();
|
return _alarmLogParser.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StatisticsParser* InverterAbstract::Statistics()
|
||||||
|
{
|
||||||
|
return _statisticsParser.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);
|
||||||
@ -112,10 +117,11 @@ uint8_t InverterAbstract::verifyAllFragments()
|
|||||||
|
|
||||||
if (getLastRequest() == RequestType::Stats) {
|
if (getLastRequest() == RequestType::Stats) {
|
||||||
// Move all fragments into target buffer
|
// Move all fragments into target buffer
|
||||||
memset(_payloadStats, 0, MAX_RF_FRAGMENT_COUNT * MAX_RF_PAYLOAD_SIZE);
|
|
||||||
uint8_t offs = 0;
|
uint8_t offs = 0;
|
||||||
|
_statisticsParser.get()->setByteAssignment(getByteAssignment(), getAssignmentCount());
|
||||||
|
_statisticsParser.get()->clearBuffer();
|
||||||
for (uint8_t i = 0; i < _rxFragmentMaxPacketId; i++) {
|
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);
|
offs += (_rxFragmentBuffer[i].len);
|
||||||
}
|
}
|
||||||
_lastStatsUpdate = millis();
|
_lastStatsUpdate = millis();
|
||||||
@ -152,151 +158,3 @@ RequestType InverterAbstract::getLastRequest()
|
|||||||
{
|
{
|
||||||
return _lastRequest;
|
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
|
#pragma once
|
||||||
|
|
||||||
#include "../parser/AlarmLogParser.h"
|
#include "../parser/AlarmLogParser.h"
|
||||||
|
#include "../parser/StatisticsParser.h"
|
||||||
#include "HoymilesRadio.h"
|
#include "HoymilesRadio.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
@ -8,60 +9,6 @@
|
|||||||
|
|
||||||
#define MAX_NAME_LENGTH 32
|
#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 {
|
enum {
|
||||||
FRAGMENT_ALL_MISSING = 255,
|
FRAGMENT_ALL_MISSING = 255,
|
||||||
FRAGMENT_RETRANSMIT_TIMEOUT = 254,
|
FRAGMENT_RETRANSMIT_TIMEOUT = 254,
|
||||||
@ -69,44 +16,9 @@ enum {
|
|||||||
FRAGMENT_OK = 0
|
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_RF_FRAGMENT_COUNT 5
|
||||||
#define MAX_RETRANSMIT_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 {
|
class InverterAbstract {
|
||||||
public:
|
public:
|
||||||
InverterAbstract(uint64_t serial);
|
InverterAbstract(uint64_t serial);
|
||||||
@ -116,20 +28,11 @@ public:
|
|||||||
virtual String typeName() = 0;
|
virtual String typeName() = 0;
|
||||||
virtual const byteAssign_t* getByteAssignment() = 0;
|
virtual const byteAssign_t* getByteAssignment() = 0;
|
||||||
virtual const uint8_t getAssignmentCount() = 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 clearRxFragmentBuffer();
|
||||||
void addRxFragment(uint8_t fragment[], uint8_t len);
|
void addRxFragment(uint8_t fragment[], uint8_t len);
|
||||||
uint8_t verifyAllFragments();
|
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 sendStatsRequest(HoymilesRadio* radio) = 0;
|
||||||
virtual bool sendAlarmLogRequest(HoymilesRadio* radio) = 0;
|
virtual bool sendAlarmLogRequest(HoymilesRadio* radio) = 0;
|
||||||
uint32_t getLastStatsUpdate();
|
uint32_t getLastStatsUpdate();
|
||||||
@ -137,6 +40,7 @@ public:
|
|||||||
void setLastRequest(RequestType request);
|
void setLastRequest(RequestType request);
|
||||||
|
|
||||||
AlarmLogParser* EventLog();
|
AlarmLogParser* EventLog();
|
||||||
|
StatisticsParser* Statistics();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
RequestType getLastRequest();
|
RequestType getLastRequest();
|
||||||
@ -149,12 +53,11 @@ private:
|
|||||||
uint8_t _rxFragmentLastPacketId = 0;
|
uint8_t _rxFragmentLastPacketId = 0;
|
||||||
uint8_t _rxFragmentRetransmitCnt = 0;
|
uint8_t _rxFragmentRetransmitCnt = 0;
|
||||||
|
|
||||||
uint8_t _payloadStats[MAX_RF_FRAGMENT_COUNT * MAX_RF_PAYLOAD_SIZE];
|
|
||||||
uint32_t _lastStatsUpdate = 0;
|
uint32_t _lastStatsUpdate = 0;
|
||||||
uint32_t _lastAlarmLogUpdate = 0;
|
uint32_t _lastAlarmLogUpdate = 0;
|
||||||
uint16_t _chanMaxPower[CH4];
|
|
||||||
|
|
||||||
RequestType _lastRequest = RequestType::None;
|
RequestType _lastRequest = RequestType::None;
|
||||||
|
|
||||||
std::unique_ptr<AlarmLogParser> _alarmLogParser;
|
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;
|
_lastPublishStats[i] = lastUpdate;
|
||||||
|
|
||||||
// Loop all channels
|
// 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_UDC);
|
||||||
publishField(subtopic, inv, c, FLD_IDC);
|
publishField(subtopic, inv, c, FLD_IDC);
|
||||||
if (c == 0) {
|
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)
|
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;
|
String chanName;
|
||||||
if (topic == "") {
|
if (topic == "") {
|
||||||
chanName = inv->getChannelFieldName(channel, fieldId);
|
chanName = inv->Statistics()->getChannelFieldName(channel, fieldId);
|
||||||
} else {
|
} else {
|
||||||
chanName = topic;
|
chanName = topic;
|
||||||
}
|
}
|
||||||
chanName.toLowerCase();
|
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);
|
auto inv = Hoymiles.addInverter(inverter->Name, inverter->Serial);
|
||||||
|
|
||||||
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
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++) {
|
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;
|
root[i][F("age_critical")] = ((millis() - inv->getLastStatsUpdate()) / 1000) > Configuration.get().Dtu_PollInterval * 5;
|
||||||
|
|
||||||
// Loop all channels
|
// 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_PAC);
|
||||||
addField(root, i, inv, c, FLD_UAC);
|
addField(root, i, inv, c, FLD_UAC);
|
||||||
addField(root, i, inv, c, FLD_IAC);
|
addField(root, i, inv, c, FLD_IAC);
|
||||||
@ -67,7 +67,7 @@ void WebApiWsLiveClass::loop()
|
|||||||
addField(root, i, inv, c, FLD_IRR);
|
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();
|
root[i][F("events")] = inv->EventLog()->getEntryCount();
|
||||||
} else {
|
} else {
|
||||||
root[i][F("events")] = -1;
|
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)
|
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;
|
String chanName;
|
||||||
if (topic == "") {
|
if (topic == "") {
|
||||||
chanName = inv->getChannelFieldName(channel, fieldId);
|
chanName = inv->Statistics()->getChannelFieldName(channel, fieldId);
|
||||||
} else {
|
} else {
|
||||||
chanName = topic;
|
chanName = topic;
|
||||||
}
|
}
|
||||||
root[idx][String(channel)][chanName]["v"] = inv->getChannelFieldValue(channel, fieldId);
|
root[idx][String(channel)][chanName]["v"] = inv->Statistics()->getChannelFieldValue(channel, fieldId);
|
||||||
root[idx][String(channel)][chanName]["u"] = inv->getChannelFieldUnit(channel, fieldId);
|
root[idx][String(channel)][chanName]["u"] = inv->Statistics()->getChannelFieldUnit(channel, fieldId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -85,7 +85,7 @@ void setup()
|
|||||||
config.Inverter[i].Serial);
|
config.Inverter[i].Serial);
|
||||||
|
|
||||||
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
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