Complete rewrite, uses Fronius Push API now
This commit contains a complete rewrite - HTTP push API server based on bottle - no more dependency to pyfronius
This commit is contained in:
parent
f486503b1e
commit
b4a833601f
26 changed files with 344 additions and 349 deletions
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -1,10 +1,6 @@
|
||||||
__pycache__
|
|
||||||
bin
|
bin
|
||||||
|
include
|
||||||
lib
|
lib
|
||||||
share
|
share
|
||||||
fronius2mqtt.bbprojectd
|
|
||||||
pip-selfcheck.json
|
|
||||||
pyvenv.cfg
|
pyvenv.cfg
|
||||||
pyfronius
|
.DS_Store
|
||||||
fronius2mqtt/fronius2mqtt.egg-info
|
|
||||||
fronius2mqtt.yaml
|
|
||||||
|
|
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
14
.idea/fronius2mqtt.iml
Normal file
14
.idea/fronius2mqtt.iml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3.9 (fronius2mqtt)" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
<component name="PyDocumentationSettings">
|
||||||
|
<option name="format" value="PLAIN" />
|
||||||
|
<option name="myDocStringFormat" value="Plain" />
|
||||||
|
</component>
|
||||||
|
</module>
|
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
4
.idea/misc.xml
Normal file
4
.idea/misc.xml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (fronius2mqtt)" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/fronius2mqtt.iml" filepath="$PROJECT_DIR$/.idea/fronius2mqtt.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
25
README.md
25
README.md
|
@ -1,6 +1,13 @@
|
||||||
# fronius2mqtt
|
# fronius2mqtt
|
||||||
|
|
||||||
A Fronius HTTP API to MQTT bridge
|
A Fronius HTTP API to MQTT bridge
|
||||||
|
|
||||||
|
Attention: This is a complete rewrite of the bridge.
|
||||||
|
While the old fronius2mqtt brigde used polling the inverters to fetch the data, the new version make use of the push feature provided by Fronius Symo.
|
||||||
|
|
||||||
|
The daemon offers no HTTP endpoints which can be configured in the configuration interface of each inverter.
|
||||||
|
It forwards the data directly to MQTT.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
- clone the git repository
|
- clone the git repository
|
||||||
|
@ -9,5 +16,21 @@ A Fronius HTTP API to MQTT bridge
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
- copy ```fronius2mqtt.yaml.example```
|
Each configuration option is also available as command line argument.
|
||||||
|
|
||||||
|
- copy ```fronius2mqtt.conf.example```
|
||||||
- configure as you like
|
- configure as you like
|
||||||
|
|
||||||
|
| option | default | arguments | comment |
|
||||||
|
|----------------|--------------------------|---------------------|--------------------------------------------------------------------|
|
||||||
|
| mqtt_host | 'localhost' | -m, --mqtt_host | The hostname of the MQTT server. |
|
||||||
|
| mqtt_port | 1883 | --mqtt_port | The port of the MQTT server. |
|
||||||
|
| mqtt_keepalive | 30 | --mqtt_keepalive | The keep alive interval for the MQTT server connection in seconds. |
|
||||||
|
| mqtt_clientid | 'fronius2mqtt' | --mqtt_clientid | The clientid to send to the MQTT server. |
|
||||||
|
| mqtt_user | - | -u, --mqtt_user | The username for the MQTT server connection. |
|
||||||
|
| mqtt_password | - | -p, --mqtt_password | The password for the MQTT server connection. |
|
||||||
|
| mqtt_topic | 'fronius' | -t, --mqtt_topic | The topic to publish MQTT message. |
|
||||||
|
| http_host | 'localhost' | --http_host | The address of the HTTP server. |
|
||||||
|
| http_port | 8080 | --http_port | The port of the HTTP server. |
|
||||||
|
| verbose | - | -v, --verbose | Be verbose while running. |
|
||||||
|
| - | '/etc/fronius2mqtt.conf' | -c, --config | The path to the config file. |
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
#!/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()
|
|
||||||
|
|
260
fronius2mqtt
Executable file
260
fronius2mqtt
Executable file
|
@ -0,0 +1,260 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import paho.mqtt.client as mqtt
|
||||||
|
|
||||||
|
from bottle import request, route, post, run
|
||||||
|
|
||||||
|
|
||||||
|
mqtt_client = None
|
||||||
|
daemon_args = None
|
||||||
|
|
||||||
|
|
||||||
|
def extract_request_body():
|
||||||
|
body = request.body
|
||||||
|
string = body.getvalue().decode('utf-8')
|
||||||
|
return string
|
||||||
|
|
||||||
|
|
||||||
|
def extract_request_data():
|
||||||
|
json_string = extract_request_body()
|
||||||
|
data = json.loads(json_string)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
@route('/')
|
||||||
|
def index():
|
||||||
|
return "Hello World!"
|
||||||
|
|
||||||
|
|
||||||
|
@post('/current_data_inverter/<device>')
|
||||||
|
def current_data_inverter(device):
|
||||||
|
data = extract_request_data()
|
||||||
|
if not 'Body' in data:
|
||||||
|
return "Empty"
|
||||||
|
topic_base = "{}/{}/current_data_inverter".format(daemon_args.mqtt_topic, device)
|
||||||
|
if 'PAC' in data['Body'] and 'Values' in data['Body']['PAC']:
|
||||||
|
for k, v in data['Body']['PAC']['Values'].items():
|
||||||
|
topic = "{}/pac/{}".format(topic_base, k)
|
||||||
|
mqtt_client.publish(topic, v)
|
||||||
|
if 'DAY_ENERGY' in data['Body'] and 'Values' in data['Body']['DAY_ENERGY']:
|
||||||
|
for k, v in data['Body']['DAY_ENERGY']['Values'].items():
|
||||||
|
topic = "{}/day_energy/{}".format(topic_base, k)
|
||||||
|
mqtt_client.publish(topic, v)
|
||||||
|
if 'YEAR_ENERGY' in data['Body'] and 'Values' in data['Body']['YEAR_ENERGY']:
|
||||||
|
for k, v in data['Body']['YEAR_ENERGY']['Values'].items():
|
||||||
|
topic = "{}/year_energy/{}".format(topic_base, k)
|
||||||
|
mqtt_client.publish(topic, v)
|
||||||
|
if 'TOTAL_ENERGY' in data['Body'] and 'Values' in data['Body']['TOTAL_ENERGY']:
|
||||||
|
for k, v in data['Body']['TOTAL_ENERGY']['Values'].items():
|
||||||
|
topic = "{}/total_energy/{}".format(topic_base, k)
|
||||||
|
mqtt_client.publish(topic, v)
|
||||||
|
return "OK"
|
||||||
|
|
||||||
|
|
||||||
|
@post('/current_data_meter/<device>')
|
||||||
|
def current_data_meter(device):
|
||||||
|
data = extract_request_data()
|
||||||
|
if not 'Body' in data:
|
||||||
|
return "Empty"
|
||||||
|
topic_base = "{}/{}/current_data_meter".format(daemon_args.mqtt_topic, device)
|
||||||
|
for m, d in data['Body'].items():
|
||||||
|
for k, v in d.items():
|
||||||
|
if v is not None and type(v) in [int, float, str] :
|
||||||
|
topic = "{}/{}/{}".format(topic_base, m, k.lower())
|
||||||
|
mqtt_client.publish(topic, v)
|
||||||
|
return "OK"
|
||||||
|
|
||||||
|
|
||||||
|
@post('/current_data_powerflow/<device>')
|
||||||
|
def current_data_powerflow(device):
|
||||||
|
data = extract_request_data()
|
||||||
|
if not 'Body' in data:
|
||||||
|
return "Empty"
|
||||||
|
topic_base = "{}/{}/current_data_powerflow".format(daemon_args.mqtt_topic, device)
|
||||||
|
if 'Site' in data['Body']:
|
||||||
|
for k, v in data['Body']['Site'].items():
|
||||||
|
if v is not None:
|
||||||
|
topic = "{}/site/{}".format(topic_base, k.lower())
|
||||||
|
mqtt_client.publish(topic, v)
|
||||||
|
if 'Inverters' in data['Body']:
|
||||||
|
for i, d in data['Body']['Inverters'].items():
|
||||||
|
for k, v in d.items():
|
||||||
|
if v is not None:
|
||||||
|
topic = "{}/{}/{}".format(topic_base, i, k.lower())
|
||||||
|
mqtt_client.publish(topic, v)
|
||||||
|
return "OK"
|
||||||
|
|
||||||
|
|
||||||
|
@post('/current_data_storages/<device>')
|
||||||
|
def current_data_storages(device):
|
||||||
|
data = extract_request_data()
|
||||||
|
if not 'Body' in data:
|
||||||
|
return "Empty"
|
||||||
|
topic_base = "{}/{}/current_data_storages".format(daemon_args.mqtt_topic, device)
|
||||||
|
for s, d in data['Body'].items():
|
||||||
|
if 'Controller' in d:
|
||||||
|
for k, v in d['Controller'].items():
|
||||||
|
if v is not None and type(v) in [int, float, str] :
|
||||||
|
topic = "{}/{}/{}".format(topic_base, s, k.lower())
|
||||||
|
mqtt_client.publish(topic, v)
|
||||||
|
if 'Modules' in d:
|
||||||
|
for m in d['Modules']:
|
||||||
|
serial = m['Details']['Serial']
|
||||||
|
for k, v in m.items():
|
||||||
|
if v is not None and type(v) in [int, float, str] :
|
||||||
|
topic = "{}/{}/{}/{}".format(topic_base, s, serial.lower(), k.lower())
|
||||||
|
mqtt_client.publish(topic, v)
|
||||||
|
return "OK"
|
||||||
|
|
||||||
|
|
||||||
|
@post('/current_data_sensorcard/<device>')
|
||||||
|
def current_data_sensorcard(device):
|
||||||
|
data = extract_request_data()
|
||||||
|
if not 'Body' in data:
|
||||||
|
return "Empty"
|
||||||
|
topic_base = "{}/{}/current_data_sensorcard".format(daemon_args.mqtt_topic, device)
|
||||||
|
# TODO not yet implemented
|
||||||
|
return "OK"
|
||||||
|
|
||||||
|
|
||||||
|
@post('/current_data_stringcontrol/<device>')
|
||||||
|
def current_data_stringcontrol(device):
|
||||||
|
data = extract_request_data()
|
||||||
|
if not 'Body' in data:
|
||||||
|
return "Empty"
|
||||||
|
topic_base = "{}/{}/current_data_stringcontrol".format(daemon_args.mqtt_topic, device)
|
||||||
|
# TODO not yet implemented
|
||||||
|
return "OK"
|
||||||
|
|
||||||
|
|
||||||
|
@post('/datamanager_io_states/<device>')
|
||||||
|
def datamanager_io_states(device):
|
||||||
|
data = extract_request_data()
|
||||||
|
topic_base = "{}/{}/datamanager_io_states".format(daemon_args.mqtt_topic, device)
|
||||||
|
for p, d in data.items():
|
||||||
|
for k, v in d.items():
|
||||||
|
if v is not None:
|
||||||
|
topic = "{}/{}/{}".format(topic_base, p.replace(' ', '_'), k.lower())
|
||||||
|
mqtt_client.publish(topic, v)
|
||||||
|
return "OK"
|
||||||
|
|
||||||
|
|
||||||
|
@post('/logdata_errors_and_events/<device>')
|
||||||
|
def logdata_errors_and_events(device):
|
||||||
|
data = extract_request_data()
|
||||||
|
if not 'Body' in data:
|
||||||
|
return "Empty"
|
||||||
|
topic_base = "{}/{}/logdata_errors_and_events".format(daemon_args.mqtt_topic, device)
|
||||||
|
return "OK"
|
||||||
|
|
||||||
|
|
||||||
|
@post('/logdata_data/<device>')
|
||||||
|
def logdata_data(device):
|
||||||
|
data = extract_request_data()
|
||||||
|
if not 'Body' in data:
|
||||||
|
return "Empty"
|
||||||
|
topic_base = "{}/{}/logdata_data".format(daemon_args.mqtt_topic, device)
|
||||||
|
return "OK"
|
||||||
|
|
||||||
|
|
||||||
|
def start_mqtt():
|
||||||
|
global daemon_args
|
||||||
|
mqtt_client = mqtt.Client(daemon_args.mqtt_clientid)
|
||||||
|
if daemon_args.verbose:
|
||||||
|
mqtt_client.enable_logger()
|
||||||
|
if daemon_args.mqtt_user is not None and daemon_args.mqtt_password is not None:
|
||||||
|
mqtt_client.username_pw_set(daemon_args.mqtt_user, daemon_args.mqtt_password)
|
||||||
|
mqtt_client.connect(daemon_args.mqtt_host, daemon_args.mqtt_port, daemon_args.mqtt_keepalive)
|
||||||
|
mqtt_client.loop_start()
|
||||||
|
return mqtt_client
|
||||||
|
|
||||||
|
|
||||||
|
def start_http():
|
||||||
|
global daemon_args
|
||||||
|
run(host=daemon_args.http_host, port=daemon_args.http_port, debug=daemon_args.verbose)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog='fronius2mqtt',
|
||||||
|
description='Send the data from Fronius HTTP push to MQTT',
|
||||||
|
epilog='Have a lot of fun!')
|
||||||
|
parser.add_argument('-m', '--mqtt_host', type=str,
|
||||||
|
default='localhost',
|
||||||
|
help='The hostname of the MQTT server. Default is localhost')
|
||||||
|
parser.add_argument('--mqtt_port', type=int,
|
||||||
|
default=1883,
|
||||||
|
help='The port of the MQTT server. Default is 1883')
|
||||||
|
parser.add_argument('--mqtt_keepalive', type=int,
|
||||||
|
default=30,
|
||||||
|
help='The keep alive interval for the MQTT server connection in seconds. Default is 30')
|
||||||
|
parser.add_argument('--mqtt_clientid', type=str,
|
||||||
|
default='fronius2mqtt',
|
||||||
|
help='The clientid to send to the MQTT server. Default is fronius2mqtt')
|
||||||
|
parser.add_argument('-u', '--mqtt_user', type=str,
|
||||||
|
help='The username for the MQTT server connection.')
|
||||||
|
parser.add_argument('-p', '--mqtt_password', type=str,
|
||||||
|
help='The password for the MQTT server connection.')
|
||||||
|
parser.add_argument('-t', '--mqtt_topic', type=str,
|
||||||
|
default='home/fronius',
|
||||||
|
help='The topic to publish MQTT message. Default is home/fronius')
|
||||||
|
parser.add_argument('--http_host', type=str,
|
||||||
|
default='localhost',
|
||||||
|
help='The address of the HTTP server. Default is localhost')
|
||||||
|
parser.add_argument('--http_port', type=int,
|
||||||
|
default=8080,
|
||||||
|
help='The port of the HTTP server. Default is 8080')
|
||||||
|
parser.add_argument('-c', '--config', type=str,
|
||||||
|
default='/etc/fronius2mqtt.conf',
|
||||||
|
help='The path to the config file. Default is /etc/fronius2mqtt.conf')
|
||||||
|
parser.add_argument('-v', '--verbose',
|
||||||
|
default=False,
|
||||||
|
action='store_true',
|
||||||
|
help='Be verbose while running.')
|
||||||
|
args = parser.parse_args()
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def parse_config():
|
||||||
|
global daemon_args
|
||||||
|
|
||||||
|
if not os.path.isfile(daemon_args.config):
|
||||||
|
return
|
||||||
|
|
||||||
|
with open(daemon_args.config, "r") as config_file:
|
||||||
|
data = json.load(config_file)
|
||||||
|
if 'mqtt_host' in data:
|
||||||
|
daemon_args.mqtt_host = data['mqtt_host']
|
||||||
|
if 'mqtt_port' in data:
|
||||||
|
daemon_args.mqtt_port = data['mqtt_port']
|
||||||
|
if 'mqtt_keepalive' in data:
|
||||||
|
daemon_args.mqtt_keepalive = data['mqtt_keepalive']
|
||||||
|
if 'mqtt_clientid' in data:
|
||||||
|
daemon_args.mqtt_clientid = data['mqtt_clientid']
|
||||||
|
if 'mqtt_user' in data:
|
||||||
|
daemon_args.mqtt_user = data['mqtt_user']
|
||||||
|
if 'mqtt_password' in data:
|
||||||
|
daemon_args.mqtt_password = data['mqtt_password']
|
||||||
|
if 'mqtt_topic' in data:
|
||||||
|
daemon_args.mqtt_topic = data['mqtt_topic']
|
||||||
|
if 'http_host' in data:
|
||||||
|
daemon_args.http_host = data['http_host']
|
||||||
|
if 'http_port' in data:
|
||||||
|
daemon_args.http_port = data['http_port']
|
||||||
|
if 'verbose' in data:
|
||||||
|
daemon_args.verbose = data['verbose']
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
global daemon_args, mqtt_client
|
||||||
|
daemon_args = parse_args()
|
||||||
|
parse_config()
|
||||||
|
mqtt_client = start_mqtt()
|
||||||
|
start_http()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -1,13 +1,6 @@
|
||||||
[program:fronius2mqtt]
|
{
|
||||||
command=/opt/service/fronius2mqtt/run
|
"mqtt_user": "fronius2mqtt",
|
||||||
process_name=%(program_name)s
|
"mqtt_password": "t0p_s3cr3t",
|
||||||
directory=/opt/service/fronius2mqtt
|
"http_host": "0.0.0.0",
|
||||||
umask=022
|
"http_port": 80
|
||||||
autostart=true
|
}
|
||||||
redirect_stderr=true
|
|
||||||
stdout_logfile=/var/log/fronius2mqtt/main.log
|
|
||||||
stdout_logfile_maxbytes=2MB
|
|
||||||
stdout_logfile_backups=1
|
|
||||||
stdout_capture_maxbytes=0
|
|
||||||
stdout_events_enabled=false
|
|
||||||
environment=LOGDIR=/var/log/fronius2mqtt
|
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
mqtt:
|
|
||||||
host: localhost
|
|
||||||
port: 1883
|
|
||||||
user: user
|
|
||||||
password: secret
|
|
||||||
topic: "mqtt/topic/for/fronius"
|
|
||||||
qos: 1
|
|
||||||
retain: true
|
|
||||||
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"
|
|
|
@ -1,62 +0,0 @@
|
||||||
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")
|
|
||||||
if not "qos" in self._mqtt:
|
|
||||||
self._mqtt["qos"] = 0
|
|
||||||
if not "retain" in self._mqtt:
|
|
||||||
self._mqtt["retain"] = False
|
|
||||||
|
|
||||||
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
|
|
|
@ -1,27 +0,0 @@
|
||||||
|
|
||||||
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()
|
|
|
@ -1,16 +0,0 @@
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
|
|
||||||
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
|
|
|
@ -1,43 +0,0 @@
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
|
|
||||||
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
|
|
|
@ -1,19 +0,0 @@
|
||||||
|
|
||||||
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
|
|
|
@ -1,22 +0,0 @@
|
||||||
|
|
||||||
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, %s, %s", topic, payload, self._config["qos"], self._config["retain"])
|
|
||||||
self._client.publish(topic, payload, self._config["qos"], self._config["retain"])
|
|
|
@ -1,18 +0,0 @@
|
||||||
|
|
||||||
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
|
|
|
@ -1,17 +0,0 @@
|
||||||
from setuptools import setup
|
|
||||||
|
|
||||||
setup(name='fronius2mqtt',
|
|
||||||
version='0.2',
|
|
||||||
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)
|
|
10
install
10
install
|
@ -1,10 +1,8 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
python3 -m venv .
|
python3 -m venv venv
|
||||||
|
|
||||||
git clone https://github.com/gbeine/pyfronius.git
|
. venv/bin/activate
|
||||||
|
|
||||||
. bin/activate
|
pip install bottle
|
||||||
pip install wheel paho-mqtt pyyaml
|
pip install paho.mqtt
|
||||||
pip install -e pyfronius
|
|
||||||
pip install -e fronius2mqtt
|
|
||||||
|
|
21
logging.conf
21
logging.conf
|
@ -1,21 +0,0 @@
|
||||||
[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
|
|
6
run
6
run
|
@ -1,7 +1,5 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
touch ${LOGDIR}/.tmpfs
|
. venv/bin/activate
|
||||||
|
|
||||||
. bin/activate
|
exec /usr/bin/env python fronius2mqtt
|
||||||
|
|
||||||
exec bin/fronius2mqtt
|
|
||||||
|
|
Loading…
Reference in a new issue