Feature: Added option to set daily yield to zero at midnight
This commit is contained in:
parent
23dd248073
commit
ec9af886d5
@ -47,6 +47,7 @@ struct INVERTER_CONFIG_T {
|
||||
bool Command_Enable_Night;
|
||||
uint8_t ReachableThreshold;
|
||||
bool ZeroRuntimeDataIfUnrechable;
|
||||
bool ZeroYieldDayOnMidnight;
|
||||
CHANNEL_CONFIG_T channel[INV_MAX_CHAN_COUNT];
|
||||
};
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "Hoymiles.h"
|
||||
#include "Utils.h"
|
||||
#include "inverters/HMS_1CH.h"
|
||||
#include "inverters/HMS_2CH.h"
|
||||
#include "inverters/HMS_4CH.h"
|
||||
@ -106,6 +107,24 @@ void HoymilesClass::loop()
|
||||
|
||||
_lastPoll = millis();
|
||||
}
|
||||
|
||||
// Perform housekeeping of all inverters on day change
|
||||
int8_t currentWeekDay = Utils::getWeekDay();
|
||||
static int8_t lastWeekDay = -1;
|
||||
if (lastWeekDay == -1) {
|
||||
lastWeekDay = currentWeekDay;
|
||||
} else {
|
||||
if (currentWeekDay != lastWeekDay) {
|
||||
|
||||
for (auto& inv : _inverters) {
|
||||
if (inv->getZeroYieldDayOnMidnight()) {
|
||||
inv->Statistics()->zeroDailyData();
|
||||
}
|
||||
}
|
||||
|
||||
lastWeekDay = currentWeekDay;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
lib/Hoymiles/src/Utils.cpp
Normal file
15
lib/Hoymiles/src/Utils.cpp
Normal file
@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2023 Thomas Basler and others
|
||||
*/
|
||||
#include "Utils.h"
|
||||
#include <time.h>
|
||||
|
||||
uint8_t Utils::getWeekDay()
|
||||
{
|
||||
time_t raw;
|
||||
struct tm info;
|
||||
time(&raw);
|
||||
localtime_r(&raw, &info);
|
||||
return info.tm_mday;
|
||||
}
|
||||
9
lib/Hoymiles/src/Utils.h
Normal file
9
lib/Hoymiles/src/Utils.h
Normal file
@ -0,0 +1,9 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class Utils {
|
||||
public:
|
||||
static uint8_t getWeekDay();
|
||||
};
|
||||
@ -116,6 +116,16 @@ bool InverterAbstract::getZeroValuesIfUnreachable()
|
||||
return _zeroValuesIfUnreachable;
|
||||
}
|
||||
|
||||
void InverterAbstract::setZeroYieldDayOnMidnight(bool enabled)
|
||||
{
|
||||
_zeroYieldDayOnMidnight = enabled;
|
||||
}
|
||||
|
||||
bool InverterAbstract::getZeroYieldDayOnMidnight()
|
||||
{
|
||||
return _zeroYieldDayOnMidnight;
|
||||
}
|
||||
|
||||
bool InverterAbstract::sendChangeChannelRequest()
|
||||
{
|
||||
return false;
|
||||
|
||||
@ -54,6 +54,9 @@ public:
|
||||
void setZeroValuesIfUnreachable(bool enabled);
|
||||
bool getZeroValuesIfUnreachable();
|
||||
|
||||
void setZeroYieldDayOnMidnight(bool enabled);
|
||||
bool getZeroYieldDayOnMidnight();
|
||||
|
||||
void clearRxFragmentBuffer();
|
||||
void addRxFragment(uint8_t fragment[], uint8_t len);
|
||||
uint8_t verifyAllFragments(CommandAbstract* cmd);
|
||||
@ -95,6 +98,7 @@ private:
|
||||
uint8_t _reachableThreshold = 3;
|
||||
|
||||
bool _zeroValuesIfUnreachable = false;
|
||||
bool _zeroYieldDayOnMidnight = false;
|
||||
|
||||
std::unique_ptr<AlarmLogParser> _alarmLogParser;
|
||||
std::unique_ptr<DevInfoParser> _devInfoParser;
|
||||
|
||||
@ -55,6 +55,10 @@ const FieldId_t runtimeFields[] = {
|
||||
FLD_IAC_3,
|
||||
};
|
||||
|
||||
const FieldId_t dailyProductionFields[] = {
|
||||
FLD_YD,
|
||||
};
|
||||
|
||||
StatisticsParser::StatisticsParser()
|
||||
: Parser()
|
||||
{
|
||||
@ -317,13 +321,23 @@ uint32_t StatisticsParser::getRxFailureCount()
|
||||
}
|
||||
|
||||
void StatisticsParser::zeroRuntimeData()
|
||||
{
|
||||
zeroFields(runtimeFields);
|
||||
}
|
||||
|
||||
void StatisticsParser::zeroDailyData()
|
||||
{
|
||||
zeroFields(dailyProductionFields);
|
||||
}
|
||||
|
||||
void StatisticsParser::zeroFields(const FieldId_t* fields)
|
||||
{
|
||||
// Loop all channels
|
||||
for (auto& t : getChannelTypes()) {
|
||||
for (auto& c : getChannelsByType(t)) {
|
||||
for (uint8_t i = 0; i < (sizeof(runtimeFields) / sizeof(runtimeFields[0])); i++) {
|
||||
if (hasChannelFieldValue(t, c, runtimeFields[i])) {
|
||||
setChannelFieldValue(t, c, runtimeFields[i], 0);
|
||||
if (hasChannelFieldValue(t, c, fields[i])) {
|
||||
setChannelFieldValue(t, c, fields[i], 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,8 +142,11 @@ public:
|
||||
uint32_t getRxFailureCount();
|
||||
|
||||
void zeroRuntimeData();
|
||||
void zeroDailyData();
|
||||
|
||||
private:
|
||||
void zeroFields(const FieldId_t* fields);
|
||||
|
||||
uint8_t _payloadStatistic[STATISTIC_PACKET_SIZE] = {};
|
||||
uint8_t _statisticLength = 0;
|
||||
uint16_t _stringMaxPower[CH_CNT];
|
||||
|
||||
@ -112,6 +112,7 @@ bool ConfigurationClass::write()
|
||||
inv["command_enable_night"] = config.Inverter[i].Command_Enable_Night;
|
||||
inv["reachable_threshold"] = config.Inverter[i].ReachableThreshold;
|
||||
inv["zero_runtime"] = config.Inverter[i].ZeroRuntimeDataIfUnrechable;
|
||||
inv["zero_day"] = config.Inverter[i].ZeroYieldDayOnMidnight;
|
||||
|
||||
JsonArray channel = inv.createNestedArray("channel");
|
||||
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
||||
@ -262,6 +263,7 @@ bool ConfigurationClass::read()
|
||||
config.Inverter[i].Command_Enable_Night = inv["command_enable_night"] | true;
|
||||
config.Inverter[i].ReachableThreshold = inv["reachable_threshold"] | REACHABLE_THRESHOLD;
|
||||
config.Inverter[i].ZeroRuntimeDataIfUnrechable = inv["zero_runtime"] | false;
|
||||
config.Inverter[i].ZeroYieldDayOnMidnight = inv["zero_day"] | false;
|
||||
|
||||
JsonArray channel = inv["channel"];
|
||||
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
||||
|
||||
@ -73,6 +73,7 @@ void InverterSettingsClass::init()
|
||||
if (inv != nullptr) {
|
||||
inv->setReachableThreshold(config.Inverter[i].ReachableThreshold);
|
||||
inv->setZeroValuesIfUnreachable(config.Inverter[i].ZeroRuntimeDataIfUnrechable);
|
||||
inv->setZeroYieldDayOnMidnight(config.Inverter[i].ZeroYieldDayOnMidnight);
|
||||
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
||||
inv->Statistics()->setStringMaxPower(c, config.Inverter[i].channel[c].MaxChannelPower);
|
||||
inv->Statistics()->setChannelFieldOffset(TYPE_DC, static_cast<ChannelNum_t>(c), FLD_YT, config.Inverter[i].channel[c].YieldTotalOffset);
|
||||
|
||||
@ -94,7 +94,7 @@ void MqttHandleInverterClass::loop()
|
||||
}
|
||||
|
||||
uint32_t lastUpdate = inv->Statistics()->getLastUpdate();
|
||||
if (lastUpdate > 0 && (lastUpdate != _lastPublishStats[i] || (inv->getZeroValuesIfUnreachable() && _statsTimeout.occured()))) {
|
||||
if (lastUpdate > 0 && (lastUpdate != _lastPublishStats[i] || ((inv->getZeroValuesIfUnreachable() || inv->getZeroYieldDayOnMidnight()) && _statsTimeout.occured()))) {
|
||||
_lastPublishStats[i] = lastUpdate;
|
||||
|
||||
// At first a change of the stats have to occour. Then the stats
|
||||
|
||||
@ -60,6 +60,7 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request)
|
||||
obj["command_enable_night"] = config.Inverter[i].Command_Enable_Night;
|
||||
obj["reachable_threshold"] = config.Inverter[i].ReachableThreshold;
|
||||
obj["zero_runtime"] = config.Inverter[i].ZeroRuntimeDataIfUnrechable;
|
||||
obj["zero_day"] = config.Inverter[i].ZeroYieldDayOnMidnight;
|
||||
|
||||
auto inv = Hoymiles.getInverterBySerial(config.Inverter[i].Serial);
|
||||
uint8_t max_channels;
|
||||
@ -286,6 +287,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
||||
inverter.Command_Enable_Night = root["command_enable_night"] | true;
|
||||
inverter.ReachableThreshold = root["reachable_threshold"] | REACHABLE_THRESHOLD;
|
||||
inverter.ZeroRuntimeDataIfUnrechable = root["zero_runtime"] | false;
|
||||
inverter.ZeroYieldDayOnMidnight = root["zero_day"] | false;
|
||||
|
||||
arrayCount++;
|
||||
}
|
||||
@ -318,6 +320,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
||||
inv->setEnableCommands(inverter.Command_Enable);
|
||||
inv->setReachableThreshold(inverter.ReachableThreshold);
|
||||
inv->setZeroValuesIfUnreachable(inverter.ZeroRuntimeDataIfUnrechable);
|
||||
inv->setZeroYieldDayOnMidnight(inverter.ZeroYieldDayOnMidnight);
|
||||
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
||||
inv->Statistics()->setStringMaxPower(c, inverter.channel[c].MaxChannelPower);
|
||||
inv->Statistics()->setChannelFieldOffset(TYPE_DC, static_cast<ChannelNum_t>(c), FLD_YT, inverter.channel[c].YieldTotalOffset);
|
||||
|
||||
@ -474,6 +474,8 @@
|
||||
"ReachableThresholdHint": "Legt fest, wie viele Anfragen fehlschlagen dürfen, bis der Wechselrichter als unerreichbar eingestuft wird.",
|
||||
"ZeroRuntime": "Nulle Laufzeit Daten",
|
||||
"ZeroRuntimeHint": "Nulle Laufzeit Daten (keine Ertragsdaten), wenn der Wechselrichter nicht erreichbar ist.",
|
||||
"ZeroDay": "Nulle Tagesertrag um Mitternacht",
|
||||
"ZeroDayHint": "Das funktioniert nur wenn der Wechselrichter nicht erreichbar ist. Wenn Daten aus dem Wechselrichter gelesen werden, werden deren Werte verwendet. (Ein Reset erfolgt nur beim Neustarten)",
|
||||
"Cancel": "@:maintenancereboot.Cancel",
|
||||
"Save": "@:dtuadmin.Save",
|
||||
"DeleteMsg": "Soll der Wechselrichter \"{name}\" mit der Seriennummer {serial} wirklich gelöscht werden?",
|
||||
|
||||
@ -474,6 +474,8 @@
|
||||
"ReachableThresholdHint": "Defines how many requests are allowed to fail until the inverter is treated is not reachable.",
|
||||
"ZeroRuntime": "Zero runtime data",
|
||||
"ZeroRuntimeHint": "Zero runtime data (no yield data) if inverter becomes unreachable.",
|
||||
"ZeroDay": "Zero daily yield at midnight",
|
||||
"ZeroDayHint": "This only works if the inverter is unreachable. If data is read from the inverter, it's values will be used. (Reset only occours on power cycle)",
|
||||
"Cancel": "@:maintenancereboot.Cancel",
|
||||
"Save": "@:dtuadmin.Save",
|
||||
"DeleteMsg": "Are you sure you want to delete the inverter \"{name}\" with serial number {serial}?",
|
||||
|
||||
@ -474,6 +474,8 @@
|
||||
"ReachableThresholdHint": "Defines how many requests are allowed to fail until the inverter is treated is not reachable.",
|
||||
"ZeroRuntime": "Zero runtime data",
|
||||
"ZeroRuntimeHint": "Zero runtime data (no yield data) if inverter becomes unreachable.",
|
||||
"ZeroDay": "Zero daily yield at midnight",
|
||||
"ZeroDayHint": "This only works if the inverter is unreachable. If data is read from the inverter, it's values will be used. (Reset only occours on power cycle)",
|
||||
"Cancel": "@:maintenancereboot.Cancel",
|
||||
"Save": "@:dtuadmin.Save",
|
||||
"DeleteMsg": "Êtes-vous sûr de vouloir supprimer l'onduleur \"{name}\" avec le numéro de série \"{serial}\" ?",
|
||||
|
||||
@ -187,6 +187,11 @@
|
||||
v-model="selectedInverterData.zero_runtime"
|
||||
type="checkbox"
|
||||
:tooltip="$t('inverteradmin.ZeroRuntimeHint')" wide/>
|
||||
|
||||
<InputElement :label="$t('inverteradmin.ZeroDay')"
|
||||
v-model="selectedInverterData.zero_day"
|
||||
type="checkbox"
|
||||
:tooltip="$t('inverteradmin.ZeroDayHint')" wide/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@ -263,6 +268,7 @@ declare interface Inverter {
|
||||
command_enable_night: boolean;
|
||||
reachable_threshold: number;
|
||||
zero_runtime: boolean;
|
||||
zero_day: boolean;
|
||||
channel: Array<Channel>;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user