Initial revision of fronius2mqtt

This commit is contained in:
Gerrit Beine 2018-12-08 18:00:32 +01:00
commit 76cc7371b1
16 changed files with 331 additions and 0 deletions

8
.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
__pycache__
bin
lib
fronius2mqtt.bbprojectd
pip-selfcheck.json
pyvenv.cfg
fronius2mqtt/fronius2mqtt.egg-info
fronius2mqtt.yaml

13
bin/start.py Executable file
View file

@ -0,0 +1,13 @@
#!/usr/bin/env python3
from fronius2mqtt import config
from fronius2mqtt import daemon
def main():
cfg = config.Config()
cfg.read()
d = daemon.Daemon(cfg)
d.run()
main()

26
fronius2mqtt.yaml.example Normal file
View file

@ -0,0 +1,26 @@
mqtt:
host: localhost
port: 1883
user: user
password: secret
topic: "mqtt/topic/for/fronius"
fronius:
- inverter:
host: "inverter.myfroniusfarm"
device: 1
topic: "inverter_1"
- inverter:
host: "inverter.myfroniusfarm"
device: 2
topic: "inverter_2"
- storage:
host: "storage.myfroniusfarm"
device: 0
topic: "battery_1"
- meter:
host: "storage.myfroniusfarm"
device: 0
topic: "meter"
- flow:
host: "storage.myfroniusfarm"
topic: "flow"

View file

View file

@ -0,0 +1,59 @@
import yaml
import logging
import logging.config
class Config:
"""Class for parsing fronius2mqtt.yaml."""
def __init__(self):
"""Initialize Config class."""
logging.config.fileConfig('logging.conf')
self._mqtt = {}
self._fronius = {}
def read(self, file='fronius2mqtt.yaml'):
"""Read config."""
logging.debug("Reading %s", file)
try:
with open(file, 'r') as filehandle:
config = yaml.load(filehandle)
self._parse_mqtt(config)
self._parse_fronius(config)
except FileNotFoundError as ex:
logging.error("Error while reading %s: %s", file, ex)
def _parse_mqtt(self, config):
"""Parse the mqtt section of fronius2mqtt.yaml."""
if "mqtt" in config:
self._mqtt = config["mqtt"]
if not "host" in self._mqtt:
raise ValueError("MQTT host not set")
if not "port" in self._mqtt:
raise ValueError("MQTT port not set")
if not "user" in self._mqtt:
raise ValueError("MQTT user not set")
if not "password" in self._mqtt:
raise ValueError("MQTT password not set")
if not "topic" in self._mqtt:
raise ValueError("MQTT topic not set")
def _parse_fronius(self, config):
"""Parse the fronius section of fronius2mqtt.yaml."""
if "fronius" in config:
self._fronius = config["fronius"]
for item in self._fronius:
if len(item) != 1:
raise ValueError("Fronius device configuration contains more than one item.")
for (type, properties) in item.items():
if not "host" in properties:
raise ValueError("Missing host for Fronius device")
if not "topic" in properties:
raise ValueError("Missing topic for Fronius device")
def mqtt(self):
return self._mqtt
def fronius(self):
return self._fronius

View file

@ -0,0 +1,27 @@
import time
from fronius2mqtt import froniusfactory
from fronius2mqtt import mqtt
class Daemon:
def __init__(self, config):
self.config = config
self.devices = []
self._init_mqtt()
self._init_fronius()
def run(self):
while True:
for device in self.devices:
device.update_and_publish(self.mqtt)
time.sleep(5)
def _init_mqtt(self):
self.mqtt = mqtt.Mqtt(self.config.mqtt())
self.mqtt.connect()
def _init_fronius(self):
factory = froniusfactory.FroniusFactory(self.config.fronius())
self.devices = factory.create_devices()

View file

@ -0,0 +1,16 @@
import logging
class Device:
def update(self):
raise NotImplementedError("update not implemented")
def update_and_publish(self, mqtt):
data = self.update()
for (key, value) in data.items():
if 'value' in value:
mqtt.publish("{}/{}".format(self.topic, key), value['value'])
else:
logging.info("Ignore %s: %s", key, value)

