diff --git a/.dockerignore b/.dockerignore index fff7c2ed..97f6bee1 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,2 @@ -settings.py \ No newline at end of file +src/settings.py +givenergy_modbus \ No newline at end of file diff --git a/.gitignore b/.gitignore index d4692e7d..5a12f46f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ settings.old __pycache__ givtcp_debug.log .venv -.vscode \ No newline at end of file +.vscode +/givenergy_modbus \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index c48914ed..3fb7bd13 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,6 +14,7 @@ RUN pip install -r requirements.txt # copy the content of the local src directory to the working directory COPY src/ . +ADD givenergy_modbus /usr/local/lib/python3.10/site-packages/givenergy_modbus ENV INVERTOR_IP="" ENV MQTT_OUTPUT="True" @@ -23,7 +24,7 @@ ENV MQTT_PASSWORD="" ENV MQTT_TOPIC="" ENV MQTT_PORT="1883" ENV JSON_OUTPUT="False" -ENV DEBUG="False" +ENV LOG_LEVEL="Error" ENV DEBUG_FILE_LOCATION="" ENV PRINT_RAW="False" ENV SELF_RUN="True" @@ -33,9 +34,6 @@ ENV INFLUX_URL="" ENV INFLUX_TOKEN="" ENV INFLUX_BUCKET="" ENV INFLUX_ORG="" -ENV HA_OUTPUT="False" -ENV HA_URL="" -ENV HA_TOKEN="" EXPOSE 6345 1883 diff --git a/README.md b/README.md index 67f9487a..88f762b4 100644 --- a/README.md +++ b/README.md @@ -16,36 +16,40 @@ A settings.py file is required in the root directory. Use the supplied settings_ # Execution of GivTCP GivTCP can be executed in a number of ways and can be set to output data in multiple formats. Exact usage is dependent on your use-case and needs: -| Method | Description | -| ---------------| ------------------------------- | -| [CLI](#cli-usage) | Execute the script at the command line and pass the relevant function and parameters as per the details below | -| [CLI through Node-Read](#cli-through-node-red) | Using the Exec node in Node-Red to call the script in the same way as above. This allows automation of script calling within a wider automated system | -| [REST Service](#restful-service) | Deployed inside a Docker container a RESTful service calls the relevant functions using the details below through GET and POST http methods | -| [Docker container](#docker) | A docker container is available which has all code and pre-requisites installed and out of the box is set to auto-discover the invertor and publish to an internal MQTT broker. This can be changed to publish to an external broker by modifying the container ENV variables | +Reccomended usage is through the Docker container found here: https://hub.docker.com/repository/docker/britkat/giv_tcp-ma +This will set up a self-running service which will publish data as required and provide a REST interface for control. An internal MQTT broker can be activiated to make data avalable on the network. + # Output formats: ## MQTT The script will publish directly to the nominated MQTT broker all the requested read data. image - ## JSON The functions return a JSON formated object which can then be consumed by other systems or functions, default output is to stdout +## InfluxDB +Publish Power and energy stats to your InfluxDB database using credentials you provide. + # GivTCP functions ## Read functions -GivTCP is able to retrieve key information from the GivEnergy Invertors through predefined functions: -### Available read functions are: GivTCP collects all invertor ad battery data through the "runAll" function. It creates a nested data structure with all data available in a structured format. Data Elements are: -*Energy - Today and all-time Total -*Power - Real-time stats and power flow data -*Invertor Details - Status details such as Serial Number -*Timeslots - Charge and Discharge -*Control - Charge/Discharge rates, Battery SOC -*Battery Details - Status and real-time cell voltages +* Energy - Today and all-time Total + * Today + * Total +* Power - Real-time stats and power flow data + * Power stats (eg. Import) + * Power Flow (eg. Grid to House) +* Invertor Details - Status details such as Serial Number +* Timeslots - Charge and Discharge +* Control - Charge/Discharge rates, Battery SOC +* Battery Details - Status and real-time cell voltages + * Battery 1 + * Battery 2 + * ... ## Control functions @@ -68,58 +72,14 @@ Control is available through predefined functions. The format of the function ca |setBatteryMode|{"mode":"1"}| Sets battery operation mode. Mode value must be in the range 1-4| |setDateTime|{"dateTime":"dd/mm/yyyy hh:mm:ss"}| Sets invertor time, format must be as shown here| -# CLI Usage -GivTCP can be called from any machine running Python3. The relevant script must be called and a function name passed to it as an argument. Exmaples of how to call read and write functions are shown here: - -## Read -`python3 read.py {{functionName}}` - -The full call to get all information by running the runAll function would then be: - -`python3 read.py runAll` - -## Control -`python3 write.py {{functionName}} '{{controlPayload}}'` - -An example payload can be found below. -Note: In order to send json payloads via CLI you will need to place the JSON string inside single quotes - -The full call to set Charge Timeslot 1 would then be: - -`python3 write.py setChargeSlot1 '{"enable": true,"start": "0100","finish": "0400","chargeToPercent": "100"}'` - -# CLI through Node-Red - -Node-Red provides the ability to call the python scripts through an "Exec" node. This essentially makes a command line call to the host OS, and mimics the CLI process above. The advantage of this execution method is that it allows integration into wider automation systems and provides a stable, self-healing ability to run the script on demand. wrapping this core code in a logic flow. - -![image](https://user-images.githubusercontent.com/69121158/122303286-3bc7e000-cefb-11eb-93c5-bf48907bd189.png) - -To execute this you must download the src files for this project, place them in an accessible folder on your machine running Node-Red then set the paramters of the Exec functon to call it. - -Example Node-Red flows are vailable here: XXXX - -# RESTful Service -GivTCP provides a wrapper function REST.py which uses Flask to expose the read and control functions as RESTful http calls. To utilise this service you will need to either use a WSGI serivce such as gunicorn or use the pre-built Docker container - -## Gunicorn -Ensure Gunicorn is installed by running: - -`pip install gunicorn` - -Then call the service by initiating the following command from the same directory as the downloaded src files: - -`gunicorn -w 4 -b 127.0.0.1:6345 REST:giv_api` - -(where the 127.0.0.1:6345 is the IP address and port you want to bind the service to) - -## Docker +# Docker The docker container can be downloaded at the Docker hub here: https://hub.docker.com/repository/docker/britkat/giv_tcp-ma * Docker image is multi-architecture so docker should grab the correct version for your system (tested on x86 and rpi3) * Create a container with the relevant ENV variables below (mimicing the settings.py file) * Set the container to auto-restart to ensure reliability -* Out of the box the default setup enables local MQTT broker and REST service +* Out of the box the default setup enables local MQTT broker and REST service (see below for details) * For Invertor autodiscovery to function your container must run on the "Host" network within docker (not Bridge). If it fails then you will need to manually add in INVERTOR_IP to the env variables | ENV Name | Example | Description | @@ -131,19 +91,21 @@ https://hub.docker.com/repository/docker/britkat/giv_tcp-ma | MQTT_USERNAME | bob | Optional | | MQTT_PASSWORD | cat | Optional | | MQTT_TOPIC | GivEnergy/Data | Optional - default is Givenergy.| -| DEBUG | False | Optional - if True then will write debug info to sepcified file location (default is same directory as the py files) | +| LOG_LEVEL | Error | Optional - you can choose Error, Info or Debug. Output will be sent to the debug file location if specified, otherwise it is sent to stdout| | DEBUG_FILE_LOCATION | /usr/pi/data | Optional | | PRINT_RAW | False | Optional - If set to True the raw register values will be returned alongside the normal data | | INFLUX_OUTPUT | False | Optional - Used to enable publishing of energy and power data to influx | | INFLUX_TOKEN |abcdefg123456789| Optional - If using influx this is the token generated from within influxdb itself | | INFLUX_BUCKET |giv_bucket| Optional - If using influx this is data bucket to use| -| INFLUX_ORG |giv_tcp| Optional - If using influx this is the org that the token is assigned to | -| HA_OUTPUT | False |Optional - Used to enable publishing of energy and power data to Home Assistant | -| HA_URL |https://homeassistant.local:8123| URL of Home Assistant instance (noting correct use of IP or domain name, whichever works in your browser)| -| HA_TOKEN |abcdefg123456789| Optional - If using Home Assistant this is the Long Lasting Token generated from within Home Assistant itself | +| INFLUX_ORG |giv_tcp| Optional - If using influx this is the org that the token is assigned to | + +# RESTful Service +GivTCP provides a wrapper function REST.py which uses Flask to expose the read and control functions as RESTful http calls. To utilise this service you will need to either use a WSGI serivce such as gunicorn or use the pre-built Docker container. +This can be used within a Node-Red flow to integrate into your automation or using Home Assistany REST sensors unsing the Home Assistant yaml package provided. +NB.This does require the Docker container running on your network. -### Calling RESTFul Functions +## Calling RESTFul Functions The following table outlines the http methods needed to call the various read and control functions. For each control function the payload is an identical JSON string as above (minus the single quotes). The RESTful Service will return a JSON object which you can then parse as you so desire @@ -151,12 +113,12 @@ The RESTful Service will return a JSON object which you can then parse as you so URL's below are based off the root http address of http://IP:6345 (Port may change if you are running yourself using gunicorn, in which case use the details specified in the gunicorn command) -#### Read Functions +### Read Functions | URL | Method | payload | | ------------------ | ------------ | -------------------- | | /runAll| GET | None | | -#### Control Functions +### Control Functions | URL | Method | payload | | ------------------ | ------------ | -------------------- | | /disableChargeTarget| POST | None | @@ -173,3 +135,25 @@ URL's below are based off the root http address of http://IP:6345 | /setDischargeSlot2| POST | {"start":"0100","finish":"0400","dischargeToPercent":"55"} | | /setBatteryMode| POST | {"mode":"1"} | | /setDateTime|POST | {"dateTime":"dd/mm/yyyy hh:mm:ss"}| + + +## Gunicorn +If you don't wish to run the docker container, you can set up your own wysg application using Gunicorn/Flask. +Ensure Gunicorn is installed by running: + +`pip install gunicorn` + +Then call the service by initiating the following command from the same directory as the downloaded src files: + +`gunicorn -w 4 -b 127.0.0.1:6345 REST:giv_api` + +(where the 127.0.0.1:6345 is the IP address and port you want to bind the service to) + +# CLI Usage +GivTCP can be called from any machine running Python3. The relevant script must be called and a function name passed to it as an argument. + +An example payload can be found below. +Note: In order to send json payloads via CLI you will need to place the JSON string inside single quotes + +The full call to set Charge Timeslot 1 would then be: +`python3 write.py setChargeSlot1 '{"enable": true,"start": "0100","finish": "0400","chargeToPercent": "100"}'` \ No newline at end of file diff --git a/documentaion/APIDocumentation.md b/documentaion/APIDocumentation.md deleted file mode 100644 index bb52628e..00000000 --- a/documentaion/APIDocumentation.md +++ /dev/null @@ -1,42 +0,0 @@ -# API Documentation - -## GivTCP class - -### Methods - -- getTimeslots() - -- getCombinedStats() - - -#### getTimeslots() -Returns the following values: - -Name |Value|MQTT topic ---------------|-------------|-------------- -Discharge Start 1| 0 to 2359 | GivEnergy/`datalloggerSN`/timeslots/dischargeStart1 -Discharge End 1| 0 to 2359 | GivEnergy/`datalloggerSN`/timeslots/dischargeEnd1 -Discharge Start 2| 0 to 2359 | GivEnergy/`datalloggerSN`/timeslots/dischargeStart2 -Discharge End 2| 0 to 2359 | GivEnergy/`datalloggerSN`/timeslots/dischargeEnd2 -Charge Start 1| 0 to 2359 | GivEnergy/`datalloggerSN`/timeslots/chargeStart1 -Charge Start 1| 0 to 2359 | GivEnergy/`datalloggerSN`/timeslots/chargeEnd1 - -#### getCombinedStats() - -Returns the following values: - -Name |Unit|MQTT topic ---------------|-------------|-------------- -PV Power| W | GivEnergy/`datalloggerSN`/power/pvPower -Grid Power| W | GivEnergy/`datalloggerSN`/power/gridPower -Import Power| W | GivEnergy/`datalloggerSN`/power/importPower -Export Power| W | GivEnergy/`datalloggerSN`/power/exportPower -EPS Power| W | GivEnergy/`datalloggerSN`/power/epsPower -Load Power| W | GivEnergy/`datalloggerSN`/power/loadPower -EPS Power| W | GivEnergy/`datalloggerSN`/power/batteryPower -Charge Power| W | GivEnergy/`datalloggerSN`/power/chargePower -Discharge Power| W | GivEnergy/`datalloggerSN`/power/dischargePower -SOC| percent | GivEnergy/`datalloggerSN`/power/SOC -Total Grid Export Energy| 0.1 kWh | GivEnergy/`datalloggerSN`/energy/totalGridExportEnergy -Total Load Energy| 0.1 kWh | GivEnergy/`datalloggerSN`/energy/totalLoadEnergy -Total Grid Import Energy| 0.1 kWh | GivEnergy/`datalloggerSN`/energy/totalGridImportEnergy diff --git a/documentaion/registersAndFunctions.xlsb.xlsx b/documentaion/registersAndFunctions.xlsb.xlsx deleted file mode 100644 index ca178b42..00000000 Binary files a/documentaion/registersAndFunctions.xlsb.xlsx and /dev/null differ diff --git a/documentaion/tutorial.md b/documentaion/tutorial.md deleted file mode 100644 index f6cb3554..00000000 --- a/documentaion/tutorial.md +++ /dev/null @@ -1,32 +0,0 @@ -# Starting Guide - -This page will shows you how to use the script, if you are unsure where to start, just following on this page! - -## Set Up -You will need set up your dongle and have your device connected to dongle. -( If you are connecting to your main Wifi and your dongle serves extention, you need to change some the configuration, see below for more) - -## Methodology -- VS code - -### VS code - - -1. Press the code button at the top right of the github page - -2. Download the zip file - -3. Extract the zip file and open the extracted file on VS code - - - -4. Go to Terminal and run the code - -```python3 GivTCP.py {{invertorIP}} {{wifiSN}} {{MQTTIP}} {{MQTTusername}} {{MQTTpassword}}``` - -where the invertorIP (if directly connected to dongle) is 10.10.100.254 - -MQTTIP is your MQTT address, if you dont have a MQTT preference, or you just want to make a test, you can choose `public.mqtthq.com` -from https://mqtthq.com/ - -The last two parameters are optional, if you have an MQTT account, you can type the parameter to save your data into your own MQTT account diff --git a/givtcp.yaml b/givtcp.yaml new file mode 100644 index 00000000..6484da8f --- /dev/null +++ b/givtcp.yaml @@ -0,0 +1,236 @@ +sensor: + - platform: rest + name: GivTCP + resource: http://192.168.2.10:6345/runAll #GivTCP Invertor connection + method: GET + scan_interval: 20 # refresh + timeout: 15 + value_template: 'Connected' + json_attributes: # define the entrys I want to retrive + - Energy + - Power + - Invertor Details + - Timeslots + - Control + - Battery Details + +template: + - sensor: + - name: "GivTCP Import Energy Total" + unit_of_measurement: "kWh" + device_class: energy + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Energy')['Total']['Import Energy Total kWh'] }}" + - name: "GivTCP Export Energy Total" + unit_of_measurement: "kWh" + device_class: energy + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Energy')['Total']['Export Energy Total kWh'] }}" + - name: "GivTCP Battery Throughput Total" + unit_of_measurement: "kWh" + device_class: energy + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Energy')['Total']['Battery Throughput Total kWh'] }}" + - name: "GivTCP AC Charge Energy Total" + unit_of_measurement: "kWh" + device_class: energy + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Energy')['Total']['AC Charge Energy Total kWh'] }}" + - name: "GivTCP Invertor Energy Total" + unit_of_measurement: "kWh" + device_class: energy + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Energy')['Total']['Invertor Energy Total kWh'] }}" + - name: "GivTCP PV Energy Total" + unit_of_measurement: "kWh" + device_class: energy + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Energy')['Total']['PV Energy Total kWh'] }}" + - name: "GivTCP Load Energy Total" + unit_of_measurement: "kWh" + device_class: energy + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Energy')['Total']['Load Energy Total kWh'] }}" + - name: "GivTCP Battery Charge Energy Total" + unit_of_measurement: "kWh" + device_class: energy + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Energy')['Total']['Battery Charge Energy Total kWh'] }}" + - name: "GivTCP Battery Discharge Energy Total" + unit_of_measurement: "kWh" + device_class: energy + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Energy')['Total']['Battery Discharge Energy Total kWh'] }}" + - name: "GivTCP Self Consumption Energy Total" + unit_of_measurement: "kWh" + device_class: energy + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Energy')['Total']['Self Consumption Energy Total kWh'] }}" + + + - name: "GivTCP Import Energy Today" + unit_of_measurement: "kWh" + device_class: energy + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Energy')['Today']['Import Energy Today kWh'] }}" + - name: "GivTCP Export Energy Today" + unit_of_measurement: "kWh" + device_class: energy + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Energy')['Today']['Export Energy Today kWh'] }}" + - name: "GivTCP Battery Throughput Today" + unit_of_measurement: "kWh" + device_class: energy + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Energy')['Today']['Battery Throughput Today kWh'] }}" + - name: "GivTCP AC Charge Energy Today" + unit_of_measurement: "kWh" + device_class: energy + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Energy')['Today']['AC Charge Energy Today kWh'] }}" + - name: "GivTCP Invertor Energy Today" + unit_of_measurement: "kWh" + device_class: energy + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Energy')['Today']['Invertor Energy Today kWh'] }}" + - name: "GivTCP PV Energy Today" + unit_of_measurement: "kWh" + device_class: energy + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Energy')['Today']['PV Energy Today kWh'] }}" + - name: "GivTCP Load Energy Today" + unit_of_measurement: "kWh" + device_class: energy + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Energy')['Today']['Load Energy Today kWh'] }}" + - name: "GivTCP Battery Charge Energy Today" + unit_of_measurement: "kWh" + device_class: energy + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Energy')['Today']['Battery Charge Energy Today kWh'] }}" + - name: "GivTCP Battery Discharge Energy Today" + unit_of_measurement: "kWh" + device_class: energy + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Energy')['Today']['Battery Discharge Energy Today kWh'] }}" + - name: "GivTCP Self Consumption Energy Today" + unit_of_measurement: "kWh" + device_class: energy + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Energy')['Today']['Self Consumption Energy Today kWh'] }}" + + + - name: "GivTCP PV Power" + unit_of_measurement: "W" + device_class: power + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Power')['Power']['PV Power'] }}" + - name: "GivTCP Grid Power" + unit_of_measurement: "W" + device_class: power + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Power')['Power']['Grid Power'] }}" + - name: "GivTCP Import Power" + unit_of_measurement: "W" + device_class: power + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Power')['Power']['Import Power'] }}" + - name: "GivTCP Export Power" + unit_of_measurement: "W" + device_class: power + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Power')['Power']['Export Power'] }}" + - name: "GivTCP EPS Power" + unit_of_measurement: "W" + device_class: power + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Power')['Power']['EPS Power'] }}" + - name: "GivTCP Invertor Power" + unit_of_measurement: "W" + device_class: power + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Power')['Power']['Invertor Power'] }}" + - name: "GivTCP Load Power" + unit_of_measurement: "W" + device_class: power + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Power')['Power']['Load Power'] }}" + - name: "GivTCP Self Consumption Power" + unit_of_measurement: "W" + device_class: power + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Power')['Power']['Self Consumption Power'] }}" + - name: "GivTCP Battery Power" + unit_of_measurement: "W" + device_class: power + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Power')['Power']['Battery Power'] }}" + - name: "GivTCP Battery Charge Power" + unit_of_measurement: "W" + device_class: power + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Power')['Power']['Charge Power'] }}" + - name: "GivTCP Battery Discharge Power" + unit_of_measurement: "W" + device_class: power + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Power')['Power']['Discharge Power'] }}" + + - name: "GivTCP Battery SOC" + unit_of_measurement: "%" + device_class: battery + state_class: measurement + state: "{{ state_attr('sensor.givtcp','Power')['Power']['SOC'] }}" + + + - name: "GivTCP Control Mode" + state: "{{ state_attr('sensor.givtcp','Control')['Mode'] }}" + - name: "GivTCP Target Charge SOC" + state: "{{ state_attr('sensor.givtcp','Control')['Target SOC'] }}" + unit_of_measurement: "%" + device_class: battery + state_class: measurement + - name: "GivTCP Battery Charge Rate" + state: "{{ state_attr('sensor.givtcp','Control')['Battery Charge Rate'] }}" + unit_of_measurement: "%" + - name: "GivTCP Battery Discharge Rate" + state: "{{ state_attr('sensor.givtcp','Control')['Battery Discharge Rate'] }}" + unit_of_measurement: "%" + - name: "GivTCP Invertor Temperature" + state: "{{ state_attr('sensor.givtcp','Invertor Details')['Invertor Temperature'] }}" + device_class: temperature + unit_of_measurement: "°C" + + - name: "GivTCP Charge Slot 1 Start" + device_class: timestamp + state: "{{ strptime(state_attr('sensor.givtcp','Timeslots')['Charge start time slot 1'],'%H:%M:%S').replace(year=now().year,month=now().month,day=now().day).timestamp()|timestamp_local() }}" + icon: "mdi:calendar-clock" + - name: "GivTCP Charge Slot 1 End" + device_class: timestamp + state: "{{ strptime(state_attr('sensor.givtcp','Timeslots')['Charge end time slot 1'],'%H:%M:%S').replace(year=now().year,month=now().month,day=now().day).timestamp()|timestamp_local() }}" + icon: "mdi:calendar-clock" + - name: "GivTCP Charge Slot 2 Start" + device_class: timestamp + state: "{{ strptime(state_attr('sensor.givtcp','Timeslots')['Charge start time slot 2'],'%H:%M:%S').replace(year=now().year,month=now().month,day=now().day).timestamp()|timestamp_local() }}" + icon: "mdi:calendar-clock" + - name: "GivTCP Charge Slot 2 End" + device_class: timestamp + state: "{{ strptime(state_attr('sensor.givtcp','Timeslots')['Charge end time slot 2'],'%H:%M:%S').replace(year=now().year,month=now().month,day=now().day).timestamp()|timestamp_local() }}" + icon: "mdi:calendar-clock" + - name: "GivTCP Discharge Slot 1 Start" + device_class: timestamp + state: "{{ strptime(state_attr('sensor.givtcp','Timeslots')['Discharge start time slot 1'],'%H:%M:%S').replace(year=now().year,month=now().month,day=now().day).timestamp()|timestamp_local() }}" + icon: "mdi:calendar-clock" + - name: "GivTCP Discharge Slot 1 End" + device_class: timestamp + state: "{{ strptime(state_attr('sensor.givtcp','Timeslots')['Discharge end time slot 1'],'%H:%M:%S').replace(year=now().year,month=now().month,day=now().day).timestamp()|timestamp_local() }}" + icon: "mdi:calendar-clock" + - name: "GivTCP Discharge Slot 2 Start" + device_class: timestamp + state: "{{ strptime(state_attr('sensor.givtcp','Timeslots')['Discharge start time slot 2'],'%H:%M:%S').replace(year=now().year,month=now().month,day=now().day).timestamp()|timestamp_local() }}" + icon: "mdi:calendar-clock" + - name: "GivTCP Discharge Slot 2 End" + device_class: timestamp + state: "{{ strptime(state_attr('sensor.givtcp','Timeslots')['Discharge end time slot 2'],'%H:%M:%S').replace(year=now().year,month=now().month,day=now().day).timestamp()|timestamp_local() }}" + icon: "mdi:calendar-clock" + \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 5b173b7e..54f8ee90 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,7 @@ -Flask gunicorn paho-mqtt -givenergy-modbus==0.9.1 +givenergy-modbus scapy schedule -rx influxdb_client -requests -simplejson \ No newline at end of file +requests \ No newline at end of file diff --git a/src/GivJson.py b/src/GivJson.py index 3bea262e..7af5fff2 100644 --- a/src/GivJson.py +++ b/src/GivJson.py @@ -1,6 +1,25 @@ -# version 1.0 +# version 2022.01.31 import json import logging +from settings import GiV_Settings + +if GiV_Settings.log_level.lower()=="debug": + if GiV_Settings.Debug_File_Location=="": + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(filename=GiV_Settings.Debug_File_Location, encoding='utf-8', level=logging.DEBUG) +elif GiV_Settings.log_level.lower()=="info": + if GiV_Settings.Debug_File_Location=="": + logging.basicConfig(level=logging.INFO) + else: + logging.basicConfig(filename=GiV_Settings.Debug_File_Location, encoding='utf-8', level=logging.INFO) +else: + if GiV_Settings.Debug_File_Location=="": + logging.basicConfig(level=logging.ERROR) + else: + logging.basicConfig(filename=GiV_Settings.Debug_File_Location, encoding='utf-8', level=logging.ERROR) + +logger = logging.getLogger("GivTCP") class GivJSON(): diff --git a/src/influx.py b/src/influx.py index 03f78523..44db20a2 100644 --- a/src/influx.py +++ b/src/influx.py @@ -1,10 +1,26 @@ -# version 1.0 -import rx +# version 2022.01.31 from influxdb_client import InfluxDBClient, WriteApi, WriteOptions -from rx import operators as ops import logging from settings import GiV_Settings +if GiV_Settings.log_level.lower()=="debug": + if GiV_Settings.Debug_File_Location=="": + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(filename=GiV_Settings.Debug_File_Location, encoding='utf-8', level=logging.DEBUG) +elif GiV_Settings.log_level.lower()=="info": + if GiV_Settings.Debug_File_Location=="": + logging.basicConfig(level=logging.INFO) + else: + logging.basicConfig(filename=GiV_Settings.Debug_File_Location, encoding='utf-8', level=logging.INFO) +else: + if GiV_Settings.Debug_File_Location=="": + logging.basicConfig(level=logging.ERROR) + else: + logging.basicConfig(filename=GiV_Settings.Debug_File_Location, encoding='utf-8', level=logging.ERROR) + +logger = logging.getLogger("GivTCP") + class GivInflux(): def line_protocol(SN,readings): @@ -17,17 +33,20 @@ def make_influx_string(datastr): def publish(SN,data): output_str="" - power_output = data['Power'] + power_output = data['Power']['Power'] for key in power_output: logging.info("Creating Power string for InfluxDB") output_str=output_str+str(GivInflux.make_influx_string(key))+'='+str(power_output[key])+',' - - energy_today = data['Energy/Today'] + flow_output = data['Power']['Flows'] + for key in flow_output: + logging.info("Creating Power Flow string for InfluxDB") + output_str=output_str+str(GivInflux.make_influx_string(key))+'='+str(flow_output[key])+',' + energy_today = data['Energy']['Today'] for key in energy_today: logging.info("Creating Energy/Today string for InfluxDB") output_str=output_str+str(GivInflux.make_influx_string(key))+'='+str(energy_today[key])+',' - energy_total = data['Energy/Total'] + energy_total = data['Energy']['Total'] for key in energy_total: logging.info("Creating Energy/Total string for InfluxDB") output_str=output_str+str(GivInflux.make_influx_string(key))+'='+str(energy_total[key])+',' @@ -39,5 +58,5 @@ def publish(SN,data): _write_api = _db_client.write_api(write_options=WriteOptions(batch_size=1)) _write_api.write(bucket=GiV_Settings.influxBucket, record=data1) - write_api.close() - db_client.close() + _write_api.close() + _db_client.close() diff --git a/src/mqtt.py b/src/mqtt.py index 1d901423..6581c9a0 100644 --- a/src/mqtt.py +++ b/src/mqtt.py @@ -1,4 +1,5 @@ -# version 1.0 +# version 2022.01.21 +from logging import Logger import paho.mqtt.client as mqtt import time import datetime @@ -7,6 +8,24 @@ from settings import GiV_Settings from givenergy_modbus.model.inverter import Model +if GiV_Settings.log_level.lower()=="debug": + if GiV_Settings.Debug_File_Location=="": + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(filename=GiV_Settings.Debug_File_Location, encoding='utf-8', level=logging.DEBUG) +elif GiV_Settings.log_level.lower()=="info": + if GiV_Settings.Debug_File_Location=="": + logging.basicConfig(level=logging.INFO) + else: + logging.basicConfig(filename=GiV_Settings.Debug_File_Location, encoding='utf-8', level=logging.INFO) +else: + if GiV_Settings.Debug_File_Location=="": + logging.basicConfig(level=logging.ERROR) + else: + logging.basicConfig(filename=GiV_Settings.Debug_File_Location, encoding='utf-8', level=logging.ERROR) + +logger = logging.getLogger("GivTCP") + class GivMQTT(): if GiV_Settings.MQTT_Port=='': @@ -24,10 +43,10 @@ class GivMQTT(): def on_connect(client, userdata, flags, rc): if rc==0: client.connected_flag=True #set flag - logging.info("connected OK Returned code="+str(rc)) + logger.info("connected OK Returned code="+str(rc)) #client.subscribe(topic) else: - logging.info("Bad connection Returned code= "+str(rc)) + logger.info("Bad connection Returned code= "+str(rc)) def multi_MQTT_publish(rootTopic,array): #Recieve multiple payloads with Topics and publish in a single MQTT connection mqtt.Client.connected_flag=False #create flag in class @@ -37,14 +56,14 @@ def multi_MQTT_publish(rootTopic,array): #Recieve multiple payloads with Topic client.username_pw_set(GivMQTT.MQTT_Username,GivMQTT.MQTT_Password) client.on_connect=GivMQTT.on_connect #bind call back function client.loop_start() - logging.info ("Connecting to broker: "+ GivMQTT.MQTT_Address) + logger.info ("Connecting to broker: "+ GivMQTT.MQTT_Address) client.connect(GivMQTT.MQTT_Address,port=GivMQTT.MQTT_Port) while not client.connected_flag: #wait in loop - logging.info ("In wait loop") + logger.info ("In wait loop") time.sleep(0.2) for p_load in array: payload=array[p_load] - logging.info('Publishing: '+rootTopic+p_load) + logger.info('Publishing: '+rootTopic+p_load) output=GivMQTT.iterate_dict(payload,rootTopic+p_load) #create LUT for MQTT publishing for value in output: client.publish(value,output[value]) @@ -59,7 +78,7 @@ def iterate_dict(array,topic): #Create LUT of topics and datapoints output=array[p_load] if isinstance(output, dict): MQTT_LUT.update(GivMQTT.iterate_dict(output,topic+"/"+p_load)) - logging.info('Prepping '+p_load+" for publishing") + logger.info('Prepping '+p_load+" for publishing") else: MQTT_LUT[topic+"/"+p_load]=output return(MQTT_LUT) \ No newline at end of file diff --git a/src/read.py b/src/read.py index 77bae645..a08e8031 100644 --- a/src/read.py +++ b/src/read.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# version 2021.01.13 +# version 2022.01.31 import array import sys import json @@ -15,17 +15,24 @@ if GiV_Settings.Print_Raw_Registers.lower()=="true": Print_Raw=True -if GiV_Settings.debug.lower()=="true": +if GiV_Settings.log_level.lower()=="debug": if GiV_Settings.Debug_File_Location=="": logging.basicConfig(level=logging.DEBUG) else: logging.basicConfig(filename=GiV_Settings.Debug_File_Location, encoding='utf-8', level=logging.DEBUG) -else: +elif GiV_Settings.log_level.lower()=="info": if GiV_Settings.Debug_File_Location=="": logging.basicConfig(level=logging.INFO) else: logging.basicConfig(filename=GiV_Settings.Debug_File_Location, encoding='utf-8', level=logging.INFO) - +else: + if GiV_Settings.Debug_File_Location=="": + logging.basicConfig(level=logging.ERROR) + else: + logging.basicConfig(filename=GiV_Settings.Debug_File_Location, encoding='utf-8', level=logging.ERROR) + +logger = logging.getLogger("GivTCP") + def runAll(): energy_total_output={} energy_today_output={} @@ -36,7 +43,7 @@ def runAll(): batteries = {} multi_output={} temp={} - logging.info("----------------------------Starting----------------------------") + logger.info("----------------------------Starting----------------------------") logging.info("Getting All Registers") #Connect to Invertor and load data @@ -50,7 +57,7 @@ def runAll(): try: numBatteries=int(GiV_Settings.numBatteries) except ValueError: - logging.error("error parsing numbatteries defaulting to 1") + logger.error("error parsing numbatteries defaulting to 1") for x in range(0, numBatteries): BatRegCache = RegisterCache() @@ -58,10 +65,10 @@ def runAll(): GEBat=Battery.from_orm(BatRegCache) batteries[GEBat.battery_serial_number]=GEBat.dict() - logging.info("Invertor connection successful, registers retrieved") + logger.info("Invertor connection successful, registers retrieved") except: e = sys.exc_info() - logging.error("Error collecting registers: " + str(e)) + logger.error("Error collecting registers: " + str(e)) temp['result']="Error collecting registers: " + str(e) return json.dumps(temp) @@ -74,39 +81,38 @@ def runAll(): try: #Total Energy Figures - logging.info("Getting Total Energy Data") + logger.info("Getting Total Energy Data") energy_total_output['Export Energy Total kWh']=GEInv.e_grid_out_total - energy_total_output['Battery Throughput Total kWh']=GEInv.e_battery_discharge_total_2 + energy_total_output['Battery Throughput Total kWh']=GEInv.e_battery_throughput_total energy_total_output['AC Charge Energy Total kWh']=GEInv.e_inverter_in_total energy_total_output['Import Energy Total kWh']=GEInv.e_grid_in_total energy_total_output['Invertor Energy Total kWh']=GEInv.e_inverter_out_total - energy_total_output['PV Energy Total kWh']=GEInv.p_pv_total_generating_capacity #CHECK-CHECK + energy_total_output['PV Energy Total kWh']=GEInv.e_pv_total if GEInv.inverter_model==Model.Hybrid: energy_total_output['Load Energy Total kWh']=(energy_total_output['Invertor Energy Total kWh']-energy_total_output['AC Charge Energy Total kWh'])-(energy_total_output['Export Energy Total kWh']-energy_total_output['Import Energy Total kWh']) - if GEInv.arm_firmware_version>=449: #Only add in battery totals if firmware is high enough - energy_total_output['Battery Charge Energy Total kWh']=GEInv.e_battery_charge_total - energy_total_output['Battery Discharge Energy Total kWh']=GEInv.e_battery_discharge_total else: energy_total_output['Load Energy Total kWh']=(energy_total_output['Invertor Energy Total kWh']-energy_total_output['AC Charge Energy Total kWh'])-(energy_total_output['Export Energy Total kWh']-energy_total_output['Import Energy Total kWh'])+energy_total_output['PV Energy Total kWh'] - if GEInv.arm_firmware_version>=553: #Only add in battery totals if firmware is high enough - energy_total_output['Battery Charge Energy Total kWh']=GEInv.e_battery_charge_total - energy_total_output['Battery Discharge Energy Total kWh']=GEInv.e_battery_discharge_total - + if GEInv.e_battery_charge_total==0 and GEInv.e_battery_discharge_total==0: #If no values in "nomal" registers then grab from battery + energy_total_output['Battery Charge Energy Total kWh']=GEBat.e_battery_charge_total_2 + energy_total_output['Battery Discharge Energy Total kWh']=GEBat.e_battery_discharge_total_2 + else: + energy_total_output['Battery Charge Energy Total kWh']=GEInv.e_battery_charge_total + energy_total_output['Battery Discharge Energy Total kWh']=GEInv.e_battery_discharge_total + energy_total_output['Self Consumption Energy Total kWh']=energy_total_output['PV Energy Total kWh']-energy_total_output['Export Energy Total kWh'] #Energy Today Figures - logging.info("Getting Today Energy Data") + logger.info("Getting Today Energy Data") energy_today_output['Battery Throughput Today kWh']=GEInv.e_battery_charge_day+GEInv.e_battery_discharge_day energy_today_output['PV Energy Today kWh']=GEInv.e_pv1_day+GEInv.e_pv2_day energy_today_output['Import Energy Today kWh']=GEInv.e_grid_in_day energy_today_output['Export Energy Today kWh']=GEInv.e_grid_out_day energy_today_output['AC Charge Energy Today kWh']=GEInv.e_inverter_in_day - energy_today_output['Invertor Energy Today kWh']=GEInv.e_inverter_out_total + energy_today_output['Invertor Energy Today kWh']=GEInv.e_inverter_out_day energy_today_output['Battery Charge Energy Today kWh']=GEInv.e_battery_charge_day energy_today_output['Battery Discharge Energy Today kWh']=GEInv.e_battery_discharge_day - energy_today_output['Import for Load Energy Today kWh']=GEInv.e_grid_in_day - GEInv.e_inverter_in_day energy_today_output['Self Consumption Energy Today kWh']=energy_today_output['PV Energy Today kWh']-energy_today_output['Export Energy Today kWh'] if GEInv.inverter_model==Model.Hybrid: @@ -118,7 +124,7 @@ def runAll(): ############ Core Power Stats ############ #PV Power - logging.info("Getting PV Power") + logger.info("Getting PV Power") PV_power_1=GEInv.p_pv1 PV_power_2=GEInv.p_pv2 PV_power=PV_power_1+PV_power_2 @@ -128,7 +134,7 @@ def runAll(): power_output['PV Power']= PV_power #Grid Power - logging.info("Getting Grid Power") + logger.info("Getting Grid Power") grid_power= GEInv.p_grid_out if grid_power<0: import_power=abs(grid_power) @@ -144,11 +150,11 @@ def runAll(): power_output['Export Power']=export_power #EPS Power - logging.info("Getting EPS Power") + logger.info("Getting EPS Power") power_output['EPS Power']= GEInv.p_eps_backup #Invertor Power - logging.info("Getting PInv Power") + logger.info("Getting PInv Power") Invertor_power=GEInv.p_inverter_out if -6000 <= Invertor_power <= 6000: power_output['Invertor Power']= Invertor_power @@ -156,13 +162,13 @@ def runAll(): power_output['AC Charge Power']= abs(Invertor_power) #Load Power - logging.info("Getting Load Power") + logger.info("Getting Load Power") Load_power=GEInv.p_load_demand if Load_power<15500: power_output['Load Power']=Load_power #Self Consumption - logging.info("Getting Self Consumption Power") + logger.info("Getting Self Consumption Power") power_output['Self Consumption Power']=max(Load_power - import_power,0) #Battery Power @@ -178,13 +184,13 @@ def runAll(): power_output['Discharge Power']=discharge_power #SOC - logging.info("Getting SOC") + logger.info("Getting SOC") power_output['SOC']=GEInv.battery_percent ############ Power Flow Stats ############ #Solar to H/B/G - logging.info("Getting Solar to H/B/G Power Flows") + logger.info("Getting Solar to H/B/G Power Flows") if PV_power>0: S2H=min(PV_power,Load_power) power_flow_output['Solar to House']=S2H @@ -198,12 +204,12 @@ def runAll(): power_flow_output['Solar to Grid']=0 #Battery to House - logging.info("Getting Battery to House Power Flow") + logger.info("Getting Battery to House Power Flow") B2H=max(discharge_power-export_power,0) power_flow_output['Battery to House']=B2H #Grid to Battery/House Power - logging.info("Getting Grid to Battery/House Power Flow") + logger.info("Getting Grid to Battery/House Power Flow") if import_power>0: power_flow_output['Grid to Battery']=charge_power-max(PV_power-Load_power,0) power_flow_output['Grid to House']=max(import_power-charge_power,0) @@ -213,7 +219,7 @@ def runAll(): power_flow_output['Grid to House']=0 #Battery to Grid Power - logging.info("Getting Battery to Grid Power Flow") + logger.info("Getting Battery to Grid Power Flow") if export_power>0: power_flow_output['Battery to Grid']=max(discharge_power-B2H,0) else: @@ -234,7 +240,7 @@ def runAll(): multi_output["Invertor Details"]=invertor ################ Run Holding Reg now ################### - logging.info("Getting mode control figures") + logger.info("Getting mode control figures") # Get Control Mode registers shallow_charge=GEInv.battery_soc_reserve self_consumption=GEInv.battery_power_mode @@ -252,7 +258,7 @@ def runAll(): discharge_enable="Active" else: discharge_enable="Paused" - logging.info("Shallow Charge= "+str(shallow_charge)+" Self Consumption= "+str(self_consumption)+" Discharge Enable= "+str(discharge_enable)) + logger.info("Shallow Charge= "+str(shallow_charge)+" Self Consumption= "+str(self_consumption)+" Discharge Enable= "+str(discharge_enable)) #Get Charge/Discharge Active status discharge_state=GEInv.battery_discharge_limit @@ -272,9 +278,9 @@ def runAll(): #Calculate Mode - logging.info("Calculating Mode...") + logger.info("Calculating Mode...") mode=GEInv.system_mode - logging.info("Mode is: " + str(mode)) + logger.info("Mode is: " + str(mode)) controlmode['Mode']=mode controlmode['Battery Power Reserve']=battery_reserve @@ -288,7 +294,7 @@ def runAll(): #Grab Timeslots timeslots={} - logging.info("Getting TimeSlot data") + logger.info("Getting TimeSlot data") timeslots['Discharge start time slot 1']=GEInv.discharge_slot_1[0] timeslots['Discharge end time slot 1']=GEInv.discharge_slot_1[1] timeslots['Discharge start time slot 2']=GEInv.discharge_slot_2[0] @@ -300,7 +306,7 @@ def runAll(): #Get Invertor Details invertor={} - logging.info("Getting Invertor Details") + logger.info("Getting Invertor Details") if GEInv.battery_type==1: batterytype="Lithium" if GEInv.battery_type==0: batterytype="Lead Acid" invertor['Battery Type']=batterytype @@ -317,9 +323,9 @@ def runAll(): #Get Battery Details battery={} batteries2={} - logging.info("Getting Battery Details") + logger.info("Getting Battery Details") for b in batteries: - logging.info("Building battery output: "+b) + logger.info("Building battery output: "+b) battery={} battery['Battery Serial Number']=batteries[b]['battery_serial_number'] battery['Battery SOC']=batteries[b]['battery_soc'] @@ -364,28 +370,32 @@ def runAll(): except: e = sys.exc_info() - logging.error("Error processing registers: " + str(e)) + logger.error("Error processing registers: " + str(e)) temp['result']="Error processing registers: " + str(e) return json.dumps(temp) return json.dumps(multi_output, indent=4, sort_keys=True, default=str) +####### Addiitonal Publish options can be added here. +####### A seperate file in the folder can be added with a new publish "plugin" +####### then referenced here with any settings required added into settings.py + def publishOutput(array,SN): tempoutput={} tempoutput=iterate_dict(array) if GiV_Settings.MQTT_Output.lower()=="true": from mqtt import GivMQTT - logging.info("Publish all to MQTT") + logger.info("Publish all to MQTT") if GiV_Settings.MQTT_Topic=="": GiV_Settings.MQTT_Topic="GivEnergy" GivMQTT.multi_MQTT_publish(str(GiV_Settings.MQTT_Topic+"/"+SN+"/"), tempoutput) if GiV_Settings.JSON_Output.lower()=="true": from GivJson import GivJSON - logging.info("Pushing JSON output") + logger.info("Pushing JSON output") GivJSON.output_JSON(tempoutput) if GiV_Settings.Influx_Output.lower()=="true": from influx import GivInflux - logging.info("Pushing output to Influx") + logger.info("Pushing output to Influx") GivInflux.publish(SN,tempoutput) def iterate_dict(array): # Create a publish safe version of the output (convert non string or int datapoints) @@ -395,25 +405,25 @@ def iterate_dict(array): # Create a publish safe version of the output (c if isinstance(output, dict): temp=iterate_dict(output) safeoutput[p_load]=temp - logging.info('Dealt with '+p_load) + logger.info('Dealt with '+p_load) elif isinstance(output, tuple): if "slot" in str(p_load): - logging.info('Converting Timeslots to publish safe string') + logger.info('Converting Timeslots to publish safe string') safeoutput[p_load+"_start"]=output[0].strftime("%H%M") safeoutput[p_load+"_end"]=output[1].strftime("%H%M") else: #Deal with other tuples _ Print each value for index, key in enumerate(output): - logging.info('Converting Tuple to multiple publish safe strings') + logger.info('Converting Tuple to multiple publish safe strings') safeoutput[p_load+"_"+str(index)]=str(key) elif isinstance(output, datetime.datetime): - logging.info('Converting datetime to publish safe string') + logger.info('Converting datetime to publish safe string') safeoutput[p_load]=output.strftime("%d-%m-%Y %H:%M:%S") elif isinstance(output, datetime.time): - logging.info('Converting time to publish safe string') + logger.info('Converting time to publish safe string') safeoutput[p_load]=output.strftime("%H:%M") elif isinstance(output, Model): - logging.info('Converting time to publish safe string') + logger.info('Converting time to publish safe string') safeoutput[p_load]=output.name elif isinstance(output, float): safeoutput[p_load]=round(output,2) diff --git a/src/settings_template.py b/src/settings_template.py index ab3e11cb..579dadc2 100644 --- a/src/settings_template.py +++ b/src/settings_template.py @@ -1,27 +1,23 @@ -# version 1.0 +# version 2022.01.31 class GiV_Settings: - invertorIP="" #Required - IP address of Invertor on local network + invertorIP="" #Required - IP address of Invertor on local network numBatteries="" +#Debug Settings + Log_Level="True" #Optional - Enables verbose debug "True" or "False". + Debug_File_Location="" #Optional - Location of logs (Default is console) + Print_Raw_Registers="False" #Optional - "True" prints all raw registers to the MQTT broker #MQTT Output Settings - MQTT_Output="" #True or False - MQTT_Address="" #IP address of MQTT broker (local or remote) - MQTT_Username="" #Optional - Username for MQTT broker - MQTT_Password="" #Optional - Password for MQTT broker - MQTT_Topic="" #Optional - Root topic for all MQTT messages. Defaults to "GivEnergy/ + MQTT_Output="" #True or False + MQTT_Address="" #IP address of MQTT broker (local or remote) + MQTT_Username="" #Optional - Username for MQTT broker + MQTT_Password="" #Optional - Password for MQTT broker + MQTT_Topic="" #Optional - Root topic for all MQTT messages. Defaults to "GivEnergy/ MQTT_Port="" -#Debug Settings - debug="True" #Optional - Enables verbose debug "True" or "False". - Debug_File_Location="" #Optional - Location of logs (Default is console) - Print_Raw_Registers="False" #Optional - "True" prints all raw registers to the MQTT broker #Influx Settings - Only works with Influx v2 - Influx_Output="False" #True or False + Influx_Output="False" #True or False influxURL="http://localhost:8086" influxToken="" influxBucket="GivEnergy" influxOrg="GivTCP" #JSON Settings - JSON_Output="False" #True or False -#Home Assistant Settings - HA_Output="False" #True or False - HA_url="http://IPADDRESS" - HA_token = "" #Create a long lasting Token in Homeassistant and paste here \ No newline at end of file + JSON_Output="False" #True or False \ No newline at end of file diff --git a/src/startup.sh b/src/startup.sh index 1fe8f893..75da6fd8 100644 --- a/src/startup.sh +++ b/src/startup.sh @@ -42,16 +42,13 @@ else printf " MQTT_Topic=\"$MQTT_TOPIC\"\n" >> settings.py printf " MQTT_Port=\"$MQTT_PORT\"\n" >> settings.py printf " JSON_Output=\"$JSON_OUTPUT\"\n" >> settings.py - printf " debug=\"$DEBUG\"\n" >> settings.py + printf " Log_Level=\"$LOG_LEVEL\"\n" >> settings.py printf " Debug_File_Location=\"$DEBUG_FILE_LOCATION\"\n" >> settings.py printf " Influx_Output=\"$INFLUX_OUTPUT\"\n" >> settings.py printf " influxURL=\"$INFLUX_URL\"\n" >> settings.py printf " influxToken=\"$INFLUX_TOKEN\"\n" >> settings.py printf " influxBucket=\"$INFLUX_BUCKET\"\n" >> settings.py printf " influxOrg=\"$INFLUX_ORG\"\n" >> settings.py - printf " HA_Output=\"$HA_OUTPUT\"\n" >> settings.py - printf " HA_url=\"$HA_URL\"\n" >> settings.py - printf " HA_token=\"$HA_TOKEN\"\n" >> settings.py fi if [ "$MQTT_ADDRESS" = "127.0.0.1" ] #Only run Mosquitto if its using local broker diff --git a/src/write.py b/src/write.py index 7fa93475..bed93468 100644 --- a/src/write.py +++ b/src/write.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# version 2022.01.14 +# version 2022.01.31 import sys import json import logging @@ -7,16 +7,24 @@ from settings import GiV_Settings from givenergy_modbus.client import GivEnergyClient -if GiV_Settings.debug.lower()=="true": +if GiV_Settings.log_level.lower()=="debug": if GiV_Settings.Debug_File_Location=="": logging.basicConfig(level=logging.DEBUG) else: logging.basicConfig(filename=GiV_Settings.Debug_File_Location, encoding='utf-8', level=logging.DEBUG) -else: +elif GiV_Settings.log_level.lower()=="info": if GiV_Settings.Debug_File_Location=="": logging.basicConfig(level=logging.INFO) else: logging.basicConfig(filename=GiV_Settings.Debug_File_Location, encoding='utf-8', level=logging.INFO) +else: + if GiV_Settings.Debug_File_Location=="": + logging.basicConfig(level=logging.ERROR) + else: + logging.basicConfig(filename=GiV_Settings.Debug_File_Location, encoding='utf-8', level=logging.ERROR) + +logger = logging.getLogger("GivTCP") + def disableChargeTarget(): temp={} @@ -26,7 +34,7 @@ def disableChargeTarget(): except: e = sys.exc_info() temp['result']="Disabling Charge Target failed: " + str(e) - logging.info (temp['result']) + logger.info (temp['result']) return json.dumps(temp) def enableChargeTarget(): @@ -37,7 +45,7 @@ def enableChargeTarget(): except: e = sys.exc_info() temp['result']="Enabling Charge Target failed: " + str(e) - logging.info (temp['result']) + logger.info (temp['result']) return json.dumps(temp) def pauseChargeSchedule(): @@ -48,7 +56,7 @@ def pauseChargeSchedule(): except: e = sys.exc_info() temp['result']="Pausing Charge Schedule failed: " + str(e) - logging.info (temp['result']) + logger.info (temp['result']) return json.dumps(temp) def resumeChargeSchedule(): @@ -59,7 +67,7 @@ def resumeChargeSchedule(): except: e = sys.exc_info() temp['result']="Resuming Charge Schedule failed: " + str(e) - logging.info (temp['result']) + logger.info (temp['result']) return json.dumps(temp) def pauseDischargeSchedule(): @@ -70,7 +78,7 @@ def pauseDischargeSchedule(): except: e = sys.exc_info() temp['result']="Pausing Discharge Schedule failed: " + str(e) - logging.info (temp['result']) + logger.info (temp['result']) return json.dumps(temp) def resumeDischargeSchedule(): @@ -81,7 +89,7 @@ def resumeDischargeSchedule(): except: e = sys.exc_info() temp['result']="Resuming Discharge Schedule failed: " + str(e) - logging.info (temp['result']) + logger.info (temp['result']) return json.dumps(temp) def pauseBatteryCharge(): @@ -92,7 +100,7 @@ def pauseBatteryCharge(): except: e = sys.exc_info() temp['result']="Pausing Charge failed: " + str(e) - logging.info (temp['result']) + logger.info (temp['result']) return json.dumps(temp) def resumeBatteryCharge(): @@ -103,7 +111,7 @@ def resumeBatteryCharge(): except: e = sys.exc_info() temp['result']="Resuming Charge failed: " + str(e) - logging.info (temp['result']) + logger.info (temp['result']) return json.dumps(temp) def pauseBatteryDischarge(): @@ -114,7 +122,7 @@ def pauseBatteryDischarge(): except: e = sys.exc_info() temp['result']="Pausing Discharge failed: " + str(e) - logging.info (temp['result']) + logger.info (temp['result']) return json.dumps(temp) def resumeBatteryDischarge(): @@ -125,7 +133,7 @@ def resumeBatteryDischarge(): except: e = sys.exc_info() temp['result']="Resuming Discharge failed: " + str(e) - logging.info (temp['result']) + logger.info (temp['result']) return json.dumps(temp) def setChargeTarget(payload): @@ -139,7 +147,7 @@ def setChargeTarget(payload): except: e = sys.exc_info() temp['result']="Setting Charge Target failed: " + str(e) - logging.info (temp['result']) + logger.info (temp['result']) return json.dumps(temp) def setBatteryReserve(payload): @@ -148,14 +156,14 @@ def setBatteryReserve(payload): target=int(payload['dischargeToPercent']) #Only allow minimum of 4% if target<4: target=4 - logging.info ("Setting battery reserve target to: " + str(target)) + logger.info ("Setting battery reserve target to: " + str(target)) try: GivEnergyClient(host=GiV_Settings.invertorIP).set_battery_power_reserve(target) temp['result']="Setting Battery Reserve was a success" except: e = sys.exc_info() temp['result']="Setting Battery Reserve failed: " + str(e) - logging.info (temp['result']) + logger.info (temp['result']) return json.dumps(temp) def setChargeRate(payload): @@ -166,14 +174,14 @@ def setChargeRate(payload): target=50 else: target=int(int(payload['chargeRate'])/3) - logging.info ("Setting battery charge rate to: " + str(target)) + logger.info ("Setting battery charge rate to: " + str(target)) try: GivEnergyClient(host=GiV_Settings.invertorIP).set_battery_charge_limit(target) temp['result']="Setting Charge Rate was a success" except: e = sys.exc_info() temp['result']="Setting Charge Rate failed: " + str(e) - logging.info (temp['result']) + logger.info (temp['result']) return json.dumps(temp) @@ -185,14 +193,14 @@ def setDischargeRate(payload): target=50 else: target=int(int(payload['dischargeRate'])/3) - logging.info ("Setting battery discharge rate to: " + str(target)) + logger.info ("Setting battery discharge rate to: " + str(target)) try: GivEnergyClient(host=GiV_Settings.invertorIP).set_battery_discharge_limit(target) temp['result']="Setting Discharge Rate was a success" except: e = sys.exc_info() temp['result']="Setting Discharge Rate failed: " + str(e) - logging.info (temp['result']) + logger.info (temp['result']) return json.dumps(temp) @@ -210,7 +218,7 @@ def setChargeSlot1(payload): except: e = sys.exc_info() temp['result']="Setting Charge Slot 1 failed: " + str(e) - logging.info (temp['result']) + logger.info (temp['result']) return json.dumps(temp) def setChargeSlot2(payload): @@ -227,7 +235,7 @@ def setChargeSlot2(payload): except: e = sys.exc_info() temp['result']="Setting Charge Slot 2 failed: " + str(e) - logging.info (temp['result']) + logger.info (temp['result']) return json.dumps(temp) def setDischargeSlot1(payload): @@ -244,7 +252,7 @@ def setDischargeSlot1(payload): except: e = sys.exc_info() temp['result']="Setting Discharge Slot 1 failed: " + str(e) - logging.info (temp['result']) + logger.info (temp['result']) return json.dumps(temp) def setDischargeSlot2(payload): @@ -261,7 +269,7 @@ def setDischargeSlot2(payload): except: e = sys.exc_info() temp['result']="Setting Discharge Slot 2 failed: " + str(e) - logging.info (temp['result']) + logger.info (temp['result']) return json.dumps(temp) @@ -279,14 +287,14 @@ def setBatteryMode(payload): elif mode==4: client=GivEnergyClient(host=GiV_Settings.invertorIP).set_mode_storage(export=True) else: - logging.info ("Invalid Mode requested: "+ mode) + logger.info ("Invalid Mode requested: "+ mode) temp['result']="Invalid Mode requested" return json.dumps(temp) temp['result']="Setting Battery Mode was a success" except: e = sys.exc_info() temp['result']="Setting Battery Mode failed: " + str(e) - logging.info (temp['result']) + logger.info (temp['result']) return json.dumps(temp) def setDateTime(payload): @@ -302,7 +310,7 @@ def setDateTime(payload): except: e = sys.exc_info() temp['result']="Setting Battery Mode failed: " + str(e) - logging.info (temp['result']) + logger.info (temp['result']) return json.dumps(temp) if __name__ == '__main__':