├── .gitignore ├── LICENSE ├── README.md ├── battery-connector.py ├── battery-controller.py ├── config.py ├── doc ├── IoT_OnOff_Fronius.jpeg ├── batt-controller-algo.md ├── forecast.md ├── growatt-example.json └── setup.md ├── energymeter-connector.py ├── forecastsolar-connector.py ├── fronius-connector.py ├── goecharger-connector.py ├── heater-connector.py ├── influxdb-connector.py ├── local-dht-connector.py ├── local-gas-connector.py ├── local-gas-connector.state ├── mqtt-connectors.code-workspace ├── mqtt-connectors.sublime-project ├── netatmo-connector.py ├── requirements.txt ├── secrets.py ├── supervisor ├── battery-controller.conf ├── battery.conf ├── energymeter.conf ├── fronius.conf ├── goecharger.conf ├── heater.conf ├── influxdb.conf ├── local-dht.conf ├── local-gas.conf ├── netatmo.conf ├── tankstellen.conf ├── vcontrold.conf └── zoe.conf ├── tankstellen-connector.py ├── vcontrold-connector.py ├── vcontrold-connector.tmpl ├── weathermath.py └── zoe-connector.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | *.sublime-workspace 103 | /credentials_token.json 104 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Andreas Kleber 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fronius-mqtt-bridge 2 | 3 | Python scripts 4 | * to fetch data from various APIs like power flow realtime data from a fronius data manager and pubish it to a mqtt broker. 5 | * for implementing controll-algorithms based on and setting via mqtt topics 6 | 7 | I use it with [this setup](doc/setup.md). 8 | 9 | # Requirements 10 | * Tested with Python 3.5 on raspian 11 | 12 | # Usage 13 | Create, activate and setup venv: 14 | 15 | ``` 16 | cd fronius-mqtt-bridge 17 | python3 -m venv ./venv 18 | source venv/bin/activate 19 | pip install -r requirements.txt 20 | ``` 21 | 22 | Then set the right hostnames or ip addresss and ports in all python files that will be used and run for example: 23 | 24 | ``` 25 | python3 fronius-connector.py 26 | ``` 27 | 28 | The scripts will run in an infinite loop until an error happens or they are aborted via Ctrl+C. 29 | 30 | # Acknowledgement 31 | * [Jan-Piet Mens](https://jpmens.net/2013/03/10/visualizing-energy-consumption-with-mqtt/) for the inspiration for the script. 32 | * Juraj's [battsett.jar](https://github.com/jandrassy/battsett) 33 | * [InfluxDB Connector](https://thingsmatic.com/2017/03/02/influxdb-and-grafana-for-sensor-time-series/) 34 | 35 | # ToDo 36 | 37 | * One mainloop https://stackoverflow.com/a/49801719/5523503 38 | * pubish only if a client is connected to the broker 39 | * document my setup 40 | * add connector for Zoe battery status 41 | * improve battery-controller strategy 42 | ** take sunshine duration prognosis into accoount 43 | * finish config.py 44 | * extend goe-charger with controlling capabilities 45 | * add goecharger-controller to set charging power based on available pv energy. 46 | * allow charging if soc < 7% 47 | * Status for battery-controller 48 | * add Forecast for tomorrow 49 | * connector for rpi temp sensor 50 | 51 | -------------------------------------------------------------------------------- /battery-connector.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Fetches and sets the fronius battery charging 4 | power in % via modbus. 5 | Inspired by Juraj's battsett.jar.""" 6 | 7 | import paho.mqtt.client as paho # pip install paho-mqtt 8 | import time 9 | import logging 10 | import sys 11 | from config import * 12 | 13 | from pymodbus.constants import Endian 14 | from pymodbus.payload import BinaryPayloadDecoder 15 | from pymodbus.client.sync import ModbusTcpClient as ModbusClient 16 | 17 | 18 | def battery_data(): 19 | 20 | values = {} 21 | 22 | # Datentypen der Register "float32","uint64","uint16" 23 | # 'string': decoder.decode_string(8), 24 | # 'float': decoder.decode_32bit_float(), 25 | # '16uint': decoder.decode_16bit_uint(), 26 | # '8int': decoder.decode_8bit_int(), 27 | # 'bits': decoder.decode_bits(), 28 | 29 | client = ModbusClient(MODBUS_HOST, port=502) 30 | client.connect() 31 | 32 | r = client.read_holding_registers(40327 - 1, 2, unit=1) 33 | inWRte = BinaryPayloadDecoder.fromRegisters(r.registers, byteorder=Endian.Big) # noqa E501 34 | 35 | values['chg_pct'] = inWRte.decode_16bit_uint() / 100 36 | logging.debug("From modbus chg_pct {}%".format(values['chg_pct'])) 37 | 38 | client.close() 39 | 40 | return values 41 | 42 | 43 | def on_message(mqttc, obj, msg): 44 | if msg.topic == "{}/set/chg_pct".format(BATTERY_MQTT_PREFIX): 45 | newValue = int(msg.payload) * 100 46 | client = ModbusClient(MODBUS_HOST, port=502) 47 | client.connect() 48 | client.write_register(40327 - 1, newValue, unit=1) 49 | client.close() 50 | 51 | logging.debug("Setting via modbus chg_pct: {}".format(newValue)) # noqa E501 52 | 53 | update() 54 | 55 | 56 | def update(): 57 | values = battery_data() 58 | for k, v in values.items(): 59 | (result, mid) = mqttc.publish("{}/{}".format(BATTERY_MQTT_PREFIX, k), str(v), 0, retain=True) # noqa E501 60 | logging.info("Pubish Result: {} MID: {} for {}: {}".format(result, mid, k, v)) # noqa E501 61 | 62 | 63 | if __name__ == '__main__': 64 | logging.basicConfig(stream=sys.stdout, 65 | format='%(asctime)s %(levelname)-8s %(message)s', 66 | datefmt='%Y-%m-%d %H:%M:%S', 67 | level=logging.INFO) 68 | 69 | mqttc = paho.Client('battery-connector', clean_session=True) 70 | # mqttc.enable_logger() 71 | mqttc.will_set("{}/connectorstatus".format(BATTERY_MQTT_PREFIX), "Battery Connector: LOST_CONNECTION", 0, retain=True) # noqa E501 72 | 73 | mqttc.connect(BROKER_HOST, BROKER_PORT, 60) 74 | logging.info("Connected to {}:{}".format(BROKER_HOST, BROKER_PORT)) 75 | 76 | mqttc.publish("{}/connectorstatus".format(BATTERY_MQTT_PREFIX), "Battery Connector: ON-LINE", retain=True) # noqa E501 77 | 78 | mqttc.on_message = on_message 79 | mqttc.subscribe("{}/set/chg_pct".format(BATTERY_MQTT_PREFIX), 0) 80 | 81 | mqttc.loop_forever() 82 | -------------------------------------------------------------------------------- /battery-controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Controlls the charging power of a fronius battery to achieve peak shaving. 5 | My fronius symo hybrid 3-0.3 is capapble of 5kW input, but only 6 | of 3kW AC output. My 4kWp Generator is sometimes able to generate more than 7 | 3kW and the fronius is in this case able to charge the battery with the power 8 | above 3kW AC. 9 | The fronius is not able to control how fast the battery is charged, but only 10 | allows to set a time when charging should be started. But then the battery is 11 | charged with full power. 12 | My small battery however is then full in about 1.5h. So by limitting the 13 | charging power I can achieve peak shaving for a longer time. 14 | The control strategy below sets the charging power to the power exceeding 3kW. 15 | With respect to a future goecharger-controller, this controller here only 16 | handles the power above 3kW AC. The goecharger-controller handles everything 17 | up to 3kW AC. So they both do not interfere. 18 | """ 19 | 20 | import paho.mqtt.client as paho # pip install paho-mqtt 21 | import time 22 | import logging 23 | import sys 24 | import datetime 25 | import math 26 | from config import * 27 | 28 | 29 | FREQUENCY = 300 30 | MAX_CHG_P = 2500 31 | MAX_AC_P = 3000 32 | PEAK_SHAVING_THRESHOLD = MAX_AC_P - (MAX_AC_P * 0.05) 33 | PEAK_EASING_RESERVE = 50 34 | MIN_CHARGE_PCT = 5 35 | PV_P_TOPIC = 'fronius/p_pv' 36 | SET_CHG_PCT_TOPIC = 'battery/set/chg_pct' 37 | CHG_PCT_TOPIC = 'battery/chg_pct' 38 | AUTO_CHG_TOPIC = 'battery/auto_chg_pct' 39 | SOC_TOPIC = 'fronius/soc' 40 | 41 | chg_pct = 0 42 | pv_p = 0 43 | auto_chg_pct = False 44 | soc = 0 45 | 46 | 47 | def publish_chg_pct(pct): 48 | if pct != chg_pct: 49 | (result, mid) = mqttc.publish(SET_CHG_PCT_TOPIC, 50 | str(pct), 0, retain=True) 51 | logging.debug("Pubish Result: {} for {}: {}".format(result, 52 | SET_CHG_PCT_TOPIC, 53 | pct)) 54 | 55 | 56 | def update_chg_p(): 57 | 58 | now = datetime.datetime.now() 59 | noon = now.replace(hour=12, minute=00, second=0, microsecond=0) 60 | afternoon = now.replace(hour=15, minute=30, second=0, microsecond=0) 61 | 62 | # if soc < PEAK_EASING_RESERVE set charging to 100% to keep some 63 | # energy in the battery to support peak demands 64 | if soc < PEAK_EASING_RESERVE: 65 | publish_chg_pct(100) 66 | return 67 | 68 | # If we have enough enegery for peak demand easing but are not 69 | # yet (before noon) above the peak shaving threashold, do not charge 70 | # anymore, to keep reserve for peak shaving 71 | if soc >= PEAK_EASING_RESERVE and pv_p < PEAK_SHAVING_THRESHOLD and now < noon: 72 | publish_chg_pct(0) 73 | return 74 | 75 | # At noon adaptive charging (see below) 76 | # Prevent peak shaving as long as possible by not charging the battery 77 | # to quickly 78 | if pv_p >= PEAK_SHAVING_THRESHOLD: 79 | new_chg_p = pv_p - MAX_AC_P 80 | new_chg_pct = math.ceil((100 * new_chg_p) / MAX_CHG_P) 81 | 82 | # bring new_chg_pct between 10 and 100 83 | if new_chg_pct < MIN_CHARGE_PCT: 84 | new_chg_pct = MIN_CHARGE_PCT 85 | if new_chg_pct > 100: 86 | new_chg_pct = 100 87 | 88 | publish_chg_pct(new_chg_pct) 89 | return 90 | 91 | # before afternoon do not charge to fast 92 | if now < afternoon: 93 | publish_chg_pct(50) 94 | return 95 | 96 | # fail safe no limit 97 | # in the afternoon charge as fast/much as possibe 98 | publish_chg_pct(100) 99 | 100 | 101 | def on_message(mqttc, obj, msg): 102 | if msg.topic == CHG_PCT_TOPIC: 103 | global chg_pct 104 | chg_pct = math.floor(float(msg.payload)) 105 | logging.debug("got new chg_pct: {}".format(chg_pct)) 106 | 107 | if msg.topic == PV_P_TOPIC: 108 | global pv_p 109 | pv_p = math.floor(float(msg.payload)) 110 | logging.debug("got new pv_p: {}".format(pv_p)) 111 | 112 | if msg.topic == AUTO_CHG_TOPIC: 113 | global auto_chg_pct 114 | if msg.payload == b'True': 115 | auto_chg_pct = True 116 | update_chg_p() 117 | else: 118 | auto_chg_pct = False 119 | # reset chg_pct to 100% when auto mode is disabled 120 | publish_chg_pct(100) 121 | 122 | logging.debug("got new auto_chg_pct: {}".format(auto_chg_pct)) 123 | 124 | if msg.topic == SOC_TOPIC: 125 | global soc 126 | soc = int(msg.payload) 127 | logging.debug("got new soc: {}".format(soc)) 128 | 129 | 130 | if __name__ == '__main__': 131 | logging.basicConfig(stream=sys.stdout, 132 | format='%(asctime)s %(levelname)-8s %(message)s', 133 | datefmt='%Y-%m-%d %H:%M:%S', 134 | level=logging.INFO) 135 | 136 | mqttc = paho.Client('battery-controller', clean_session=True) 137 | # mqttc.enable_logger() 138 | 139 | mqttc.connect(BROKER_HOST, BROKER_PORT, 60) 140 | logging.info("Connected to {}:{}".format(BROKER_HOST, BROKER_PORT)) 141 | 142 | mqttc.on_message = on_message 143 | mqttc.subscribe(PV_P_TOPIC, 0) 144 | mqttc.subscribe(CHG_PCT_TOPIC, 0) 145 | mqttc.subscribe(AUTO_CHG_TOPIC, 0) 146 | mqttc.subscribe(SOC_TOPIC, 0) 147 | 148 | mqttc.loop_start() 149 | while True: 150 | try: 151 | if auto_chg_pct: 152 | update_chg_p() 153 | time.sleep(FREQUENCY) 154 | except KeyboardInterrupt: 155 | break 156 | except Exception: 157 | raise 158 | 159 | mqttc.disconnect() 160 | mqttc.loop_stop() # waits, until DISCONNECT message is sent out 161 | logging.info("Disconnected from to {}:{}".format(BROKER_HOST, BROKER_PORT)) 162 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | BROKER_HOST = 'mosquitto.kleber.home.arpa' 4 | BROKER_PORT = 1883 5 | 6 | INFLUXDB_HOST = 'influxdb.kleber.home.arpa' 7 | 8 | FRONIUS_HOST = 'fronius.kleber.home.arpa' 9 | FRONIUS_MQTT_PREFIX = 'fronius' 10 | ENERGYMETER_MQTT_PREFIX = 'energymeter' 11 | 12 | MODBUS_HOST = 'fronius.kleber.home.arpa' 13 | BATTERY_MQTT_PREFIX = 'battery' 14 | 15 | PV_P_TOPIC = 'fronius/p_pv' 16 | SET_CHG_PCT_TOPIC = 'battery/set/chg_pct' 17 | CHG_PCT_TOPIC = 'battery/chg_pct' 18 | AUTO_CHG_TOPIC = 'battery/auto_chg_pct' 19 | SOC_TOPIC = 'fronius/soc' 20 | 21 | GOECHARGER_HOST = 'go-echarger.kleber.home.arpa' 22 | GOECHARGER_MQTT_PREFIX = 'goe' 23 | 24 | NETATMO_MQTT_PREFIX = 'netatmo' 25 | 26 | ZOE_MQTT_PREFIX = 'zoe' 27 | 28 | HEATER_MQTT_PREFIX = 'heater' 29 | 30 | VCONTROLD_MQTT_PREFIX = 'vcontrold' 31 | 32 | DIESEL_MQTT_PREFIX = 'diesel' 33 | -------------------------------------------------------------------------------- /doc/IoT_OnOff_Fronius.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akleber/mqtt-connectors/2d25de4f5fdd971900a1cd2db919c1ea0ea9d451/doc/IoT_OnOff_Fronius.jpeg -------------------------------------------------------------------------------- /doc/batt-controller-algo.md: -------------------------------------------------------------------------------- 1 | # Algo 2 | 3 | ## Global rules 4 | * In the morning (before 9:30) no charging 5 | Reserver space in battery for peak shaving 6 | * At noon adaptive charging (see below) 7 | Prevent peak shaving as long as possible by not charging the battery to quickly 8 | * In the afternoon (after 15:00) 100% 9 | Get the battery as full as possible for after sunset 10 | * If SOC < 30% chage 50% 11 | Keep some energy in battery to support peak demands 12 | * Depending on forecaset, ... to be defined 13 | Try to get the battery as full as possible on cloudy days, prevent low SOC because of above rules. 14 | 15 | ## Rules during adaptive charging 16 | * ... -------------------------------------------------------------------------------- /doc/forecast.md: -------------------------------------------------------------------------------- 1 | # Forecast 2 | 3 | https://api.forecast.solar/estimate/watthours/day/49.9/8.5/35/0/4.06 4 | 5 | 2019-03-28_15-42: 6 | 2019-03-28 18643.52 7 | 2019-03-29 18468.94 8 | 9 | 2019-03-28 16-37: 10 | 2019-03-28 18643.52 11 | 2019-03-29 22208.2 12 | 13 | 2019-03-29 09-51: 14 | 2019-03-29 21696.64 15 | 2019-03-30 21623.56 16 | 17 | 2019-03-29 11-09: 18 | 2019-03-29 20023.92 19 | 2019-03-30 22187.9 20 | 21 | 2019-03-29 13-12: 22 | 2019-03-29 19845.28 23 | 2019-03-30 21806.26 24 | 25 | -------------------------------------------------------------------------------- /doc/growatt-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "InverterStatus": 1, 3 | "InputPower": 442.2, 4 | "PV1Voltage": 58.6, 5 | "PV1InputCurrent": 7, 6 | "PV1InputPower": 442.2, 7 | "PV2Voltage": 0, 8 | "PV2InputCurrent": 0, 9 | "PV2InputPower": 0, 10 | "OutputPower": 433.9, 11 | "GridFrequency": 50.01, 12 | "L1ThreePhaseGridVoltage": 236.5, 13 | "L1ThreePhaseGridOutputCurrent": 1.7, 14 | "L1ThreePhaseGridOutputPower": 420.5, 15 | "L2ThreePhaseGridVoltage": 0, 16 | "L2ThreePhaseGridOutputCurrent": 0, 17 | "L2ThreePhaseGridOutputPower": 0, 18 | "L3ThreePhaseGridVoltage": 0, 19 | "L3ThreePhaseGridOutputCurrent": 0, 20 | "L3ThreePhaseGridOutputPower": 0, 21 | "TodayGenerateEnergy": 0, 22 | "TotalGenerateEnergy": 0.2, 23 | "TWorkTimeTotal": 4789, 24 | "PV1EnergyToday": 0, 25 | "PV1EnergyTotal": 0.2, 26 | "PV2EnergyToday": 0, 27 | "PV2EnergyTotal": 0, 28 | "PVEnergyTotal": 0.2, 29 | "InverterTemperature": 24.9, 30 | "TemperatureInsideIPM": 24.9, 31 | "BoostTemperature": 0, 32 | "DischargePower": 0, 33 | "ChargePower": 0, 34 | "BatteryVoltage": 0, 35 | "SOC": 0, 36 | "ACPowerToUser": 0, 37 | "ACPowerToUserTotal": 0, 38 | "ACPowerToGrid": 0, 39 | "ACPowerToGridTotal": 0, 40 | "INVPowerToLocalLoad": 0, 41 | "INVPowerToLocalLoadTotal": 0, 42 | "BatteryTemperature": 0, 43 | "BatteryState": 0, 44 | "EnergyToUserToday": 0, 45 | "EnergyToUserTotal": 0, 46 | "EnergyToGridToday": 0, 47 | "EnergyToGridTotal": 0, 48 | "DischargeEnergyToday": 0, 49 | "DischargeEnergyTotal": 0, 50 | "ChargeEnergyToday": 0, 51 | "ChargeEnergyTotal": 0, 52 | "LocalLoadEnergyToday": 0, 53 | "LocalLoadEnergyTotal": 0, 54 | "ACChargeEnergyToday": 0, 55 | "ACChargeEnergyTotal": 0, 56 | "Mac": "B4:8A:0A:E9:11:85", 57 | "Cnt": 141 58 | } -------------------------------------------------------------------------------- /doc/setup.md: -------------------------------------------------------------------------------- 1 | # Setup 2 | 3 | To have faster access to the Fronius Realtime Date, without using solar.web and their app, 4 | I have a [mosquitto](https://mosquitto.org) MQTT broker running on a Raspberry Pi and I am using 5 | the [IoT OnOff](https://www.iot-onoff.com) app for visualizing its data. 6 | On the Pi these scripts here runs in a loop to fetch data from the Fronius API an publish it to mosquitto every 2 seconds. 7 | 8 | 9 | 10 | # Running as a service 11 | 12 | On my raspian I use supervisor to run the connectors as a service with the config files found in the supervisor directory. 13 | 14 | To update supervisor with changes run 15 | 16 | ``` 17 | supervisorctl reread 18 | supervisorctl update 19 | ``` 20 | 21 | Restart a script 22 | ``` 23 | supervisorctl restart battery-controller 24 | ``` 25 | 26 | 27 | # Deploy 28 | 29 | Via git push to the rpi. 30 | On the rpi: 31 | 32 | ``` 33 | git config --local receive.denyCurrentBranch updateInstead 34 | ``` 35 | 36 | Hook wip 37 | 38 | 39 | -------------------------------------------------------------------------------- /energymeter-connector.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Fetches some data from the fronius json api 4 | and publishes the result to mqtt. 5 | Read only. 6 | Fronius meter via fronius api. It is very slow.""" 7 | 8 | import paho.mqtt.client as paho # pip install paho-mqtt 9 | import requests 10 | import time 11 | import logging 12 | import sys 13 | from config import * 14 | 15 | 16 | FREQUENCY = 10 #sec 17 | TIMEOUT = 3 #sec 18 | 19 | 20 | def energymeter_data(): 21 | 22 | values = {} 23 | 24 | try: 25 | url = "http://{}/solar_api/v1/GetMeterRealtimeData.cgi?Scope=System".format(FRONIUS_HOST) # noqa E501 26 | r = requests.get(url, timeout=TIMEOUT) 27 | r.raise_for_status() 28 | meter_data = r.json() 29 | 30 | values['e_feedin'] = meter_data['Body']['Data']['0']['EnergyReal_WAC_Sum_Produced'] / 1000 31 | values['e_receive'] = meter_data['Body']['Data']['0']['EnergyReal_WAC_Sum_Consumed'] / 1000 32 | 33 | values['voltage_l1'] = meter_data['Body']['Data']['0']['Voltage_AC_Phase_1'] 34 | values['voltage_l2'] = meter_data['Body']['Data']['0']['Voltage_AC_Phase_2'] 35 | values['voltage_l3'] = meter_data['Body']['Data']['0']['Voltage_AC_Phase_3'] 36 | 37 | # handling for null/None values 38 | for k, v in values.items(): 39 | if v is None: 40 | values[k] = 0 41 | 42 | except requests.exceptions.Timeout: 43 | logging.exception("Timeout requesting {}".format(url)) 44 | except requests.exceptions.RequestException as e: 45 | logging.exception("requests exception {}".format(e)) 46 | 47 | return values 48 | 49 | 50 | if __name__ == '__main__': 51 | logging.basicConfig(stream=sys.stdout, 52 | format='%(asctime)s %(levelname)-8s %(message)s', 53 | datefmt='%Y-%m-%d %H:%M:%S', 54 | level=logging.INFO) 55 | 56 | logging.getLogger("urllib3").setLevel(logging.INFO) 57 | 58 | mqttc = paho.Client('energymeter-connector', clean_session=True) 59 | # mqttc.enable_logger() 60 | mqttc.will_set("{}/connectorstatus".format(ENERGYMETER_MQTT_PREFIX), "Energymeter Connector: LOST_CONNECTION", 0, retain=True) 61 | 62 | mqttc.connect(BROKER_HOST, BROKER_PORT, 60) 63 | logging.info("Connected to {}:{}".format(BROKER_HOST, BROKER_PORT)) 64 | 65 | mqttc.publish("{}/connectorstatus".format(ENERGYMETER_MQTT_PREFIX), "Energymeter Connector: ON-LINE", retain=True) 66 | 67 | mqttc.loop_start() 68 | while True: 69 | try: 70 | values = energymeter_data() 71 | for k, v in values.items(): 72 | (result, mid) = mqttc.publish("{}/{}".format(ENERGYMETER_MQTT_PREFIX, k), str(v), 0) 73 | logging.debug("Pubish Result: {} MID: {} for {}: {}".format(result, mid, k, v)) # noqa E501 74 | 75 | time.sleep(FREQUENCY) 76 | except KeyboardInterrupt: 77 | break 78 | except Exception: 79 | raise 80 | 81 | mqttc.publish("{}/connectorstatus".format(ENERGYMETER_MQTT_PREFIX), "Energymeter Connector: OFF-LINE", retain=True) 82 | 83 | mqttc.disconnect() 84 | mqttc.loop_stop() # waits, until DISCONNECT message is sent out 85 | logging.info("Disconnected from to {}:{}".format(BROKER_HOST, BROKER_PORT)) 86 | -------------------------------------------------------------------------------- /forecastsolar-connector.py: -------------------------------------------------------------------------------- 1 | import paho.mqtt.client as paho # pip install paho-mqtt 2 | import requests 3 | import logging 4 | import sys 5 | import time 6 | 7 | 8 | FORECAST_API_DAY = 'https://api.forecast.solar/estimate/watthours/day/49.9/8.5/35/0/4.06' 9 | FORECAST_MQTT_PREFIX = 'forecast' 10 | 11 | BROKER_HOST = 'localhost' 12 | BROKER_PORT = 1883 13 | 14 | if __name__ == '__main__': 15 | logging.basicConfig(stream=sys.stderr, level=logging.INFO) 16 | logging.getLogger("urllib3").setLevel(logging.INFO) 17 | 18 | url = FORECAST_API_DAY 19 | r = requests.get(url, timeout=5) 20 | r.raise_for_status() 21 | j = r.json() 22 | 23 | today = time.strftime("%Y-%m-%d") 24 | forecast_today = j['result'][today] 25 | 26 | logging.info("Forecast today: {}".format(forecast_today)) 27 | 28 | 29 | mqttc = paho.Client('forecastsolar-mqtt-connector', clean_session=True) 30 | # mqttc.enable_logger() 31 | 32 | mqttc.connect(BROKER_HOST, BROKER_PORT, 60) 33 | logging.info("Connected to {}:{}".format(BROKER_HOST, BROKER_PORT)) 34 | 35 | k = 'day' 36 | v = forecast_today 37 | 38 | (result, mid) = mqttc.publish("{}/{}".format(FORECAST_MQTT_PREFIX, k), str(v), 0, retain=True) 39 | logging.info("Pubish Result: {} MID: {} for {}: {}".format(result, mid, k, v)) # noqa E501 40 | 41 | mqttc.disconnect() 42 | logging.info("Disconnected from to {}:{}".format(BROKER_HOST, BROKER_PORT)) 43 | -------------------------------------------------------------------------------- /fronius-connector.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Fetches some data from the fronius json api 4 | and publishes the result to mqtt. 5 | Read only.""" 6 | 7 | import paho.mqtt.client as paho # pip install paho-mqtt 8 | import requests 9 | import time 10 | import logging 11 | import sys 12 | from config import * 13 | 14 | 15 | FREQUENCY = 3 16 | 17 | 18 | def fronius_data(): 19 | 20 | values = {} 21 | 22 | try: 23 | url = "http://{}/solar_api/v1/GetPowerFlowRealtimeData.fcgi".format(FRONIUS_HOST) # noqa E501 24 | r = requests.get(url, timeout=FREQUENCY - 0.5) 25 | r.raise_for_status() 26 | powerflow_data = r.json() 27 | 28 | values['p_pv'] = powerflow_data['Body']['Data']['Site']['P_PV'] 29 | values['p_grid'] = powerflow_data['Body']['Data']['Site']['P_Grid'] 30 | values['p_akku'] = powerflow_data['Body']['Data']['Site']['P_Akku'] 31 | values['p_load'] = -powerflow_data['Body']['Data']['Site']['P_Load'] 32 | values['soc'] = powerflow_data['Body']['Data']['Inverters']['1'].get('SOC') 33 | values['battery_mode'] = powerflow_data['Body']['Data']['Inverters']['1'].get('Battery_Mode') 34 | values['e_day'] = powerflow_data['Body']['Data']['Inverters']['1']['E_Day'] / 1000 35 | 36 | # handling for null/None values 37 | for k, v in values.items(): 38 | if v is None: 39 | values[k] = 0 40 | 41 | except requests.exceptions.Timeout: 42 | print("Timeout requesting {}".format(url)) 43 | except requests.exceptions.RequestException as e: 44 | print("requests exception {}".format(e)) 45 | 46 | return values 47 | 48 | 49 | if __name__ == '__main__': 50 | logging.basicConfig(stream=sys.stderr, level=logging.INFO) 51 | logging.getLogger("urllib3").setLevel(logging.INFO) 52 | 53 | mqttc = paho.Client('fronius-connector', clean_session=True) 54 | # mqttc.enable_logger() 55 | mqttc.will_set("{}/connectorstatus".format(FRONIUS_MQTT_PREFIX), "Fronius Connector: LOST_CONNECTION", 0, retain=True) 56 | 57 | mqttc.connect(BROKER_HOST, BROKER_PORT, 60) 58 | logging.info("Connected to {}:{}".format(BROKER_HOST, BROKER_PORT)) 59 | 60 | mqttc.publish("{}/connectorstatus".format(FRONIUS_MQTT_PREFIX), "Fronius Connector: ON-LINE", retain=True) 61 | 62 | mqttc.loop_start() 63 | while True: 64 | try: 65 | values = fronius_data() 66 | for k, v in values.items(): 67 | (result, mid) = mqttc.publish("{}/{}".format(FRONIUS_MQTT_PREFIX, k), str(v), 0) 68 | logging.debug("Pubish Result: {} MID: {} for {}: {}".format(result, mid, k, v)) # noqa E501 69 | 70 | time.sleep(FREQUENCY) 71 | except KeyboardInterrupt: 72 | break 73 | except Exception: 74 | raise 75 | 76 | mqttc.publish("{}/connectorstatus".format(FRONIUS_MQTT_PREFIX), "Fronius Connector: OFF-LINE", retain=True) 77 | 78 | mqttc.disconnect() 79 | mqttc.loop_stop() # waits, until DISCONNECT message is sent out 80 | logging.info("Disconnected from to {}:{}".format(BROKER_HOST, BROKER_PORT)) 81 | -------------------------------------------------------------------------------- /goecharger-connector.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Fetches some data from the go-eCharger local 4 | json api and published them to mqtt every 5 5 | seconds. 6 | Read-only.""" 7 | 8 | import paho.mqtt.client as paho # pip install paho-mqtt 9 | import requests 10 | import time 11 | import logging 12 | import sys 13 | from config import * 14 | 15 | 16 | FREQUENCY = 5 # sleep in sec 17 | EXCEPTION_DELAY = 120 # sleep in sec 18 | 19 | 20 | def goecharger_data(): 21 | 22 | values = {} 23 | 24 | try: 25 | url = "http://{}/status".format(GOECHARGER_HOST) 26 | r = requests.get(url, timeout=FREQUENCY - 0.5) 27 | r.raise_for_status() 28 | j = r.json() 29 | 30 | values['power_sum'] = (j['nrg'][7] + j['nrg'][8] + j['nrg'][9]) / 10 31 | values['cur_chg_e'] = int(j['dws']) / 360000 # Deka-Watt-Sec to kWh 32 | 33 | except requests.Timeout: 34 | logging.error(f"Requests: Timeout: {url}") 35 | time.sleep(EXCEPTION_DELAY) 36 | except requests.RequestException as e: 37 | logging.error(f"Requests: Exception: {e}") 38 | time.sleep(EXCEPTION_DELAY) 39 | 40 | return values 41 | 42 | 43 | if __name__ == '__main__': 44 | logging.basicConfig( 45 | format='%(asctime)s %(levelname)-8s %(message)s', 46 | level=logging.INFO, 47 | datefmt='%Y-%m-%d %H:%M:%S') 48 | 49 | logging.getLogger("urllib3").setLevel(logging.INFO) 50 | 51 | mqttc = paho.Client('goecharger-mqtt-connector', clean_session=True) 52 | # mqttc.enable_logger() 53 | mqttc.will_set("{}/connectorstatus".format(GOECHARGER_MQTT_PREFIX), "go-eCharger Connector: LOST_CONNECTION", 0, retain=True) 54 | 55 | mqttc.connect(BROKER_HOST, BROKER_PORT, 60) 56 | logging.info("Connected to {}:{}".format(BROKER_HOST, BROKER_PORT)) 57 | 58 | mqttc.publish("{}/connectorstatus".format(GOECHARGER_MQTT_PREFIX), "go-eCharger Connector: ON-LINE", retain=True) 59 | 60 | mqttc.loop_start() 61 | while True: 62 | try: 63 | values = goecharger_data() 64 | for k, v in values.items(): 65 | (result, mid) = mqttc.publish("{}/{}".format(GOECHARGER_MQTT_PREFIX, k), str(v), 0) 66 | logging.debug("Pubish Result: {} MID: {} for {}: {}".format(result, mid, k, v)) # noqa E501 67 | 68 | time.sleep(FREQUENCY) 69 | except KeyboardInterrupt: 70 | break 71 | except Exception: 72 | raise 73 | 74 | mqttc.publish("{}/connectorstatus".format(GOECHARGER_MQTT_PREFIX), "go-eCharger Connector: OFF-LINE", retain=True) 75 | 76 | mqttc.disconnect() 77 | mqttc.loop_stop() # waits, until DISCONNECT message is sent out 78 | logging.info("Disconnected from to {}:{}".format(BROKER_HOST, BROKER_PORT)) 79 | -------------------------------------------------------------------------------- /heater-connector.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import paho.mqtt.client as paho # pip install paho-mqtt 4 | import time 5 | import logging 6 | import sys 7 | import subprocess 8 | from pathlib import Path 9 | from config import * 10 | 11 | 12 | FREQUENCY = 5 # query ever x sec 13 | 14 | 15 | def sensor_data(): 16 | values = {} 17 | 18 | devices_path = Path('/sys/bus/w1/devices') 19 | 20 | for device in devices_path.iterdir(): 21 | if not device.is_dir() or device.name == 'w1_bus_master1': 22 | continue 23 | 24 | device_temperature_file = devices_path / device / 'temperature' 25 | 26 | temperture_string = device_temperature_file.read_text() 27 | temperature = float(temperture_string) / 1000 28 | 29 | values[device.name] = temperature 30 | 31 | return values 32 | 33 | 34 | if __name__ == '__main__': 35 | logging.basicConfig(stream=sys.stderr, level=logging.INFO) 36 | 37 | mqttc = paho.Client('heater-connector', clean_session=True) 38 | # mqttc.enable_logger() 39 | mqttc.will_set("{}/connectorstatus".format(HEATER_MQTT_PREFIX), "Heater Connector: LOST_CONNECTION", 0, retain=True) 40 | 41 | mqttc.connect(BROKER_HOST, BROKER_PORT, 60) 42 | logging.info("Connected to {}:{}".format(BROKER_HOST, BROKER_PORT)) 43 | 44 | mqttc.publish("{}/connectorstatus".format(HEATER_MQTT_PREFIX), "Heater Connector: ON-LINE", retain=True) 45 | 46 | mqttc.loop_start() 47 | while True: 48 | try: 49 | values = sensor_data() 50 | for k, v in values.items(): 51 | (result, mid) = mqttc.publish("{}/{}".format(HEATER_MQTT_PREFIX, k), str(v), 0) 52 | logging.debug("Pubish Result: {} MID: {} for {}: {}".format(result, mid, k, v)) # noqa E501 53 | 54 | time.sleep(FREQUENCY) 55 | except KeyboardInterrupt: 56 | break 57 | except Exception: 58 | raise 59 | 60 | mqttc.publish("{}/connectorstatus".format(HEATER_MQTT_PREFIX), "Heater Connector: OFF-LINE", retain=True) 61 | 62 | mqttc.disconnect() 63 | mqttc.loop_stop() # waits, until DISCONNECT message is sent out 64 | logging.info("Disconnected from to {}:{}".format(BROKER_HOST, BROKER_PORT)) 65 | -------------------------------------------------------------------------------- /influxdb-connector.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # https://thingsmatic.com/2017/03/02/influxdb-and-grafana-for-sensor-time-series/ 3 | import paho.mqtt.client as mqtt 4 | import datetime 5 | import time 6 | import json 7 | import logging 8 | import sys 9 | from influxdb import InfluxDBClient 10 | from config import * 11 | 12 | 13 | def on_connect(client, userdata, flags, rc): 14 | print("Connected with result code " + str(rc)) 15 | client.subscribe("#") 16 | 17 | 18 | def on_message(client, userdata, msg): 19 | topic = msg.topic 20 | # print("Received a message on topic: " + topic) 21 | # Use utc as timestamp 22 | receiveTime = datetime.datetime.utcnow() 23 | message = msg.payload.decode("utf-8") 24 | 25 | if "tele/" in topic: 26 | send_tasmota_data(topic, receiveTime, message) 27 | elif "growatt" in topic: 28 | send_growatt_data(topic, receiveTime, message) 29 | else: 30 | send_plain_data(topic, receiveTime, message) 31 | 32 | 33 | def send_growatt_data(topic, receiveTime, message): 34 | try: 35 | data = json.loads(message) 36 | except json.JSONDecodeError: 37 | return 38 | 39 | fields = ["PV1Voltage", "PV1InputCurrent", "PV1InputPower", 40 | "OutputPower", "GridFrequency", "L1ThreePhaseGridVoltage", "L1ThreePhaseGridOutputCurrent", 41 | "L1ThreePhaseGridOutputPower", "TodayGenerateEnergy", "TotalGenerateEnergy", "TWorkTimeTotal", 42 | "InverterTemperature"] 43 | 44 | points = [] 45 | for field in fields: 46 | try: 47 | float_v = float(data[field]) 48 | except Exception: 49 | continue 50 | 51 | point = { 52 | "measurement": f"{topic}/{field}", 53 | "time": receiveTime, 54 | "fields": { 55 | "value": float_v 56 | } 57 | } 58 | points.append(point) 59 | 60 | try: 61 | dbclient.write_points(points) 62 | except Exception: 63 | logging.exception("Exception writing to db") 64 | 65 | 66 | def send_tasmota_data(topic, receiveTime, message): 67 | try: 68 | data = json.loads(message) 69 | except json.JSONDecodeError: 70 | return 71 | 72 | sensordata = data.get("ENERGY", data.get("WTS01")) 73 | if not sensordata: 74 | return 75 | 76 | points = [] 77 | for k,v in sensordata.items(): 78 | try: 79 | float_v = float(v) 80 | except Exception: 81 | continue 82 | 83 | point = { 84 | "measurement": f"{topic}/{k}", 85 | "time": receiveTime, 86 | "fields": { 87 | "value": float_v 88 | } 89 | } 90 | points.append(point) 91 | 92 | try: 93 | dbclient.write_points(points) 94 | except Exception: 95 | logging.exception("Exception writing to db") 96 | 97 | 98 | def send_plain_data(topic, receiveTime, message): 99 | try: 100 | # Convert the string to a float so that it is stored as a number and not a string in the database 101 | val = float(message) 102 | except Exception: 103 | # print("Could not convert " + message + " to a float value") 104 | return 105 | 106 | # print(str(receiveTime) + ": " + topic + " " + str(val)) 107 | 108 | points = [ 109 | { 110 | "measurement": topic, 111 | "time": receiveTime, 112 | "fields": { 113 | "value": val 114 | } 115 | } 116 | ] 117 | 118 | try: 119 | dbclient.write_points(points) 120 | except Exception: 121 | logging.exception("Exception writing to db") 122 | # print("Finished writing to InfluxDB") 123 | 124 | 125 | logging.basicConfig(stream=sys.stdout, 126 | format='%(asctime)s %(levelname)-8s %(message)s', 127 | datefmt='%Y-%m-%d %H:%M:%S', 128 | level=logging.INFO) 129 | logging.info("start") 130 | 131 | # Set up a client for InfluxDB 132 | dbclient = InfluxDBClient(INFLUXDB_HOST, 8086, 'mqtt', 'mqtt', 'mqtt') 133 | logging.info("dbclient created") 134 | 135 | # Initialize the MQTT client that should connect to the Mosquitto broker 136 | client = mqtt.Client() 137 | client.on_connect = on_connect 138 | client.on_message = on_message 139 | connOK = False 140 | while(connOK is False): 141 | try: 142 | client.connect(BROKER_HOST, BROKER_PORT, 60) 143 | connOK = True 144 | except Exception: 145 | connOK = False 146 | time.sleep(2) 147 | 148 | logging.info("mqtt connection established") 149 | 150 | # Blocking loop to the Mosquitto broker 151 | client.loop_forever() 152 | -------------------------------------------------------------------------------- /local-dht-connector.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import paho.mqtt.client as paho # pip install paho-mqtt 4 | import time 5 | import logging 6 | import sys 7 | import weathermath 8 | from config import * 9 | 10 | from pigpio_dht import DHT22 11 | 12 | 13 | MQTT_PREFIX = 'rpi' 14 | FREQUENCY_S = 300 15 | 16 | dhtDevice = DHT22(4) 17 | 18 | 19 | def data(retry=True): 20 | 21 | values = {} 22 | 23 | try: 24 | result = dhtDevice.read() 25 | 26 | if result['valid']: 27 | values['waschkueche/temp'] = result['temp_c'] 28 | values['waschkueche/humidity'] = result['humidity'] 29 | values['waschkueche/humidity_abs'] = weathermath.AF(result['humidity'], result['temp_c']) 30 | logging.info("{:.1f} C, {} %, {:.2f} g/m3 ".format(values['waschkueche/temp'], 31 | values['waschkueche/humidity'], 32 | values['waschkueche/humidity_abs'])) 33 | else: 34 | logging.info("data not valid") 35 | 36 | except TimeoutError as error: 37 | logging.info("DHT error: ".format(str(error))) 38 | 39 | if retry: 40 | logging.info("Retrying in 3 sec...") 41 | time.sleep(3) 42 | values = data(False) 43 | else: 44 | logging.info("Not retrying again") 45 | 46 | return values 47 | 48 | 49 | if __name__ == '__main__': 50 | logging.basicConfig(stream=sys.stderr, level=logging.INFO) 51 | 52 | mqttc = paho.Client('local-dht-connector', clean_session=True) 53 | # mqttc.enable_logger() 54 | mqttc.will_set("{}/connectorstatus".format(MQTT_PREFIX), "Local DHT Connector: LOST_CONNECTION", 0, retain=True) 55 | 56 | mqttc.connect(BROKER_HOST, BROKER_PORT, 60) 57 | logging.info("Connected to {}:{}".format(BROKER_HOST, BROKER_PORT)) 58 | 59 | mqttc.publish("{}/connectorstatus".format(MQTT_PREFIX), "Local DHT Connector: ON-LINE", retain=True) 60 | 61 | mqttc.loop_start() 62 | while True: 63 | try: 64 | values = data() 65 | for k, v in values.items(): 66 | (result, mid) = mqttc.publish("{}/{}".format(MQTT_PREFIX, k), str(v), qos=0, retain=True) 67 | logging.debug("Pubish Result: {} MID: {} for {}: {}".format(result, mid, k, v)) # noqa E501 68 | 69 | time.sleep(FREQUENCY_S) 70 | except KeyboardInterrupt: 71 | break 72 | except Exception: 73 | raise 74 | 75 | mqttc.publish("{}/connectorstatus".format(MQTT_PREFIX), "Local DHT Connector: OFF-LINE", retain=True) 76 | 77 | mqttc.disconnect() 78 | mqttc.loop_stop() # waits, until DISCONNECT message is sent out 79 | logging.info("Disconnected from to {}:{}".format(BROKER_HOST, BROKER_PORT)) 80 | -------------------------------------------------------------------------------- /local-gas-connector.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import paho.mqtt.client as paho # pip install paho-mqtt 4 | import time 5 | import logging 6 | import sys 7 | import pigpio 8 | from config import * 9 | 10 | MQTT_PREFIX = 'gas' 11 | FREQUENCY_S = 1 12 | GAS_GPIO = 27 13 | 14 | m3abs = 0.0 15 | gpio = None 16 | reed_state_old = 1 17 | 18 | 19 | def read_state(): 20 | global m3abs 21 | 22 | with open('local-gas-connector.state') as f: 23 | m3abs = float(f.readline().strip()) 24 | 25 | logging.info("Read initial value: {:.2f}".format(m3abs)) 26 | 27 | 28 | def init_gpio(): 29 | global gpio 30 | gpio = pigpio.pi() 31 | gpio.set_mode(GAS_GPIO, pigpio.INPUT) 32 | gpio.set_pull_up_down(GAS_GPIO, pigpio.PUD_UP) 33 | 34 | logging.info('GPIO initialized') 35 | 36 | 37 | def data(): 38 | global reed_state_old 39 | global m3abs 40 | 41 | values = {} 42 | reed_state = gpio.read(GAS_GPIO) 43 | 44 | if reed_state == 1: 45 | logging.debug("Reed state open") 46 | reed_state_old = reed_state 47 | else: 48 | logging.debug("Reed state closed") 49 | 50 | if reed_state_old != reed_state: 51 | reed_state_old = reed_state 52 | 53 | m3abs += 0.01 54 | values['volume'] = "{:.2f}".format(m3abs) 55 | values['tick'] = '1' 56 | 57 | logging.debug("m3abs: {:.2f}".format(m3abs)) 58 | 59 | return values 60 | 61 | 62 | if __name__ == '__main__': 63 | logging.basicConfig(stream=sys.stderr, level=logging.INFO) 64 | 65 | read_state() 66 | init_gpio() 67 | 68 | mqttc = paho.Client('local-gas-connector', clean_session=True) 69 | # mqttc.enable_logger() 70 | mqttc.will_set("{}/connectorstatus".format(MQTT_PREFIX), "Local Gas Connector: LOST_CONNECTION", 0, retain=True) 71 | 72 | mqttc.connect(BROKER_HOST, BROKER_PORT, 60) 73 | logging.info("Connected to {}:{}".format(BROKER_HOST, BROKER_PORT)) 74 | 75 | mqttc.publish("{}/connectorstatus".format(MQTT_PREFIX), "Local Gas Connector: ON-LINE", retain=True) 76 | 77 | # initial value 78 | (result, mid) = mqttc.publish("{}/{}".format(MQTT_PREFIX, 'volume'), str("{:.2f}".format(m3abs)), 0, retain=True) 79 | logging.debug("Pubish Result: {} MID: {} for {}: {}".format(result, mid, 'volume', "{:.2f}".format(m3abs))) # noqa E501 80 | 81 | mqttc.loop_start() 82 | while True: 83 | try: 84 | values = data() 85 | for k, v in values.items(): 86 | (result, mid) = mqttc.publish("{}/{}".format(MQTT_PREFIX, k), str(v), 0, retain=True) 87 | logging.debug("Pubish Result: {} MID: {} for {}: {}".format(result, mid, k, v)) # noqa E501 88 | 89 | time.sleep(FREQUENCY_S) 90 | except KeyboardInterrupt: 91 | break 92 | except Exception: 93 | raise 94 | 95 | mqttc.publish("{}/connectorstatus".format(MQTT_PREFIX), "Local Gas Connector: OFF-LINE", retain=True) 96 | 97 | mqttc.disconnect() 98 | mqttc.loop_stop() # waits, until DISCONNECT message is sent out 99 | logging.info("Disconnected from to {}:{}".format(BROKER_HOST, BROKER_PORT)) 100 | -------------------------------------------------------------------------------- /local-gas-connector.state: -------------------------------------------------------------------------------- 1 | 21971.49 2 | -------------------------------------------------------------------------------- /mqtt-connectors.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": {} 8 | } -------------------------------------------------------------------------------- /mqtt-connectors.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "path": "." 6 | } 7 | ], 8 | 9 | "settings": 10 | { 11 | "pep8_max_line_length": 120, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /netatmo-connector.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import paho.mqtt.client as paho # pip install paho-mqtt 4 | import pyatmo 5 | import time 6 | import logging 7 | import sys 8 | 9 | from config import * 10 | from secrets import * 11 | 12 | 13 | FREQUENCY = 10 * 60 # in seconds 14 | 15 | sensors_to_use = ['Temperature', 'CO2', 'Humidity', 'Noise', 'Pressure', 16 | 'AbsolutePressure', 'battery_percent', 17 | 'Rain', 'sum_rain_24', 'sum_rain_1'] 18 | 19 | 20 | def get_values(authorization): 21 | 22 | values = {} 23 | 24 | try: 25 | weatherData = pyatmo.WeatherStationData(authorization) 26 | weatherDataMap = weatherData.lastData() 27 | 28 | for station in weatherDataMap: 29 | for sensor in weatherDataMap[station]: 30 | if sensor in sensors_to_use: 31 | values["{}/{}".format(station, sensor)] = weatherDataMap[station][sensor] 32 | 33 | except Exception as e: 34 | print("exception {}".format(e)) 35 | 36 | return values 37 | 38 | 39 | if __name__ == '__main__': 40 | logging.basicConfig(stream=sys.stderr, level=logging.INFO) 41 | logging.getLogger("urllib3").setLevel(logging.INFO) 42 | 43 | mqttc = paho.Client('netatmo-mqtt-connector', clean_session=True) 44 | # mqttc.enable_logger() 45 | mqttc.will_set("{}/connectorstatus".format(NETATMO_MQTT_PREFIX), 46 | "netatmo Connector: LOST_CONNECTION", 0, retain=True) 47 | 48 | mqttc.connect(BROKER_HOST, BROKER_PORT, 60) 49 | logging.info("Connected to {}:{}".format(BROKER_HOST, BROKER_PORT)) 50 | 51 | mqttc.publish("{}/connectorstatus".format(NETATMO_MQTT_PREFIX), "netatmo Connector: ON-LINE", retain=True) 52 | 53 | mqttc.loop_start() 54 | 55 | authorization = pyatmo.ClientAuth( 56 | clientId=NETATMO_CLIENT_ID, 57 | clientSecret=NETATMO_CLIENT_SECRET, 58 | username=NETATMO_USERNAME, 59 | password=NETATMO_PASSWORD, 60 | ) 61 | 62 | while True: 63 | try: 64 | values = get_values(authorization) 65 | for k, v in values.items(): 66 | (result, mid) = mqttc.publish("{}/{}".format(NETATMO_MQTT_PREFIX, k), str(v), qos=0, retain=True) 67 | logging.debug("Pubish Result: {} MID: {} for {}: {}".format(result, mid, k, v)) # noqa E501 68 | 69 | time.sleep(FREQUENCY) 70 | except KeyboardInterrupt: 71 | break 72 | except Exception: 73 | raise 74 | 75 | mqttc.publish("{}/connectorstatus".format(NETATMO_MQTT_PREFIX), "netatmo Connector: OFF-LINE", retain=True) 76 | 77 | mqttc.disconnect() 78 | mqttc.loop_stop() # waits, until DISCONNECT message is sent out 79 | logging.info("Disconnected from to {}:{}".format(BROKER_HOST, BROKER_PORT)) 80 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2022.12.7 2 | chardet==3.0.4 3 | idna==2.6 4 | paho-mqtt==1.3.1 5 | pymodbus==1.4.0 6 | pyserial==3.4 7 | requests==2.31.0 8 | six==1.11.0 9 | urllib3==1.26.5 10 | influxdb==5.2.1 11 | pyatmo==2.1.1 12 | pigpio-dht==0.3.2 13 | pigpio==1.45 14 | PyZE==0.6.0 15 | -------------------------------------------------------------------------------- /secrets.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ZOE_VIN = "" 4 | ZOE_ZE_USERNAME = "" 5 | ZOE_ZE_PASSWORD = "" 6 | 7 | KAMEREON_API_KEY = "" 8 | GIGYA_API_KEY = "" 9 | 10 | NETATMO_CLIENT_ID = "" 11 | NETATMO_CLIENT_SECRET = "" 12 | NETATMO_USERNAME = "" 13 | NETATMO_PASSWORD = "" 14 | 15 | TANKERKOENIG_API_KEY = "" 16 | -------------------------------------------------------------------------------- /supervisor/battery-controller.conf: -------------------------------------------------------------------------------- 1 | [program:battery-controller] 2 | command=/root/mqtt-connectors/venv/bin/python3 battery-controller.py 3 | directory=/root/mqtt-connectors 4 | autostart=true 5 | autorestart=true 6 | startretries=3 7 | stderr_logfile=/var/log/supervisor/battery-controller.err.log 8 | stdout_logfile=/var/log/supervisor/battery-controller.out.log 9 | user=root -------------------------------------------------------------------------------- /supervisor/battery.conf: -------------------------------------------------------------------------------- 1 | [program:battery] 2 | command=/root/mqtt-connectors/venv/bin/python3 battery-connector.py 3 | directory=/root/mqtt-connectors 4 | autostart=true 5 | autorestart=true 6 | startretries=3 7 | stderr_logfile=/var/log/supervisor/battery-connector.err.log 8 | stdout_logfile=/var/log/supervisor/battery-connector.out.log 9 | user=root -------------------------------------------------------------------------------- /supervisor/energymeter.conf: -------------------------------------------------------------------------------- 1 | [program:energymeter] 2 | command=/root/mqtt-connectors/venv/bin/python3 energymeter-connector.py 3 | directory=/root/mqtt-connectors 4 | autostart=true 5 | autorestart=true 6 | startretries=3 7 | stderr_logfile=/var/log/supervisor/energymeter-connector.err.log 8 | stdout_logfile=/var/log/supervisor/energymeter-connector.out.log 9 | user=root -------------------------------------------------------------------------------- /supervisor/fronius.conf: -------------------------------------------------------------------------------- 1 | [program:fronius] 2 | command=/root/mqtt-connectors/venv/bin/python3 fronius-connector.py 3 | directory=/root/mqtt-connectors 4 | autostart=true 5 | autorestart=true 6 | startretries=3 7 | stderr_logfile=/var/log/supervisor/fronius-connector.err.log 8 | stdout_logfile=/var/log/supervisor/fronius-connector.out.log 9 | user=root -------------------------------------------------------------------------------- /supervisor/goecharger.conf: -------------------------------------------------------------------------------- 1 | [program:goecharger] 2 | command=/root/mqtt-connectors/venv/bin/python3 goecharger-connector.py 3 | directory=/root/mqtt-connectors 4 | autostart=true 5 | autorestart=true 6 | startretries=3 7 | stderr_logfile=/var/log/supervisor/goecharger-connector.err.log 8 | stdout_logfile=/var/log/supervisor/goecharger-connector.out.log 9 | user=root -------------------------------------------------------------------------------- /supervisor/heater.conf: -------------------------------------------------------------------------------- 1 | [program:heater] 2 | command=/root/mqtt-connectors/venv/bin/python3 heater-connector.py 3 | directory=/root/mqtt-connectors 4 | autostart=true 5 | autorestart=true 6 | startretries=3 7 | stderr_logfile=/var/log/supervisor/heater-connector.err.log 8 | stdout_logfile=/var/log/supervisor/heater-connector.out.log 9 | user=root -------------------------------------------------------------------------------- /supervisor/influxdb.conf: -------------------------------------------------------------------------------- 1 | [program:influxdb] 2 | command=/root/mqtt-connectors/venv/bin/python3 influxdb-connector.py 3 | directory=/root/mqtt-connectors 4 | autostart=true 5 | autorestart=true 6 | startretries=3 7 | stderr_logfile=/var/log/supervisor/influxdb-connector.err.log 8 | stdout_logfile=/var/log/supervisor/influxdb-connector.out.log 9 | user=root 10 | -------------------------------------------------------------------------------- /supervisor/local-dht.conf: -------------------------------------------------------------------------------- 1 | [program:local-dht-connector] 2 | command=/home/pi/mqtt-connectors/venv/bin/python3 local-dht-connector.py 3 | directory=/home/pi/mqtt-connectors 4 | autostart=true 5 | autorestart=true 6 | startretries=3 7 | stderr_logfile=/var/log/supervisor/local-dht-connector.err.log 8 | stdout_logfile=/var/log/supervisor/local-dht-connector.out.log 9 | user=pi -------------------------------------------------------------------------------- /supervisor/local-gas.conf: -------------------------------------------------------------------------------- 1 | [program:local-gas-connector] 2 | command=/home/pi/mqtt-connectors/venv/bin/python3 local-gas-connector.py 3 | directory=/home/pi/mqtt-connectors 4 | autostart=true 5 | autorestart=true 6 | startretries=3 7 | stderr_logfile=/var/log/supervisor/local-gas-connector.err.log 8 | stdout_logfile=/var/log/supervisor/local-gas-connector.out.log 9 | user=pi -------------------------------------------------------------------------------- /supervisor/netatmo.conf: -------------------------------------------------------------------------------- 1 | [program:netatmo] 2 | command=/root/mqtt-connectors/venv/bin/python3 netatmo-connector.py 3 | directory=/root/mqtt-connectors 4 | autostart=true 5 | autorestart=true 6 | startretries=3 7 | stderr_logfile=/var/log/supervisor/netatmo-connector.err.log 8 | stdout_logfile=/var/log/supervisor/netatmo-connector.out.log 9 | user=root -------------------------------------------------------------------------------- /supervisor/tankstellen.conf: -------------------------------------------------------------------------------- 1 | [program:tankstellen] 2 | command=/root/mqtt-connectors/venv/bin/python3 tankstellen-connector.py 3 | directory=/root/mqtt-connectors 4 | autostart=true 5 | autorestart=true 6 | startretries=3 7 | stderr_logfile=/var/log/supervisor/tankstellen-connector.err.log 8 | stdout_logfile=/var/log/supervisor/tankstellen-connector.out.log 9 | user=root 10 | -------------------------------------------------------------------------------- /supervisor/vcontrold.conf: -------------------------------------------------------------------------------- 1 | [program:vcontrold] 2 | command=/root/mqtt-connectors/venv/bin/python3 vcontrold-connector.py 3 | directory=/root/mqtt-connectors 4 | autostart=true 5 | autorestart=true 6 | startretries=3 7 | stderr_logfile=/var/log/supervisor/vcontrold-connector.err.log 8 | stdout_logfile=/var/log/supervisor/vcontrold-connector.out.log 9 | user=root 10 | -------------------------------------------------------------------------------- /supervisor/zoe.conf: -------------------------------------------------------------------------------- 1 | [program:zoe] 2 | command=/root/mqtt-connectors/venv/bin/python3 zoe-connector.py 3 | directory=/root/mqtt-connectors 4 | autostart=true 5 | autorestart=true 6 | startretries=3 7 | startsecs=60 8 | stderr_logfile=/var/log/supervisor/zoe-connector.err.log 9 | stdout_logfile=/var/log/supervisor/zoe-connector.out.log 10 | user=root -------------------------------------------------------------------------------- /tankstellen-connector.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Fetches diesel prices""" 4 | 5 | import paho.mqtt.client as paho # pip install paho-mqtt 6 | import time 7 | import logging 8 | import sys 9 | import requests 10 | from pathlib import Path 11 | 12 | from config import * 13 | from secrets import * 14 | 15 | 16 | FREQUENCY = 3600 # 1h 17 | TIMEOUT = 10 #sec 18 | 19 | 20 | def fetch_data(): 21 | 22 | # 73ce263a-8b6a-4b3f-b283-a1f4dc0925c4 Aral Tankstelle Darmstädter Str. 23 | # 51d4b70c-a095-1aa0-e100-80009459e03a Supermarkt-Tankstelle WEITERSTADT IM ROEDLING 8 A 24 | # 213e33be-8b98-4a3f-8f52-fec1edbb6403 Shell Buettelborn A67 Buettelborn Sued 25 | # de607d7b-9acf-4135-9568-7f0ea8e7121d Shell Buettelborn Im Mehlsee 5 26 | 27 | values = {} 28 | 29 | tankstellen = ['73ce263a-8b6a-4b3f-b283-a1f4dc0925c4', '51d4b70c-a095-1aa0-e100-80009459e03a', 'de607d7b-9acf-4135-9568-7f0ea8e7121d'] 30 | 31 | try: 32 | tankstellenlist = ','.join(tankstellen) 33 | url = f"https://creativecommons.tankerkoenig.de/json/prices.php?ids={tankstellenlist}&apikey={TANKERKOENIG_API_KEY}" # noqa E501 34 | r = requests.get(url, timeout=TIMEOUT) 35 | r.raise_for_status() 36 | data = r.json() 37 | 38 | if not data['ok']: 39 | raise RuntimeError('tankerkoenig result not ok') 40 | 41 | if 'diesel' in data['prices']['73ce263a-8b6a-4b3f-b283-a1f4dc0925c4']: 42 | values['aral'] = data['prices']['73ce263a-8b6a-4b3f-b283-a1f4dc0925c4']['diesel'] 43 | 44 | if 'diesel' in data['prices']['51d4b70c-a095-1aa0-e100-80009459e03a']: 45 | values['metro'] = data['prices']['51d4b70c-a095-1aa0-e100-80009459e03a']['diesel'] 46 | 47 | if 'diesel' in data['prices']['de607d7b-9acf-4135-9568-7f0ea8e7121d']: 48 | values['shell'] = data['prices']['de607d7b-9acf-4135-9568-7f0ea8e7121d']['diesel'] 49 | 50 | except requests.exceptions.Timeout: 51 | logging.error(f"Timeout requesting {url}") 52 | except requests.exceptions.RequestException as e: 53 | logging.error(f"requests exception {e}") 54 | 55 | return values 56 | 57 | 58 | def update(): 59 | values = fetch_data() 60 | for k, v in values.items(): 61 | (result, mid) = mqttc.publish(f"{DIESEL_MQTT_PREFIX}/{k}", str(v), 0, retain=True) # noqa E501 62 | logging.info(f"Pubish Result: {result} MID: {mid} for {k}: {v}") # noqa E501 63 | 64 | 65 | if __name__ == '__main__': 66 | logging.basicConfig(stream=sys.stdout, 67 | format='%(asctime)s %(levelname)-8s %(message)s', 68 | datefmt='%Y-%m-%d %H:%M:%S', 69 | level=logging.INFO) 70 | 71 | mqttc = paho.Client(f'{Path(__file__).stem}-connector', clean_session=True) 72 | # mqttc.enable_logger() 73 | mqttc.will_set(f"{DIESEL_MQTT_PREFIX}/connectorstatus", "Connector: LOST_CONNECTION", 0, retain=True) # noqa E501 74 | 75 | mqttc.connect(BROKER_HOST, BROKER_PORT, 60) 76 | logging.info(f"Connected to {BROKER_HOST}:{BROKER_PORT}") 77 | 78 | mqttc.publish(f"{DIESEL_MQTT_PREFIX}/connectorstatus", "Connector: ON-LINE", retain=True) # noqa E501 79 | 80 | mqttc.loop_start() 81 | while True: 82 | try: 83 | update() 84 | time.sleep(FREQUENCY) 85 | except KeyboardInterrupt: 86 | break 87 | except Exception: 88 | raise 89 | 90 | mqttc.publish(f"{DIESEL_MQTT_PREFIX}/connectorstatus", "Connector: OFF-LINE", retain=True) # noqa E501 91 | 92 | mqttc.disconnect() 93 | mqttc.loop_stop() # waits, until DISCONNECT message is sent out 94 | logging.info(f"Disconnected from to {BROKER_HOST}:{BROKER_PORT}") 95 | -------------------------------------------------------------------------------- /vcontrold-connector.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import paho.mqtt.client as paho # pip install paho-mqtt 4 | import time 5 | import logging 6 | import sys 7 | import subprocess 8 | from pathlib import Path 9 | from config import * 10 | 11 | 12 | FREQUENCY = 5 # query ever x sec 13 | 14 | 15 | def viessmann_data(): 16 | values = {} 17 | 18 | commands = "getTempVListM1,getTempRL17A,getTempWWist,getPumpeStatusM1,getBrennerStatus,getBrennerStarts,getBrennerStunden1" 19 | 20 | # vclient -h localhost:3002 -c getPumpeStatusM1,getTempWWist -t vcontrold-connector.tmpl 21 | p = subprocess.run(["/usr/local/bin/vclient", "-h", "localhost:3002", "-c", commands, "-t", "/root/mqtt-connectors/vcontrold-connector.tmpl"], capture_output=True, text=True) 22 | 23 | for line in p.stdout.splitlines(): 24 | key, value = line.split(":") 25 | values[key] = value 26 | 27 | return values 28 | 29 | 30 | if __name__ == '__main__': 31 | logging.basicConfig(stream=sys.stderr, level=logging.INFO) 32 | 33 | mqttc = paho.Client('vcontrold-connector', clean_session=True) 34 | # mqttc.enable_logger() 35 | mqttc.will_set("{}/connectorstatus".format(VCONTROLD_MQTT_PREFIX), "vcontrold Connector: LOST_CONNECTION", 0, retain=True) 36 | 37 | mqttc.connect(BROKER_HOST, BROKER_PORT, 60) 38 | logging.info("Connected to {}:{}".format(BROKER_HOST, BROKER_PORT)) 39 | 40 | mqttc.publish("{}/connectorstatus".format(VCONTROLD_MQTT_PREFIX), "vcontrold Connector: ON-LINE", retain=True) 41 | 42 | mqttc.loop_start() 43 | while True: 44 | try: 45 | values = viessmann_data() 46 | for k, v in values.items(): 47 | (result, mid) = mqttc.publish("{}/{}".format(VCONTROLD_MQTT_PREFIX, k), str(v), 0) 48 | logging.debug("Pubish Result: {} MID: {} for {}: {}".format(result, mid, k, v)) # noqa E501 49 | 50 | time.sleep(FREQUENCY) 51 | except KeyboardInterrupt: 52 | break 53 | except Exception: 54 | raise 55 | 56 | mqttc.publish("{}/connectorstatus".format(VCONTROLD_MQTT_PREFIX), "vcontrold Connector: OFF-LINE", retain=True) 57 | 58 | mqttc.disconnect() 59 | mqttc.loop_stop() # waits, until DISCONNECT message is sent out 60 | logging.info("Disconnected from to {}:{}".format(BROKER_HOST, BROKER_PORT)) 61 | -------------------------------------------------------------------------------- /vcontrold-connector.tmpl: -------------------------------------------------------------------------------- 1 | $C1:$1 2 | $C2:$2 3 | $C3:$3 4 | $C4:$R4 5 | $C5:$5 6 | $C6:$6 7 | $C7:$7 8 | -------------------------------------------------------------------------------- /weathermath.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python 2 | # coding: utf-8 3 | 4 | ''' 5 | Source 6 | http://www.wetterochs.de/wetter/feuchte.html 7 | 8 | Bezeichnungen: 9 | r = relative Luftfeuchte 10 | T = Temperatur in C 11 | TK = Temperatur in Kelvin (TK = T + 273.15) 12 | TD = Taupunkttemperatur in °C 13 | DD = Dampfdruck in hPa 14 | SDD = Sättigungsdampfdruck in hPa 15 | 16 | Parameter: 17 | a = 7.5, b = 237.3 für T >= 0 18 | a = 7.6, b = 240.7 für T < 0 über Wasser (Taupunkt) 19 | a = 9.5, b = 265.5 für T < 0 über Eis (Frostpunkt) 20 | 21 | R* = 8314.3 J/(kmol*K) (universelle Gaskonstante) 22 | mw = 18.016 kg/kmol (Molekulargewicht des Wasserdampfes) 23 | AF = absolute Feuchte in g Wasserdampf pro m3 Luft 24 | 25 | Formeln: 26 | 27 | SDD(T) = 6.1078 * 10^((a*T)/(b+T)) 28 | 29 | DD(r,T) = r/100 * SDD(T) 30 | 31 | r(T,TD) = 100 * SDD(TD) / SDD(T) 32 | 33 | TD(r,T) = b*v/(a-v) mit v(r,T) = log10(DD(r,T)/6.1078) 34 | 35 | AF(r,TK) = 10^5 * mw/R* * DD(r,T)/TK; AF(TD,TK) = 10^5 * mw/R* * SDD(TD)/TK 36 | ''' 37 | 38 | import math 39 | 40 | R = 8314.3 41 | mw = 18.016 42 | 43 | def SDD(T): 44 | if (T >= 0.0): 45 | a = 7.5 46 | b = 237.3 47 | else: 48 | a = 7.6 49 | b = 240.7 50 | 51 | return 6.1078 * math.pow( 10, ( (a*T)/(b+T) ) ) 52 | 53 | def DD(r, T): 54 | return (r/100.0) * SDD(T) 55 | 56 | def AF(r, T): 57 | TK = T + 273.15 58 | return 100000 * mw/R * DD(r, T)/TK 59 | 60 | -------------------------------------------------------------------------------- /zoe-connector.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import paho.mqtt.client as paho # pip install paho-mqtt 4 | import time 5 | import logging 6 | import sys 7 | from pyze.api import Gigya, Kamereon, Vehicle 8 | 9 | from config import * 10 | from secrets import * 11 | 12 | 13 | FREQUENCY = 1200 # sec 14 | EXCEPTION_DELAY = 300 15 | 16 | 17 | def getSocRange(gigya): 18 | 19 | k = Kamereon(api_key=KAMEREON_API_KEY, gigya=gigya, country='DE') 20 | v = Vehicle(ZOE_VIN, k) 21 | 22 | b = v.battery_status() 23 | soc = b['batteryLevel'] 24 | remaining_range = b['batteryAutonomy'] 25 | 26 | logging.info("Zoe API: soc: {}%, range: {}km".format(soc, remaining_range)) 27 | 28 | return soc, remaining_range 29 | 30 | 31 | def update(gigya): 32 | soc, remaining_range = getSocRange(gigya) 33 | 34 | (result, mid) = mqttc.publish("{}/{}".format(ZOE_MQTT_PREFIX, 'soc'), str(soc), 0, retain=True) 35 | # logging.info("Pubish Result: {} MID: {} for {}: {}".format(result, mid, 'soc', soc)) 36 | 37 | (result, mid) = mqttc.publish("{}/{}".format(ZOE_MQTT_PREFIX, 'range'), str(remaining_range), 0, retain=True) 38 | # logging.info("Pubish Result: {} MID: {} for {}: {}".format(result, mid, 'range', remaining_range)) 39 | 40 | 41 | if __name__ == '__main__': 42 | logging.basicConfig(stream=sys.stdout, 43 | format='%(asctime)s %(levelname)-8s %(message)s', 44 | datefmt='%Y-%m-%d %H:%M:%S', 45 | level=logging.INFO) 46 | 47 | mqttc = paho.Client('zoe-connector', clean_session=True) 48 | # mqttc.enable_logger() 49 | 50 | try: 51 | g = Gigya(api_key=GIGYA_API_KEY) 52 | g.login(ZOE_ZE_USERNAME, ZOE_ZE_PASSWORD) # You should only need to do this once 53 | g.account_info() # Retrieves and caches person ID 54 | except Exception: 55 | logging.exception("Exception during Gigya login, sleeping 30s") 56 | time.sleep(30) 57 | raise 58 | 59 | mqttc.connect(BROKER_HOST, BROKER_PORT, 60) 60 | logging.info("Connected to {}:{}".format(BROKER_HOST, BROKER_PORT)) 61 | 62 | exception_counter = 0 63 | 64 | mqttc.loop_start() 65 | while True: 66 | try: 67 | update(g) 68 | time.sleep(FREQUENCY) 69 | except KeyboardInterrupt: 70 | logging.warning("Keyboard interruption") 71 | break 72 | except Exception: 73 | exception_counter = exception_counter + 1 74 | if exception_counter > 24: 75 | logging.exception("Exception occured in main loop too many times ({exception_counter}), giving up....") 76 | raise 77 | else: 78 | logging.exception(f"Exception occured in main loop, retrying after {EXCEPTION_DELAY} sec") 79 | 80 | time.sleep(EXCEPTION_DELAY) 81 | 82 | mqttc.disconnect() 83 | mqttc.loop_stop() # waits, until DISCONNECT message is sent out 84 | logging.info("Disconnected from to {}:{}".format(BROKER_HOST, BROKER_PORT)) 85 | 86 | --------------------------------------------------------------------------------