From cd99ab8e4231cbe1693aeafb4f64c28ef76827b2 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Sat, 18 Feb 2023 19:44:16 +0100 Subject: [PATCH] Added settings to pause polling/sending commands in general and at night --- include/Configuration.h | 4 ++ include/InverterSettings.h | 15 +++++ src/Configuration.cpp | 9 +++ src/InverterSettings.cpp | 82 ++++++++++++++++++++++++++ src/WebApi_inverter.cpp | 11 ++++ src/main.cpp | 45 +------------- webapp/src/locales/de.json | 5 ++ webapp/src/locales/en.json | 5 ++ webapp/src/locales/fr.json | 5 ++ webapp/src/views/InverterAdminView.vue | 21 +++++++ 10 files changed, 160 insertions(+), 42 deletions(-) create mode 100644 include/InverterSettings.h create mode 100644 src/InverterSettings.cpp diff --git a/include/Configuration.h b/include/Configuration.h index 449cf51..8d88bbd 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -40,6 +40,10 @@ struct CHANNEL_CONFIG_T { struct INVERTER_CONFIG_T { uint64_t Serial; 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]; }; diff --git a/include/InverterSettings.h b/include/InverterSettings.h new file mode 100644 index 0000000..188025b --- /dev/null +++ b/include/InverterSettings.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include + +class InverterSettingsClass { +public: + void init(); + void loop(); + +private: + uint32_t _lastUpdate = 0; +}; + +extern InverterSettingsClass InverterSettings; \ No newline at end of file diff --git a/src/Configuration.cpp b/src/Configuration.cpp index eda2dc4..43e75e8 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -96,6 +96,10 @@ bool ConfigurationClass::write() JsonObject inv = inverters.createNestedObject(); inv["serial"] = config.Inverter[i].Serial; 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"); 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; 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"]; for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { config.Inverter[i].channel[c].MaxChannelPower = channel[c]["max_power"] | 0; diff --git a/src/InverterSettings.cpp b/src/InverterSettings.cpp new file mode 100644 index 0000000..ebb354b --- /dev/null +++ b/src/InverterSettings.cpp @@ -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 + +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(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(); +} \ No newline at end of file diff --git a/src/WebApi_inverter.cpp b/src/WebApi_inverter.cpp index 77a9c88..f133167 100644 --- a/src/WebApi_inverter.cpp +++ b/src/WebApi_inverter.cpp @@ -51,6 +51,10 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request) ((uint32_t)((config.Inverter[i].Serial >> 32) & 0xFFFFFFFF)), ((uint32_t)(config.Inverter[i].Serial & 0xFFFFFFFF))); 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); uint8_t max_channels; @@ -270,6 +274,11 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request) inverter.channel[arrayCount].MaxChannelPower = channel[F("max_power")].as(); inverter.channel[arrayCount].YieldTotalOffset = channel[F("yield_total_offset")].as(); 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++; } @@ -297,6 +306,8 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request) } if (inv != nullptr) { + inv->setEnablePolling(inverter.Poll_Enable); + inv->setEnableCommands(inverter.Command_Enable); 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(c), FLD_YT, inverter.channel[c].YieldTotalOffset); diff --git a/src/main.cpp b/src/main.cpp index 6191e4b..3cdefa3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,6 +4,7 @@ */ #include "Configuration.h" #include "Display_Graphic.h" +#include "InverterSettings.h" #include "MessageOutput.h" #include "MqttHandleDtu.h" #include "MqttHandleHass.h" @@ -17,7 +18,6 @@ #include "WebApi.h" #include "defaults.h" #include -#include #include void setup() @@ -126,53 +126,14 @@ void setup() } MessageOutput.println(F("done")); - // 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(c), FLD_YT, config.Inverter[i].channel[c].YieldTotalOffset); - } - } - MessageOutput.println(F(" done")); - } - } - MessageOutput.println(F("done")); - } else { - MessageOutput.println(F("Invalid pin config")); - } + InverterSettings.init(); } void loop() { NetworkSettings.loop(); yield(); - Hoymiles.loop(); + InverterSettings.loop(); yield(); MqttHandleDtu.loop(); yield(); diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index b7bfa67..a30620d 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -408,6 +408,11 @@ "InverterSerial": "Wechselrichter Seriennummer:", "InverterName": "Wechselrichter Name:", "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}:", "StringNameHint": "Hier kann ein eigener Name für den entsprechenden Port des Wechselrichters angegeben werden.", "StringMaxPower": "Max. Leistung String {num}:", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index a11b384..cfb659c 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -408,6 +408,11 @@ "InverterSerial": "Inverter Serial:", "InverterName": "Inverter Name:", "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}:", "StringNameHint": "Here you can specify a custom name for the respective port of your inverter.", "StringMaxPower": "Max power string {num}:", diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index f026fe5..571a785 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -408,6 +408,11 @@ "InverterSerial": "Numéro de série de l'onduleur", "InverterName": "Nom de l'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}:", "StringNameHint": "Ici, vous pouvez spécifier un nom personnalisé pour le port respectif de votre onduleur.", "StringMaxPower": "Puissance maximale de la ligne {num}:", diff --git a/webapp/src/views/InverterAdminView.vue b/webapp/src/views/InverterAdminView.vue index b30f083..b457d91 100644 --- a/webapp/src/views/InverterAdminView.vue +++ b/webapp/src/views/InverterAdminView.vue @@ -74,6 +74,21 @@ + + + + + + +
@@ -168,6 +183,7 @@ import BasePage from '@/components/BasePage.vue'; import BootstrapAlert from "@/components/BootstrapAlert.vue"; import CardElement from '@/components/CardElement.vue'; +import InputElement from '@/components/InputElement.vue'; import { authHeader, handleResponse } from '@/utils/authentication'; import * as bootstrap from 'bootstrap'; import { @@ -188,6 +204,10 @@ declare interface Inverter { serial: number; name: string; type: string; + poll_enable: boolean; + poll_enable_night: boolean; + command_enable: boolean; + command_enable_night: boolean; channel: Array; } @@ -206,6 +226,7 @@ export default defineComponent({ BIconInfoCircle, BIconPencil, BIconTrash, + InputElement, }, data() { return {