Added settings to pause polling/sending commands in general and at night

This commit is contained in:
Thomas Basler 2023-02-18 19:44:16 +01:00
parent b319c78dc1
commit cd99ab8e42
10 changed files with 160 additions and 42 deletions

View File

@ -40,6 +40,10 @@ struct CHANNEL_CONFIG_T {
struct INVERTER_CONFIG_T { struct INVERTER_CONFIG_T {
uint64_t Serial; uint64_t Serial;
char Name[INV_MAX_NAME_STRLEN + 1]; char Name[INV_MAX_NAME_STRLEN + 1];
bool Poll_Enable;
bool Poll_Enable_Night;
bool Command_Enable;
bool Command_Enable_Night;
CHANNEL_CONFIG_T channel[INV_MAX_CHAN_COUNT]; CHANNEL_CONFIG_T channel[INV_MAX_CHAN_COUNT];
}; };

View File

@ -0,0 +1,15 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstdint>
class InverterSettingsClass {
public:
void init();
void loop();
private:
uint32_t _lastUpdate = 0;
};
extern InverterSettingsClass InverterSettings;

View File

@ -96,6 +96,10 @@ bool ConfigurationClass::write()
JsonObject inv = inverters.createNestedObject(); JsonObject inv = inverters.createNestedObject();
inv["serial"] = config.Inverter[i].Serial; inv["serial"] = config.Inverter[i].Serial;
inv["name"] = config.Inverter[i].Name; inv["name"] = config.Inverter[i].Name;
inv["poll_enable"] = config.Inverter[i].Poll_Enable;
inv["poll_enable_night"] = config.Inverter[i].Poll_Enable_Night;
inv["command_enable"] = config.Inverter[i].Command_Enable;
inv["command_enable_night"] = config.Inverter[i].Command_Enable_Night;
JsonArray channel = inv.createNestedArray("channel"); JsonArray channel = inv.createNestedArray("channel");
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
@ -230,6 +234,11 @@ bool ConfigurationClass::read()
config.Inverter[i].Serial = inv["serial"] | 0ULL; config.Inverter[i].Serial = inv["serial"] | 0ULL;
strlcpy(config.Inverter[i].Name, inv["name"] | "", sizeof(config.Inverter[i].Name)); strlcpy(config.Inverter[i].Name, inv["name"] | "", sizeof(config.Inverter[i].Name));
config.Inverter[i].Poll_Enable = inv["poll_enable"] | true;
config.Inverter[i].Poll_Enable_Night = inv["poll_enable_night"] | true;
config.Inverter[i].Command_Enable = inv["command_enable"] | true;
config.Inverter[i].Command_Enable_Night = inv["command_enable_night"] | true;
JsonArray channel = inv["channel"]; JsonArray channel = inv["channel"];
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
config.Inverter[i].channel[c].MaxChannelPower = channel[c]["max_power"] | 0; config.Inverter[i].channel[c].MaxChannelPower = channel[c]["max_power"] | 0;

82
src/InverterSettings.cpp Normal file
View File

@ -0,0 +1,82 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023 Thomas Basler and others
*/
#include "InverterSettings.h"
#include "Configuration.h"
#include "MessageOutput.h"
#include "PinMapping.h"
#include "SunPosition.h"
#include <Hoymiles.h>
InverterSettingsClass InverterSettings;
void InverterSettingsClass::init()
{
const CONFIG_T& config = Configuration.get();
const PinMapping_t& pin = PinMapping.get();
// Initialize inverter communication
MessageOutput.print(F("Initialize Hoymiles interface... "));
if (PinMapping.isValidNrf24Config()) {
SPIClass* spiClass = new SPIClass(HSPI);
spiClass->begin(pin.nrf24_clk, pin.nrf24_miso, pin.nrf24_mosi, pin.nrf24_cs);
Hoymiles.setMessageOutput(&MessageOutput);
Hoymiles.init(spiClass, pin.nrf24_en, pin.nrf24_irq);
MessageOutput.println(F(" Setting radio PA level... "));
Hoymiles.getRadio()->setPALevel((rf24_pa_dbm_e)config.Dtu_PaLevel);
MessageOutput.println(F(" Setting DTU serial... "));
Hoymiles.getRadio()->setDtuSerial(config.Dtu_Serial);
MessageOutput.println(F(" Setting poll interval... "));
Hoymiles.setPollInterval(config.Dtu_PollInterval);
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
if (config.Inverter[i].Serial > 0) {
MessageOutput.print(F(" Adding inverter: "));
MessageOutput.print(config.Inverter[i].Serial, HEX);
MessageOutput.print(F(" - "));
MessageOutput.print(config.Inverter[i].Name);
auto inv = Hoymiles.addInverter(
config.Inverter[i].Name,
config.Inverter[i].Serial);
if (inv != nullptr) {
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);
}
}
MessageOutput.println(F(" done"));
}
}
MessageOutput.println(F("done"));
} else {
MessageOutput.println(F("Invalid pin config"));
}
}
void InverterSettingsClass::loop()
{
if (millis() - _lastUpdate > SUNPOS_UPDATE_INTERVAL) {
const CONFIG_T& config = Configuration.get();
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
auto const& inv_cfg = config.Inverter[i];
if (inv_cfg.Serial == 0) {
continue;
}
auto inv = Hoymiles.getInverterBySerial(inv_cfg.Serial);
if (inv == nullptr) {
continue;
}
inv->setEnablePolling(inv_cfg.Poll_Enable && (SunPosition.isDayPeriod() || inv_cfg.Poll_Enable_Night));
inv->setEnableCommands(inv_cfg.Command_Enable && (SunPosition.isDayPeriod() || inv_cfg.Command_Enable_Night));
}
}
Hoymiles.loop();
}

