Refactored Hoymiles Lib: Move statistics parser into separate class

This commit is contained in:
Thomas Basler 2022-07-12 18:27:56 +02:00
parent 96e66dde47
commit 5bb9acdbc6
9 changed files with 315 additions and 267 deletions

View File

@ -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);

View File

@ -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();
@ -152,151 +158,3 @@ 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;
}

View File

@ -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;
};

View 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;
}

View 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;
};

View File

@ -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)));
}
}

View File

@ -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]);
}
}

View File

@ -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);
}
}

View File

@ -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]);
}
}
}