Skip to content

Commit

Permalink
Feature: Added option to set daily yield to zero at midnight
Browse files Browse the repository at this point in the history
  • Loading branch information
tbnobody committed Sep 5, 2023
1 parent 23dd248 commit ec9af88
Show file tree
Hide file tree
Showing 16 changed files with 96 additions and 3 deletions.
1 change: 1 addition & 0 deletions include/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -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];
};

Expand Down
19 changes: 19 additions & 0 deletions lib/Hoymiles/src/Hoymiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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;
}
}
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions lib/Hoymiles/src/Utils.cpp
Original file line number Diff line number Diff line change
@@ -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 changes: 9 additions & 0 deletions lib/Hoymiles/src/Utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include <cstdint>

class Utils {
public:
static uint8_t getWeekDay();
};
10 changes: 10 additions & 0 deletions lib/Hoymiles/src/inverters/InverterAbstract.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions lib/Hoymiles/src/inverters/InverterAbstract.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ class InverterAbstract {
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);
Expand Down Expand Up @@ -95,6 +98,7 @@ class InverterAbstract {
uint8_t _reachableThreshold = 3;

bool _zeroValuesIfUnreachable = false;
bool _zeroYieldDayOnMidnight = false;

std::unique_ptr<AlarmLogParser> _alarmLogParser;
std::unique_ptr<DevInfoParser> _devInfoParser;
Expand Down
18 changes: 16 additions & 2 deletions lib/Hoymiles/src/parser/StatisticsParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ const FieldId_t runtimeFields[] = {
FLD_IAC_3,
};

const FieldId_t dailyProductionFields[] = {
FLD_YD,
};

StatisticsParser::StatisticsParser()
: Parser()
{
Expand Down Expand Up @@ -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);
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions lib/Hoymiles/src/parser/StatisticsParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,11 @@ class StatisticsParser : public Parser {
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];
Expand Down
2 changes: 2 additions & 0 deletions src/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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++) {
Expand Down Expand Up @@ -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++) {
Expand Down
1 change: 1 addition & 0 deletions src/InverterSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/MqttHandleInverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/WebApi_inverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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++;
}
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions webapp/src/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -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?",
Expand Down
2 changes: 2 additions & 0 deletions webapp/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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}?",
Expand Down
2 changes: 2 additions & 0 deletions webapp/src/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -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}\" ?",
Expand Down
6 changes: 6 additions & 0 deletions webapp/src/views/InverterAdminView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -263,6 +268,7 @@ declare interface Inverter {
command_enable_night: boolean;
reachable_threshold: number;
zero_runtime: boolean;
zero_day: boolean;
channel: Array<Channel>;
}
Expand Down

0 comments on commit ec9af88

Please sign in to comment.