View file

@ -0,0 +1,14 @@
from pyfronius import fronius
from fronius2mqtt import device
class Flow(device.Device):
def __init__(self, config):
self.host = config['host']
self.topic = config['topic']
self.fronius = fronius.Fronius(self.host)
def update(self):
data = self.fronius.current_power_flow()
return data

View file

@ -0,0 +1,43 @@
from fronius2mqtt import inverter
from fronius2mqtt import storage
from fronius2mqtt import flow
from fronius2mqtt import meter
class FroniusFactory:
def __init__(self, config):
self.config = config
def create_devices(self):
devices = []
for c in self.config:
(type, properties) = c.popitem()
if not type in factory:
raise ValueError("Not a valid device: {}".format(type))
device = factory[type](properties)
devices.append(device)
return devices
def _create_inverter(properties):
device = inverter.Inverter(properties)
return device
def _create_storage(properties):
device = storage.Storage(properties)
return device
def _create_meter(properties):
device = meter.Meter(properties)
return device
def _create_flow(properties):
device = flow.Flow(properties)
return device
factory = {
"inverter" : _create_inverter,
"storage" : _create_storage,
"meter" : _create_meter,
"flow" : _create_flow
}

View file

@ -0,0 +1,19 @@
from pyfronius import fronius
from fronius2mqtt import device
class Inverter(device.Device):
def __init__(self, config):
self.host = config['host']
if 'device' in config:
self.device = config['device']
self.topic = config['topic']
self.fronius = fronius.Fronius(self.host)
def update(self):
if hasattr(self, 'device'):
data = self.fronius.current_inverter_data(self.device)
else:
data = self.fronius.current_system_inverter_data()
return data

View file

@ -0,0 +1,19 @@
from pyfronius import fronius
from fronius2mqtt import device
class Meter(device.Device):
def __init__(self, config):
self.host = config['host']
if 'device' in config:
self.device = config['device']
self.topic = config['topic']
self.fronius = fronius.Fronius(self.host)
def update(self):
if hasattr(self, 'device'):
data = self.fronius.current_meter_data(self.device)
else:
data = self.fronius.current_system_meter_data()
return data

View file

@ -0,0 +1,22 @@
import logging
import paho.mqtt.client as mqtt
class Mqtt:
def __init__(self, config):
self.config = config
def connect(self):
self.client = mqtt.Client()
self.client.username_pw_set(self.config['user'], self.config['password'])
self.client.connect(self.config['host'], self.config['port'])
self.client.loop_start()
def disconnect(self):
self.client.disconnect()
def publish(self, topic, payload):
topic = "{}/{}".format(self.config['topic'], topic)
logging.info("Publish %s: %s", topic, payload)
self.client.publish(topic, payload)

View file

@ -0,0 +1,18 @@
from pyfronius import fronius
from fronius2mqtt import device
class Storage(device.Device):
def __init__(self, config):
self.host = config['host']
if 'device' in config:
self.device = config['device']
else:
self.device = 0
self.topic = config['topic']
self.fronius = fronius.Fronius(self.host)
def update(self):
data = self.fronius.current_storage_data(self.device)
return data

17
fronius2mqtt/setup.py Normal file
View file

@ -0,0 +1,17 @@
from setuptools import setup
setup(name='fronius2mqtt',
version='0.1',
description='Fronius 2 MQTT bridge',
url='https://github.com/gbeine/fronius2mqtt',
author='Gerrit',
author_email='mail@gerritbeine.de',
license='MIT',
packages=['fronius2mqtt'],
requires=[
'logging',
'paho.mqtt',
'pyfronius',
'pyyaml',
],
zip_safe=False)

9
install Normal file
View file

@ -0,0 +1,9 @@
#!/bin/sh
python3 -m venv .
git clone https://github.com/gbeine/pyfronius.git
source bin/activate
pip install -e pyfronius
pip install -e fronius2mqtt

21
logging.conf Normal file
View file

@ -0,0 +1,21 @@
[loggers]
keys=root
[handlers]
keys=consoleHandler
[formatters]
keys=simpleFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s