From ec25277b58f2b3cbf9587003e1e572e7cc42b446 Mon Sep 17 00:00:00 2001 From: Reinhard Weber Date: Thu, 5 Oct 2023 22:16:27 +0200 Subject: [PATCH 01/18] configuration via config file --- src/config.ini | 70 +++++++++++++++++++++ src/solarflow-control.py | 129 +++++++++++++++++++++++++-------------- 2 files changed, 154 insertions(+), 45 deletions(-) create mode 100644 src/config.ini diff --git a/src/config.ini b/src/config.ini new file mode 100644 index 0000000..e2ca53f --- /dev/null +++ b/src/config.ini @@ -0,0 +1,70 @@ +[solarflow] +sf_device_id = +#sf_product_id = + +[local] +mqtt_host = 192.168.1.245 +#mqtt_port = +#mqtt_user = +#mqtt_pwd = +#latitude = +#longitude = + +[control] +battery_low = 2 +battery_high = 98 +min_charge_level = 125 +max_discharge_level = 150 +day_discharge_soc = 50 +overage_limit = 15 +max_inverter_limit = 800 +inverter_mppts = 4 +inverter_sf_inputs_used = 1 +fast_change_offset = 200 +limit_inverter = true + +# window sizes to calculate moving averages of values to avoid overreacting to short spikes/drops +# use average of last X measurements of Solarflow solarinput +sf_window = 5 +# use average of last X measurements of house smartmeter/consumption +sm_window = 5 +# use average of last X measurements of inverter output +inv_window = 5 +# use average of last X measurements of inverter limit +limit_window = 5 + +# MQTT telemetry topics specify where solarflow control can read data for it's operation +# all topics must provide integer or float values (no json message format) +[mqtt_telemetry_topics] +# the topic that provides the current household power consumption, read from a smartmeter or equivalent +# you can also provide multiple topics (which will be added up), e.g for Shelly 3-Phase measurement devices +# by separating them with "," +# topic_house = shellies/shellyem3/emeter/1/power, shellies/shellyem3/emeter/2/power, shellies/shellyem3/emeter/3/power +topic_house = + +# topic for the microinverter input to home (e.g. from OpenDTU, AhouyDTU) +topic_acinput = + +# topics for panels power which are directly connected to the microinverter (optional) +# typically you would also get this from OpenDTU, AhouDTU or your inverter +# you can provide multiple topics by separating them with "," +# topic_direct_panel = solar/116491132532/1/power, solar/116491132532/2/power +topic_direct_panel = + +# topics for telemetry read from Solarflow Hub +# Note: Solarflow doesn't directly write to these topics when publishing to your local MQTT broker +# it rather writes to it's predefined topic. +# Therefor it's recommended to either run the solarflow statuspage or the little topic mapper script to +# "clean up" the topics and provide them at these locations +# See: /~https://github.com/reinhard-brandstaedter/solarflow-bt-manager/blob/master/src/solarflow-topic-mapper.py +#topic_solarflow_solarinput = solarflow-hub/telemetry/solarInputPower +#topic_solarflow_electriclevel = solarflow-hub/telemetry/electricLevel +#topic_solarflow_outputpack = solarflow-hub/telemetry/outputPackPower +#topic_solarflow_packinput = solarflow-hub/telemetry/packInputPower +#topic_solarflow_outputhome = solarflow-hub/telemetry/outputHomePower +#topic_solarflow_maxtemp = solarflow-hub/telemetry/batteries/+/maxTemp +#topic_solarflow_battery_soclevel = solarflow-hub/telemetry/batteries/+/socLevel + +# topic to steer your microinverter +# for OpenDTU and AhoyDTU please use the command topic that sets the ABSOLUTE limit (in watts) not the RELATIVE limit in percent +topic_limit_non_persistent = solar/116491132532/cmd/limit_nonpersistent_absolute diff --git a/src/solarflow-control.py b/src/solarflow-control.py index bbc49ac..57bf3bf 100644 --- a/src/solarflow-control.py +++ b/src/solarflow-control.py @@ -6,63 +6,110 @@ from astral.sun import sun import requests from ip2geotools.databases.noncommercial import DbIpCity +import configparser import click FORMAT = '%(asctime)s:%(levelname)s: %(message)s' logging.basicConfig(stream=sys.stdout, level="INFO", format=FORMAT) log = logging.getLogger("") -sf_device_id = os.environ.get('SF_DEVICE_ID',None) -sf_product_id = os.environ.get('SF_PRODUCT_ID',"73bkTV") -mqtt_user = os.environ.get('MQTT_USER',None) -mqtt_pwd = os.environ.get('MQTT_PWD',None) -mqtt_host = os.environ.get('MQTT_HOST',None) -mqtt_port = os.environ.get('MQTT_PORT',1883) -MIN_CHARGE_LEVEL = int(os.environ.get('MIN_CHARGE_LEVEL',125)) # The amount of power that should be always reserved for charging, if available. Nothing will be fed to the house if less is produced -MAX_DISCHARGE_LEVEL = int(os.environ.get('MAX_DISCHARGE_LEVEL',145)) # The maximum discharge level of the packSoc. Even if there is more demand it will not go beyond that -DAY_DISCHARGE_SOC = int(os.environ.get('DAY_DISCHARGE_SOC',50)) # The minimum state of charge of the battery to start discharging also throughout the day -OVERAGE_LIMIT = 15 # if we produce more than what we need we can feed that much to the grid -BATTERY_LOW = int(os.environ.get('BATTERY_LOW',10)) -BATTERY_HIGH = int(os.environ.get('BATTERY_HIGH',98)) -MAX_INVERTER_LIMIT = 800 # the maximum allowed inverter output +config: configparser.ConfigParser +def load_config(): + config = configparser.ConfigParser() + try: + with open("config.ini","r") as cf: + config.read_file(cf) + except: + log.error("No configuration file (config.ini) found in execution directory! Using environment variables.") + return config + +config = load_config() + +sf_device_id = config.get('solarflow', 'sf_device_id', fallback=None) or os.environ.get('SF_DEVICE_ID',None) +sf_product_id = config.get('solarflow', 'sf_product_id', fallback="73bkTV") or os.environ.get('SF_PRODUCT_ID',"73bkTV") +mqtt_user = config.get('local', 'mqtt_user', fallback=None) or os.environ.get('MQTT_USER',None) +mqtt_pwd = config.get('local', 'mqtt_pwd', fallback=None) or os.environ.get('MQTT_PWD',None) +mqtt_host = config.get('local', 'mqtt_host', fallback=None) or os.environ.get('MQTT_HOST',None) +mqtt_port = config.getint('local', 'mqtt_port', fallback=None) or os.environ.get('MQTT_PORT',1883) + +# The amount of power that should be always reserved for charging, if available. Nothing will be fed to the house if less is produced +MIN_CHARGE_LEVEL = config.getint('control', 'min_charge_level', fallback=125) \ + or int(os.environ.get('MIN_CHARGE_LEVEL',125)) + +# The maximum discharge level of the packSoc. Even if there is more demand it will not go beyond that +MAX_DISCHARGE_LEVEL = config.getint('control', 'max_discharge_level', fallback=150) \ + or int(os.environ.get('MAX_DISCHARGE_LEVEL',145)) + +# The minimum state of charge of the battery to start discharging also throughout the day +DAY_DISCHARGE_SOC = config.getint('control', 'day_discharge_soc', fallback=50) \ + or int(os.environ.get('DAY_DISCHARGE_SOC',50)) + +# if we produce more than what we need we can feed that much to the grid +OVERAGE_LIMIT = config.getint('control', 'overage_limit', fallback=15) \ + or int(os.environ.get('OVERAGE_LIMIT',15)) + +# battery SoC levels to consider the battry full or empty +BATTERY_LOW = config.getint('control', 'battery_low', fallback=2) \ + or int(os.environ.get('BATTERY_LOW',10)) +BATTERY_HIGH = config.getint('control', 'battery_high', fallback=98) \ + or int(os.environ.get('BATTERY_HIGH',98)) + +# the maximum allowed inverter output +MAX_INVERTER_LIMIT = config.getint('control', 'max_inverter_limit', fallback=98) \ + or int(os.environ.get('MAX_INVERTER_LIMIT',800)) MAX_INVERTER_INPUT = MAX_INVERTER_LIMIT - MIN_CHARGE_LEVEL -INVERTER_MPPTS = int(os.environ.get('INVERTER_MPPTS',4)) # the number of inverter inputs or mppts. SF only uses 2 so when limiting we need to adjust for that -INVERTER_SF_INPUTS_USED = int(os.environ.get('INVERTER_SF_INPUTS_USED',2)) # how many Inverter input channels are used by Solarflow -FAST_CHANGE_OFFSET = 200 -limit_inverter = bool(os.environ.get('LIMIT_INVERTER',False)) + + # the number of inverter inputs or mppts. SF only uses 1 or 2 so when limiting we need to adjust for that +INVERTER_MPPTS = config.getint('control', 'inverter_mppts', fallback=4) \ + or int(os.environ.get('INVERTER_MPPTS',4)) + +# how many Inverter input channels are used by Solarflow +INVERTER_INPUTS_USED = config.getint('control', 'inverter_sf_inputs_used', fallback=2) \ + or int(os.environ.get('INVERTER_SF_INPUTS_USED',2)) + +# the delta between two consecutive measurements on houshold usage to consider it a fast rise or drop +FAST_CHANGE_OFFSET = config.getint('control', 'fast_change_offset', fallback=200) \ + or int(os.environ.get('FAST_CHANGE_OFFSET',200)) + +# wether to limit the inverter or the solarflow hub +limit_inverter = config.getboolean('control', 'limit_inverter', fallback=False) \ + or bool(os.environ.get('LIMIT_INVERTER',False)) # Location Info -LAT=float(os.environ.get('LATITUDE',48.147381)) -LNG=float(os.environ.get('LONGITUDE',11.730140)) +LAT = config.getfloat('local', 'latitude', fallback=48.147381) or float(os.environ.get('LATITUDE',48.147381)) +LNG = config.getfloat('local', 'latitude', fallback=11.730140) or float(os.environ.get('LONGITUDE',11.730140)) # topic for the current household consumption (e.g. from smartmeter): int Watts # if there is no single topic wich aggregates multiple phases (e.g. shelly 3EM) you can specify the topic in an array like this -# topic_house = [shellies/shellyem3/emeter/1/power, shellies/shellyem3/emeter/2/power, shellies/shellyem3/emeter/3/power] -topic_house = os.environ.get('TOPIC_HOUSE',"tele/E220/SENSOR") -topics_house = [ t.strip() for t in topic_house.split(',')] +# topic_house = shellies/shellyem3/emeter/1/power, shellies/shellyem3/emeter/2/power, shellies/shellyem3/emeter/3/power +topic_house = config.get('mqtt_telemetry_topics', 'topic_house', fallback="tele/E220/SENSOR") \ + or os.environ.get('TOPIC_HOUSE',"tele/E220/SENSOR") +topics_house = [ t.strip() for t in topic_house.split(',')] # topic for the microinverter input to home (e.g. from OpenDTU, AhouyDTU) -topic_acinput = os.environ.get('TOPIC_ACINPUT',"solar/ac/power") +topic_acinput = config.get('mqtt_telemetry_topics', 'topic_acinput', fallback="solar/ac/power") \ + or os.environ.get('TOPIC_ACINPUT',"solar/ac/power") # topics for panels power which feed directly to inverter -topic_direct_panel = os.environ.get('TOPIC_DIRECT_PANEL',"solar/116491132532/1/power") -topics_direct_panel = [ t.strip() for t in topic_direct_panel.split(',')] +topic_direct_panel = config.get('mqtt_telemetry_topics', 'topic_direct_panel', fallback="solar/116491132532/1/power") \ + or os.environ.get('TOPIC_DIRECT_PANEL',"solar/116491132532/1/power") +topics_direct_panel = [ t.strip() for t in topic_direct_panel.split(',')] # topics for telemetry read from Solarflow Hub -topic_solarflow_solarinput = "solarflow-hub/telemetry/solarInputPower" -topic_solarflow_electriclevel = "solarflow-hub/telemetry/electricLevel" -topic_solarflow_outputpack = "solarflow-hub/telemetry/outputPackPower" -topic_solarflow_packinput = "solarflow-hub/telemetry/packInputPower" -topic_solarflow_outputhome = "solarflow-hub/telemetry/outputHomePower" -topic_solarflow_maxtemp = "solarflow-hub/telemetry/batteries/+/maxTemp" -topic_solarflow_battery_soclevel = "solarflow-hub/telemetry/batteries/+/socLevel" +topic_solarflow_solarinput = config.get('mqtt_telemetry_topics', 'topic_solarflow_solarinput', fallback="solarflow-hub/telemetry/solarInputPower") +topic_solarflow_electriclevel = config.get('mqtt_telemetry_topics', 'topic_solarflow_electriclevel', fallback="solarflow-hub/telemetry/electricLevel") +topic_solarflow_outputpack = config.get('mqtt_telemetry_topics', 'topic_solarflow_outputpack', fallback="solarflow-hub/telemetry/outputPackPower") +topic_solarflow_packinput = config.get('mqtt_telemetry_topics', 'topic_solarflow_packinput', fallback="solarflow-hub/telemetry/packInputPower") +topic_solarflow_outputhome = config.get('mqtt_telemetry_topics', 'topic_solarflow_outputhome', fallback="solarflow-hub/telemetry/outputHomePower") +topic_solarflow_maxtemp = config.get('mqtt_telemetry_topics', 'topic_solarflow_maxtemp', fallback="solarflow-hub/telemetry/batteries/+/maxTemp") +topic_solarflow_battery_soclevel = config.get('mqtt_telemetry_topics', 'topic_solarflow_battery_soclevel', fallback="solarflow-hub/telemetry/batteries/+/socLevel") # topic to control the Solarflow Hub (used to set output limit) topic_limit_solarflow = f'iot/{sf_product_id}/{sf_device_id}/properties/write' -# optional topic for controlling the inverter limit -#topic_ahoylimit = "inverter/ctrl/limit/0" #AhoyDTU -topic_limit_non_persistent = os.environ.get('TOPIC_LIMIT_OPENDTU',"solar/116491132532/cmd/limit_nonpersistent_absolute") #OpenDTU +# topic for controlling the inverter limit +topic_limit_non_persistent = config.get('mqtt_telemetry_topics', 'topic_limit_non_persistent', fallback="solar/116491132532/cmd/limit_nonpersistent_absolute") \ + or os.environ.get('TOPIC_LIMIT_OPENDTU',"solar/116491132532/cmd/limit_nonpersistent_absolute") # location info for determining sunrise/sunset loc = LocationInfo(timezone='Europe/Berlin',latitude=LAT, longitude=LNG) @@ -278,7 +325,7 @@ def checkCharging(client: mqtt_client): global maxtemp socset = {"properties": { "socSet": 0 }} if maxtemp < 1000: - log.warning(f'The maximum measured battery temperature is {maxtemp/100}. Disabling charging to avoid damage! Please reset manually one temperature is high enough!') + log.warning(f'The maximum measured battery temperature is {maxtemp/100}. Disabling charging to avoid damage! Please reset manually once temperature is high enough!') client.publish(topic_limit_solarflow,json.dumps(socset)) # limit the output to home setting on the Solarflow hub @@ -304,7 +351,7 @@ def limitSolarflow(client: mqtt_client, limit): # set the limit on the inverter (when using inverter only mode) def limitInverter(client: mqtt_client, limit): # make sure that the inverter limit (which is applied to all MPPTs output equally) matches globally for what we need - inv_limit = limit*(1/(INVERTER_SF_INPUTS_USED/INVERTER_MPPTS)) + inv_limit = limit*(1/(INVERTER_INPUTS_USED/INVERTER_MPPTS)) client.publish(topic_limit_non_persistent,f'{inv_limit}') return inv_limit @@ -428,16 +475,8 @@ def run(): checkCharging(client) limitHomeInput(client) - client.loop_stop() -#@click.command -#@click.option("--limit-via", type=click.Choice(['inverter','hub'], case_sensitive=False)) -#@click.option("--broker","-b",help="IP/Hostname of the local MQTT broker to use") -#@click.option("--port","-p",help="Port of the local MQTT broker, if different from default (1883)") -#@click.option("--user","-u", help="Login name for local MQTT broker") -#@click.option("--secret","-s", help="Password for the local MQTT broker user") -#@click.option("--offline/--online", default=True, help="Offline/Online mode: either connect to the Zendure API/MQTT or not (requires local MQTT with hub data present)") def main(argv): global mqtt_host, mqtt_port, mqtt_user, mqtt_pwd global sf_device_id From 85ffaadd7676ebb84924188b02c9437a33c750fb Mon Sep 17 00:00:00 2001 From: Reinhard Weber Date: Thu, 5 Oct 2023 22:28:05 +0200 Subject: [PATCH 02/18] remove x-build specifics --- src/Dockerfile | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Dockerfile b/src/Dockerfile index d891057..63efd62 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -6,13 +6,7 @@ WORKDIR / COPY solarflow-control.py / RUN pip install --upgrade pip -RUN apk add --no-cache gcc -RUN apk add --no-cache linux-headers -RUN apk add --no-cache --virtual .build-deps gcc libc-dev libxslt-dev && \ - apk add --no-cache libxslt && \ - pip install --no-cache-dir lxml>=3.5.0 && \ - apk del .build-deps -RUN pip install cffi paho-mqtt astral click ip2geotools +RUN pip install paho-mqtt astral ip2geotools ENTRYPOINT ["python","solarflow-control.py"] \ No newline at end of file From 11bd7cf8c8a30b2e296acdf98debafc928bf14f8 Mon Sep 17 00:00:00 2001 From: Reinhard Weber Date: Thu, 5 Oct 2023 22:33:28 +0200 Subject: [PATCH 03/18] split pip installs --- src/Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Dockerfile b/src/Dockerfile index 63efd62..73917b5 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -6,7 +6,8 @@ WORKDIR / COPY solarflow-control.py / RUN pip install --upgrade pip - -RUN pip install paho-mqtt astral ip2geotools +RUN pip install paho-mqtt +RUN pip install astral +RUN pip install ip2geotools ENTRYPOINT ["python","solarflow-control.py"] \ No newline at end of file From b11f42da6c69e91653bfb0126ee0076a05362c22 Mon Sep 17 00:00:00 2001 From: Reinhard Weber Date: Thu, 5 Oct 2023 22:42:55 +0200 Subject: [PATCH 04/18] add dev libs for x-build --- src/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Dockerfile b/src/Dockerfile index 73917b5..6cf9436 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -8,6 +8,7 @@ COPY solarflow-control.py / RUN pip install --upgrade pip RUN pip install paho-mqtt RUN pip install astral +RUN apk add libxml2-dev libxslt-dev RUN pip install ip2geotools ENTRYPOINT ["python","solarflow-control.py"] \ No newline at end of file From fabae8f6c791b2c568855008ff265badf68d3228 Mon Sep 17 00:00:00 2001 From: Reinhard Weber Date: Thu, 5 Oct 2023 22:50:33 +0200 Subject: [PATCH 05/18] x-build deps --- src/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dockerfile b/src/Dockerfile index 6cf9436..376f7d6 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -8,7 +8,7 @@ COPY solarflow-control.py / RUN pip install --upgrade pip RUN pip install paho-mqtt RUN pip install astral -RUN apk add libxml2-dev libxslt-dev +RUN apk add libxml2-dev libxslt-dev libffi libffi-dev RUN pip install ip2geotools ENTRYPOINT ["python","solarflow-control.py"] \ No newline at end of file From 6ef268389ef6b7cc2aeaef4e2022046f2f41aa32 Mon Sep 17 00:00:00 2001 From: Reinhard Weber Date: Thu, 5 Oct 2023 22:57:48 +0200 Subject: [PATCH 06/18] add gcc for x-build --- src/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dockerfile b/src/Dockerfile index 376f7d6..6cc3697 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -8,7 +8,7 @@ COPY solarflow-control.py / RUN pip install --upgrade pip RUN pip install paho-mqtt RUN pip install astral -RUN apk add libxml2-dev libxslt-dev libffi libffi-dev +RUN apk add libxml2-dev libxslt-dev libffi libffi-dev gcc RUN pip install ip2geotools ENTRYPOINT ["python","solarflow-control.py"] \ No newline at end of file From f00157dacd0ab8872c7d159abb885816880e84c5 Mon Sep 17 00:00:00 2001 From: Reinhard Weber Date: Thu, 5 Oct 2023 23:06:35 +0200 Subject: [PATCH 07/18] add musl dev --- src/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dockerfile b/src/Dockerfile index 6cc3697..db7366a 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -8,7 +8,7 @@ COPY solarflow-control.py / RUN pip install --upgrade pip RUN pip install paho-mqtt RUN pip install astral -RUN apk add libxml2-dev libxslt-dev libffi libffi-dev gcc +RUN apk add libxml2-dev libxslt-dev libffi libffi-dev gcc musl-dev RUN pip install ip2geotools ENTRYPOINT ["python","solarflow-control.py"] \ No newline at end of file From ff67ba608977d34be229317726b0bd65388126d1 Mon Sep 17 00:00:00 2001 From: Reinhard Weber Date: Thu, 5 Oct 2023 23:43:08 +0200 Subject: [PATCH 08/18] add rust --- src/solarflow-control.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/solarflow-control.py b/src/solarflow-control.py index 3f0858e..e745fb3 100644 --- a/src/solarflow-control.py +++ b/src/solarflow-control.py @@ -1,5 +1,5 @@ import random, json, time, logging, sys, getopt, os -from datetime import datetime, date +from datetime import datetime, timedelta from functools import reduce from paho.mqtt import client as mqtt_client from astral import LocationInfo @@ -136,6 +136,7 @@ def load_config(): direct_panel_values = {} direct_panel_power = -1 last_solar_input_update = datetime.now() +charge_through = False class MyLocation: @@ -423,11 +424,16 @@ def limitHomeInput(client: mqtt_client): if solarinput <= MIN_CHARGE_LEVEL: path += "2." # producing less than the minimum charge level if (now < sunrise or now > sunset) or min(batterySocs.values()) > DAY_DISCHARGE_SOC: - path += "1" + path += "1" limit = min(demand,MAX_DISCHARGE_LEVEL) # in the morning keep using battery, in the evening start using battery + td = timedelta(minutes = 5) + if charge_through or (now > sunset and now < sunset + td and packSoc < DAY_DISCHARGE_SOC): # charge through mode, do not discharge when battery is low at sunset + charge_through = True + limit = 0 else: path += "2" limit = 0 # throughout the day use everything to charge + charge_through = False if len(limit_values) >= limit_window: limit_values.pop(0) From 31dc94ce892a82b98c98d9df57bf3142166eacce Mon Sep 17 00:00:00 2001 From: Reinhard Weber Date: Thu, 5 Oct 2023 23:43:13 +0200 Subject: [PATCH 09/18] add rust --- src/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dockerfile b/src/Dockerfile index db7366a..b11e816 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -8,7 +8,7 @@ COPY solarflow-control.py / RUN pip install --upgrade pip RUN pip install paho-mqtt RUN pip install astral -RUN apk add libxml2-dev libxslt-dev libffi libffi-dev gcc musl-dev +RUN apk add libxml2-dev libxslt-dev libffi libffi-dev gcc musl-dev rust RUN pip install ip2geotools ENTRYPOINT ["python","solarflow-control.py"] \ No newline at end of file From 864339c04acdf8c5d8ed472ccf285f1bc522ec25 Mon Sep 17 00:00:00 2001 From: Reinhard Weber Date: Fri, 6 Oct 2023 09:08:56 +0200 Subject: [PATCH 10/18] add cargo --- src/Dockerfile | 2 +- src/solarflow-control.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Dockerfile b/src/Dockerfile index b11e816..edc7f66 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -8,7 +8,7 @@ COPY solarflow-control.py / RUN pip install --upgrade pip RUN pip install paho-mqtt RUN pip install astral -RUN apk add libxml2-dev libxslt-dev libffi libffi-dev gcc musl-dev rust +RUN apk add libxml2-dev libxslt-dev libffi libffi-dev gcc musl-dev rust cargo RUN pip install ip2geotools ENTRYPOINT ["python","solarflow-control.py"] \ No newline at end of file diff --git a/src/solarflow-control.py b/src/solarflow-control.py index e745fb3..17d49bf 100644 --- a/src/solarflow-control.py +++ b/src/solarflow-control.py @@ -365,6 +365,8 @@ def limitHomeInput(client: mqtt_client): global home global packSoc, batterySocs, direct_panel_power global smartmeter_values, solarflow_values, inverter_values + global charge_through + # ensure we have data to work on if len(smartmeter_values) == 0: log.info(f'Waiting for smartmeter data to make decisions...') From 9fa4ea72d5b0b1a604c72292b12e38c0d5b76152 Mon Sep 17 00:00:00 2001 From: Reinhard Weber Date: Fri, 6 Oct 2023 09:40:31 +0200 Subject: [PATCH 11/18] add openssl --- src/Dockerfile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Dockerfile b/src/Dockerfile index edc7f66..b97498a 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -6,9 +6,10 @@ WORKDIR / COPY solarflow-control.py / RUN pip install --upgrade pip -RUN pip install paho-mqtt -RUN pip install astral -RUN apk add libxml2-dev libxslt-dev libffi libffi-dev gcc musl-dev rust cargo -RUN pip install ip2geotools +RUN apk add libxml2-dev libxslt-dev libffi libffi-dev gcc musl-dev rust cargo openssl && \ + pip install paho-mqtt astral ip2geotools && \ + apk del libxml2-dev libxslt-dev libffi-dev musl-dev gcc rust && \ + rm -rf /var/cache/apk/* + ENTRYPOINT ["python","solarflow-control.py"] \ No newline at end of file From fcf45d9406b13f495b121a53e61192adeadf258f Mon Sep 17 00:00:00 2001 From: Reinhard Weber Date: Fri, 6 Oct 2023 09:43:21 +0200 Subject: [PATCH 12/18] openssl-dev --- src/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dockerfile b/src/Dockerfile index b97498a..5835777 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -6,7 +6,7 @@ WORKDIR / COPY solarflow-control.py / RUN pip install --upgrade pip -RUN apk add libxml2-dev libxslt-dev libffi libffi-dev gcc musl-dev rust cargo openssl && \ +RUN apk add libxml2-dev libxslt-dev libffi libffi-dev gcc musl-dev rust cargo openssl-dev && \ pip install paho-mqtt astral ip2geotools && \ apk del libxml2-dev libxslt-dev libffi-dev musl-dev gcc rust && \ rm -rf /var/cache/apk/* From ce577cfaa08f817ac5ca6c5cf79742df8ac79146 Mon Sep 17 00:00:00 2001 From: Reinhard Weber Date: Fri, 6 Oct 2023 10:27:54 +0200 Subject: [PATCH 13/18] make image slimmer --- src/Dockerfile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Dockerfile b/src/Dockerfile index 542ac44..e87f653 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -5,11 +5,11 @@ WORKDIR / COPY solarflow-control.py / -RUN pip install --upgrade pip -RUN pip install paho-mqtt -RUN pip install astral -RUN apk add libxml2-dev libxslt-dev libffi libffi-dev gcc musl-dev rust cargo openssl-dev -RUN pip install ip2geotools - +RUN pip install --upgrade pip && \ + apk add libxml2-dev libxslt-dev libffi libffi-dev gcc musl-dev rust cargo openssl-dev && \ + pip install paho-mqtt astral ip2geotools && \ + apk del openssl-dev libffi-dev xz-dev libxml2-dev libxslt-dev openssl-dev linux-headers musl-dev gcc && \ + rm -rf /root/.cache/ && \ + rm -rf /var/cache/apk/* ENTRYPOINT ["python","solarflow-control.py"] \ No newline at end of file From 64cecb8c81951f558f7716a3810ce01b675683aa Mon Sep 17 00:00:00 2001 From: Reinhard Weber Date: Fri, 6 Oct 2023 10:28:32 +0200 Subject: [PATCH 14/18] make image slimmer --- src/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dockerfile b/src/Dockerfile index e87f653..853fe8b 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -8,7 +8,7 @@ COPY solarflow-control.py / RUN pip install --upgrade pip && \ apk add libxml2-dev libxslt-dev libffi libffi-dev gcc musl-dev rust cargo openssl-dev && \ pip install paho-mqtt astral ip2geotools && \ - apk del openssl-dev libffi-dev xz-dev libxml2-dev libxslt-dev openssl-dev linux-headers musl-dev gcc && \ + apk del openssl-dev libffi-dev xz-dev libxml2-dev libxslt-dev openssl-dev linux-headers musl-dev gcc rust cargo && \ rm -rf /root/.cache/ && \ rm -rf /var/cache/apk/* From 4a43412b67c8aced97595091e13c9285445f8654 Mon Sep 17 00:00:00 2001 From: Reinhard Weber Date: Fri, 6 Oct 2023 12:29:14 +0200 Subject: [PATCH 15/18] default value bug --- src/solarflow-control.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/solarflow-control.py b/src/solarflow-control.py index 17d49bf..cfb0192 100644 --- a/src/solarflow-control.py +++ b/src/solarflow-control.py @@ -55,7 +55,7 @@ def load_config(): or int(os.environ.get('BATTERY_HIGH',98)) # the maximum allowed inverter output -MAX_INVERTER_LIMIT = config.getint('control', 'max_inverter_limit', fallback=98) \ +MAX_INVERTER_LIMIT = config.getint('control', 'max_inverter_limit', fallback=800) \ or int(os.environ.get('MAX_INVERTER_LIMIT',800)) MAX_INVERTER_INPUT = MAX_INVERTER_LIMIT - MIN_CHARGE_LEVEL @@ -366,7 +366,7 @@ def limitHomeInput(client: mqtt_client): global packSoc, batterySocs, direct_panel_power global smartmeter_values, solarflow_values, inverter_values global charge_through - + # ensure we have data to work on if len(smartmeter_values) == 0: log.info(f'Waiting for smartmeter data to make decisions...') From 34b5f4e33cc35d766b91943b10193a52fe0acd4c Mon Sep 17 00:00:00 2001 From: Reinhard Weber Date: Fri, 6 Oct 2023 14:07:15 +0200 Subject: [PATCH 16/18] config for direct panel --- src/config.ini | 2 +- src/solarflow-control.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/config.ini b/src/config.ini index e2ca53f..994be98 100644 --- a/src/config.ini +++ b/src/config.ini @@ -49,7 +49,7 @@ topic_acinput = # typically you would also get this from OpenDTU, AhouDTU or your inverter # you can provide multiple topics by separating them with "," # topic_direct_panel = solar/116491132532/1/power, solar/116491132532/2/power -topic_direct_panel = +#topic_direct_panel = # topics for telemetry read from Solarflow Hub # Note: Solarflow doesn't directly write to these topics when publishing to your local MQTT broker diff --git a/src/solarflow-control.py b/src/solarflow-control.py index cfb0192..2b18eb3 100644 --- a/src/solarflow-control.py +++ b/src/solarflow-control.py @@ -91,9 +91,9 @@ def load_config(): or os.environ.get('TOPIC_ACINPUT',"solar/ac/power") # topics for panels power which feed directly to inverter -topic_direct_panel = config.get('mqtt_telemetry_topics', 'topic_direct_panel', fallback="solar/116491132532/1/power") \ - or os.environ.get('TOPIC_DIRECT_PANEL',"solar/116491132532/1/power") -topics_direct_panel = [ t.strip() for t in topic_direct_panel.split(',')] +topic_direct_panel = config.get('mqtt_telemetry_topics', 'topic_direct_panel', fallback=None) \ + or os.environ.get('TOPIC_DIRECT_PANEL',None) +topics_direct_panel = [ t.strip() for t in topic_direct_panel.split(',') ] if topic_direct_panel else [] # topics for telemetry read from Solarflow Hub From 782cacf279abacf6583f8611175ca2de6a82aabd Mon Sep 17 00:00:00 2001 From: Reinhard Weber Date: Fri, 6 Oct 2023 20:55:28 +0200 Subject: [PATCH 17/18] parallel builds --- src/Dockerfile | 2 +- src/solarflow-control.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Dockerfile b/src/Dockerfile index 853fe8b..83d5535 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -7,7 +7,7 @@ COPY solarflow-control.py / RUN pip install --upgrade pip && \ apk add libxml2-dev libxslt-dev libffi libffi-dev gcc musl-dev rust cargo openssl-dev && \ - pip install paho-mqtt astral ip2geotools && \ + pip install --install-option="--jobs=4" paho-mqtt astral ip2geotools && \ apk del openssl-dev libffi-dev xz-dev libxml2-dev libxslt-dev openssl-dev linux-headers musl-dev gcc rust cargo && \ rm -rf /root/.cache/ && \ rm -rf /var/cache/apk/* diff --git a/src/solarflow-control.py b/src/solarflow-control.py index 2b18eb3..5d28f01 100644 --- a/src/solarflow-control.py +++ b/src/solarflow-control.py @@ -42,7 +42,10 @@ def load_config(): # The minimum state of charge of the battery to start discharging also throughout the day DAY_DISCHARGE_SOC = config.getint('control', 'day_discharge_soc', fallback=50) \ - or int(os.environ.get('DAY_DISCHARGE_SOC',50)) + or int(os.environ.get('DAY_DISCHARGE_SOC',50)) + +CHARGE_THROUGH_THRESHOLD = config.getint('control', 'charge_through_threshold', fallback=60) \ + or int(os.environ.get('CHARGE_THROUGH_THRESHOLD',60)) # if we produce more than what we need we can feed that much to the grid OVERAGE_LIMIT = config.getint('control', 'overage_limit', fallback=15) \ @@ -429,7 +432,7 @@ def limitHomeInput(client: mqtt_client): path += "1" limit = min(demand,MAX_DISCHARGE_LEVEL) # in the morning keep using battery, in the evening start using battery td = timedelta(minutes = 5) - if charge_through or (now > sunset and now < sunset + td and packSoc < DAY_DISCHARGE_SOC): # charge through mode, do not discharge when battery is low at sunset + if charge_through or (now > sunset and now < sunset + td and packSoc < CHARGE_THROUGH_THRESHOLD): # charge through mode, do not discharge when battery is low at sunset charge_through = True limit = 0 else: From 3592eee715f8fe7420d90b678556c58ece4cbd3c Mon Sep 17 00:00:00 2001 From: Reinhard Weber Date: Fri, 6 Oct 2023 20:59:39 +0200 Subject: [PATCH 18/18] parallel install option not working anymor --- src/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dockerfile b/src/Dockerfile index 83d5535..853fe8b 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -7,7 +7,7 @@ COPY solarflow-control.py / RUN pip install --upgrade pip && \ apk add libxml2-dev libxslt-dev libffi libffi-dev gcc musl-dev rust cargo openssl-dev && \ - pip install --install-option="--jobs=4" paho-mqtt astral ip2geotools && \ + pip install paho-mqtt astral ip2geotools && \ apk del openssl-dev libffi-dev xz-dev libxml2-dev libxslt-dev openssl-dev linux-headers musl-dev gcc rust cargo && \ rm -rf /root/.cache/ && \ rm -rf /var/cache/apk/*