View File

@ -51,6 +51,10 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request)
((uint32_t)((config.Inverter[i].Serial >> 32) & 0xFFFFFFFF)), ((uint32_t)((config.Inverter[i].Serial >> 32) & 0xFFFFFFFF)),
((uint32_t)(config.Inverter[i].Serial & 0xFFFFFFFF))); ((uint32_t)(config.Inverter[i].Serial & 0xFFFFFFFF)));
obj[F("serial")] = buffer; obj[F("serial")] = buffer;
obj[F("poll_enable")] = config.Inverter[i].Poll_Enable;
obj[F("poll_enable_night")] = config.Inverter[i].Poll_Enable_Night;
obj[F("command_enable")] = config.Inverter[i].Command_Enable;
obj[F("command_enable_night")] = config.Inverter[i].Command_Enable_Night;
auto inv = Hoymiles.getInverterBySerial(config.Inverter[i].Serial); auto inv = Hoymiles.getInverterBySerial(config.Inverter[i].Serial);
uint8_t max_channels; uint8_t max_channels;
@ -270,6 +274,11 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
inverter.channel[arrayCount].MaxChannelPower = channel[F("max_power")].as<uint16_t>(); inverter.channel[arrayCount].MaxChannelPower = channel[F("max_power")].as<uint16_t>();
inverter.channel[arrayCount].YieldTotalOffset = channel[F("yield_total_offset")].as<float>(); inverter.channel[arrayCount].YieldTotalOffset = channel[F("yield_total_offset")].as<float>();
strncpy(inverter.channel[arrayCount].Name, channel[F("name")] | "", sizeof(inverter.channel[arrayCount].Name)); strncpy(inverter.channel[arrayCount].Name, channel[F("name")] | "", sizeof(inverter.channel[arrayCount].Name));
inverter.Poll_Enable = root[F("poll_enable")] | true;
inverter.Poll_Enable_Night = root[F("poll_enable_night")] | true;
inverter.Command_Enable = root[F("command_enable")] | true;
inverter.Command_Enable_Night = root[F("command_enable_night")] | true;
arrayCount++; arrayCount++;
} }
@ -297,6 +306,8 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
} }
if (inv != nullptr) { if (inv != nullptr) {
inv->setEnablePolling(inverter.Poll_Enable);
inv->setEnableCommands(inverter.Command_Enable);
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
inv->Statistics()->setStringMaxPower(c, inverter.channel[c].MaxChannelPower); inv->Statistics()->setStringMaxPower(c, inverter.channel[c].MaxChannelPower);
inv->Statistics()->setChannelFieldOffset(TYPE_DC, static_cast<ChannelNum_t>(c), FLD_YT, inverter.channel[c].YieldTotalOffset); inv->Statistics()->setChannelFieldOffset(TYPE_DC, static_cast<ChannelNum_t>(c), FLD_YT, inverter.channel[c].YieldTotalOffset);

View File

@ -4,6 +4,7 @@
*/ */
#include "Configuration.h" #include "Configuration.h"
#include "Display_Graphic.h" #include "Display_Graphic.h"
#include "InverterSettings.h"
#include "MessageOutput.h" #include "MessageOutput.h"
#include "MqttHandleDtu.h" #include "MqttHandleDtu.h"
#include "MqttHandleHass.h" #include "MqttHandleHass.h"
@ -17,7 +18,6 @@
#include "WebApi.h" #include "WebApi.h"
#include "defaults.h" #include "defaults.h"
#include <Arduino.h> #include <Arduino.h>
#include <Hoymiles.h>
#include <LittleFS.h> #include <LittleFS.h>
void setup() void setup()
@ -126,53 +126,14 @@ void setup()
} }
MessageOutput.println(F("done")); MessageOutput.println(F("done"));
// Initialize inverter communication InverterSettings.init();
MessageOutput.print(F("Initialize Hoymiles interface... "));
if (PinMapping.isValidNrf24Config()) {
SPIClass* spiClass = new SPIClass(HSPI);
spiClass->begin(pin.nrf24_clk, pin.nrf24_miso, pin.nrf24_mosi, pin.nrf24_cs);
Hoymiles.setMessageOutput(&MessageOutput);
Hoymiles.init(spiClass, pin.nrf24_en, pin.nrf24_irq);
MessageOutput.println(F(" Setting radio PA level... "));
Hoymiles.getRadio()->setPALevel((rf24_pa_dbm_e)config.Dtu_PaLevel);
MessageOutput.println(F(" Setting DTU serial... "));
Hoymiles.getRadio()->setDtuSerial(config.Dtu_Serial);
MessageOutput.println(F(" Setting poll interval... "));
Hoymiles.setPollInterval(config.Dtu_PollInterval);
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
if (config.Inverter[i].Serial > 0) {
MessageOutput.print(F(" Adding inverter: "));
MessageOutput.print(config.Inverter[i].Serial, HEX);
MessageOutput.print(F(" - "));
MessageOutput.print(config.Inverter[i].Name);
auto inv = Hoymiles.addInverter(
config.Inverter[i].Name,
config.Inverter[i].Serial);
if (inv != nullptr) {
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);
}
}
MessageOutput.println(F(" done"));
}
}
MessageOutput.println(F("done"));
} else {
MessageOutput.println(F("Invalid pin config"));
}
} }
void loop() void loop()
{ {
NetworkSettings.loop(); NetworkSettings.loop();
yield(); yield();
Hoymiles.loop(); InverterSettings.loop();
yield(); yield();
MqttHandleDtu.loop(); MqttHandleDtu.loop();
yield(); yield();

View File

@ -408,6 +408,11 @@
"InverterSerial": "Wechselrichter Seriennummer:", "InverterSerial": "Wechselrichter Seriennummer:",
"InverterName": "Wechselrichter Name:", "InverterName": "Wechselrichter Name:",
"InverterNameHint": "Hier kann ein eigener Namen für den Wechselrichter angeben werden.", "InverterNameHint": "Hier kann ein eigener Namen für den Wechselrichter angeben werden.",
"InverterStatus": "Empfangen / senden",
"PollEnable": "Daten abrufen",
"PollEnableNight": "Daten auch nachts abrufen",
"CommandEnable": "Befehle senden",
"CommandEnableNight": "Befehle auch nachts senden",
"StringName": "Name String {num}:", "StringName": "Name String {num}:",
"StringNameHint": "Hier kann ein eigener Name für den entsprechenden Port des Wechselrichters angegeben werden.", "StringNameHint": "Hier kann ein eigener Name für den entsprechenden Port des Wechselrichters angegeben werden.",
"StringMaxPower": "Max. Leistung String {num}:", "StringMaxPower": "Max. Leistung String {num}:",

View File

@ -408,6 +408,11 @@
"InverterSerial": "Inverter Serial:", "InverterSerial": "Inverter Serial:",
"InverterName": "Inverter Name:", "InverterName": "Inverter Name:",
"InverterNameHint": "Here you can specify a custom name for your inverter.", "InverterNameHint": "Here you can specify a custom name for your inverter.",
"InverterStatus": "Receive / Send",
"PollEnable": "Poll inverter data",
"PollEnableNight": "Poll inverter data at night",
"CommandEnable": "Send commands",
"CommandEnableNight": "Send commands at night",
"StringName": "Name string {num}:", "StringName": "Name string {num}:",
"StringNameHint": "Here you can specify a custom name for the respective port of your inverter.", "StringNameHint": "Here you can specify a custom name for the respective port of your inverter.",
"StringMaxPower": "Max power string {num}:", "StringMaxPower": "Max power string {num}:",

View File

@ -408,6 +408,11 @@
"InverterSerial": "Numéro de série de l'onduleur", "InverterSerial": "Numéro de série de l'onduleur",
"InverterName": "Nom de l'onduleur :", "InverterName": "Nom de l'onduleur :",
"InverterNameHint": "Ici, vous pouvez spécifier un nom personnalisé pour votre onduleur.", "InverterNameHint": "Ici, vous pouvez spécifier un nom personnalisé pour votre onduleur.",
"InverterStatus": "Receive / Send",
"PollEnable": "Poll inverter data",
"PollEnableNight": "Poll inverter data at night",
"CommandEnable": "Send commands",
"CommandEnableNight": "Send commands at night",
"StringName": "Nom de la ligne {num}:", "StringName": "Nom de la ligne {num}:",
"StringNameHint": "Ici, vous pouvez spécifier un nom personnalisé pour le port respectif de votre onduleur.", "StringNameHint": "Ici, vous pouvez spécifier un nom personnalisé pour le port respectif de votre onduleur.",
"StringMaxPower": "Puissance maximale de la ligne {num}:", "StringMaxPower": "Puissance maximale de la ligne {num}:",

View File

@ -74,6 +74,21 @@
</label> </label>
<input v-model="selectedInverterData.name" type="text" id="inverter-name" <input v-model="selectedInverterData.name" type="text" id="inverter-name"
class="form-control" maxlength="31" /> class="form-control" maxlength="31" />
<CardElement :text="$t('inverteradmin.InverterStatus')" addSpace>
<InputElement :label="$t('inverteradmin.PollEnable')"
v-model="selectedInverterData.poll_enable"
type="checkbox" wide />
<InputElement :label="$t('inverteradmin.PollEnableNight')"
v-model="selectedInverterData.poll_enable_night"
type="checkbox" wide/>
<InputElement :label="$t('inverteradmin.CommandEnable')"
v-model="selectedInverterData.command_enable"
type="checkbox" wide/>
<InputElement :label="$t('inverteradmin.CommandEnableNight')"
v-model="selectedInverterData.command_enable_night"
type="checkbox" wide/>
</CardElement>
</div> </div>
<div v-for="(max, index) in selectedInverterData.channel" :key="`${index}`"> <div v-for="(max, index) in selectedInverterData.channel" :key="`${index}`">
@ -168,6 +183,7 @@
import BasePage from '@/components/BasePage.vue'; import BasePage from '@/components/BasePage.vue';
import BootstrapAlert from "@/components/BootstrapAlert.vue"; import BootstrapAlert from "@/components/BootstrapAlert.vue";
import CardElement from '@/components/CardElement.vue'; import CardElement from '@/components/CardElement.vue';
import InputElement from '@/components/InputElement.vue';
import { authHeader, handleResponse } from '@/utils/authentication'; import { authHeader, handleResponse } from '@/utils/authentication';
import * as bootstrap from 'bootstrap'; import * as bootstrap from 'bootstrap';
import { import {
@ -188,6 +204,10 @@ declare interface Inverter {
serial: number; serial: number;
name: string; name: string;
type: string; type: string;
poll_enable: boolean;
poll_enable_night: boolean;
command_enable: boolean;
command_enable_night: boolean;
channel: Array<Channel>; channel: Array<Channel>;
} }
@ -206,6 +226,7 @@ export default defineComponent({
BIconInfoCircle, BIconInfoCircle,
BIconPencil, BIconPencil,
BIconTrash, BIconTrash,
InputElement,
}, },
data() { data() {
return { return {