mirror of https://github.com/lumapu/ahoy.git
Jan-Jonas Sämann
3 years ago
4 changed files with 161 additions and 0 deletions
@ -0,0 +1,138 @@ |
|||||
|
#!/usr/bin/env python3 |
||||
|
|
||||
|
import socket |
||||
|
from datetime import datetime, timezone |
||||
|
from hoymiles.decoders import StatusResponse |
||||
|
|
||||
|
try: |
||||
|
from influxdb_client import InfluxDBClient |
||||
|
except ModuleNotFoundError: |
||||
|
pass |
||||
|
|
||||
|
class OutputPluginFactory: |
||||
|
def __init__(self, **params): |
||||
|
"""Initialize output plugin""" |
||||
|
|
||||
|
self.inverter_ser = params.get('inverter_ser', 0) |
||||
|
|
||||
|
def store_status(self, data): |
||||
|
raise NotImplementedError('The current output plugin does not implement store_status') |
||||
|
|
||||
|
class InfluxOutputPlugin(OutputPluginFactory): |
||||
|
def __init__(self, url, token, **params): |
||||
|
super().__init__(**params) |
||||
|
|
||||
|
self._bucket = params.get('bucket', 'hoymiles/autogen') |
||||
|
self._org = params.get('org', '') |
||||
|
self._measurement = params.get('measurement', |
||||
|
f'inverter,host={socket.gethostname()}') |
||||
|
|
||||
|
client = InfluxDBClient(url, token, bucket=self._bucket) |
||||
|
self.api = client.write_api() |
||||
|
|
||||
|
def store_status(self, response): |
||||
|
""" |
||||
|
Publish StatusResponse object |
||||
|
|
||||
|
:param influxdb.InfluxDBClient influx_client: A connected instance to Influx database |
||||
|
:param str inverter_ser: inverter serial |
||||
|
:param hoymiles.StatusResponse data: decoded inverter StatusResponse |
||||
|
:type response: hoymiles.StatusResponse |
||||
|
:param measurement: Influx measurement name |
||||
|
:type measurement: str |
||||
|
""" |
||||
|
|
||||
|
if not isinstance(response, StatusResponse): |
||||
|
raise RuntimeError('Data needs to be instance of StatusResponse') |
||||
|
|
||||
|
data = response.__dict__() |
||||
|
|
||||
|
measurement = self._measurement + f',location={data["inverter_ser"]}' |
||||
|
|
||||
|
data_stack = [] |
||||
|
|
||||
|
time_rx = datetime.now() |
||||
|
if 'time' in data and isinstance(data['time'], datetime): |
||||
|
time_rx = data['time'] |
||||
|
|
||||
|
# InfluxDB uses UTC |
||||
|
utctime = datetime.fromtimestamp(time_rx.timestamp(), tz=timezone.utc) |
||||
|
|
||||
|
# InfluxDB requires nanoseconds |
||||
|
ctime = int(utctime.timestamp() * 1e9) |
||||
|
|
||||
|
# AC Data |
||||
|
phase_id = 0 |
||||
|
for phase in data['phases']: |
||||
|
data_stack.append(f'{measurement},phase={phase_id},type=power value={phase["power"]} {ctime}') |
||||
|
data_stack.append(f'{measurement},phase={phase_id},type=voltage value={phase["voltage"]} {ctime}') |
||||
|
data_stack.append(f'{measurement},phase={phase_id},type=current value={phase["current"]} {ctime}') |
||||
|
phase_id = phase_id + 1 |
||||
|
|
||||
|
# DC Data |
||||
|
string_id = 0 |
||||
|
for string in data['strings']: |
||||
|
data_stack.append(f'{measurement},string={string_id},type=total value={string["energy_total"]/1000:.4f} {ctime}') |
||||
|
data_stack.append(f'{measurement},string={string_id},type=power value={string["power"]:.2f} {ctime}') |
||||
|
data_stack.append(f'{measurement},string={string_id},type=voltage value={string["voltage"]:.3f} {ctime}') |
||||
|
data_stack.append(f'{measurement},string={string_id},type=current value={string["current"]:3f} {ctime}') |
||||
|
string_id = string_id + 1 |
||||
|
# Global |
||||
|
data_stack.append(f'{measurement},type=frequency value={data["frequency"]:.3f} {ctime}') |
||||
|
data_stack.append(f'{measurement},type=temperature value={data["temperature"]:.2f} {ctime}') |
||||
|
|
||||
|
self.api.write(self._bucket, self._org, data_stack) |
||||
|
|
||||
|
try: |
||||
|
import paho.mqtt.client |
||||
|
except ModuleNotFoundError: |
||||
|
pass |
||||
|
|
||||
|
class MqttOutputPlugin(OutputPluginFactory): |
||||
|
def __init__(self, *args, **params): |
||||
|
super().__init__(*args, **params) |
||||
|
|
||||
|
mqtt_client = paho.mqtt.client.Client() |
||||
|
mqtt_client.username_pw_set(params.get('user', None), params.get('password', None)) |
||||
|
mqtt_client.connect(params.get('host', '127.0.0.1'), params.get('port', 1883)) |
||||
|
mqtt_client.loop_start() |
||||
|
|
||||
|
self.client = mqtt_client |
||||
|
|
||||
|
def store_status(self, response, **params): |
||||
|
""" |
||||
|
Publish StatusResponse object |
||||
|
|
||||
|
:param paho.mqtt.client.Client broker: mqtt-client instance |
||||
|
:param str inverter_ser: inverter serial |
||||
|
:param hoymiles.StatusResponse data: decoded inverter StatusResponse |
||||
|
:param topic: custom mqtt topic prefix (default: hoymiles/{inverter_ser}) |
||||
|
:type topic: str |
||||
|
""" |
||||
|
|
||||
|
if not isinstance(response, StatusResponse): |
||||
|
raise RuntimeError('Data needs to be instance of StatusResponse') |
||||
|
|
||||
|
data = response.__dict__() |
||||
|
|
||||
|
topic = params.get('topic', f'hoymiles/{inverter_ser}') |
||||
|
|
||||
|
# AC Data |
||||
|
phase_id = 0 |
||||
|
for phase in data['phases']: |
||||
|
self.mqtt_client.publish(f'{topic}/emeter/{phase_id}/power', phase['power']) |
||||
|
self.mqtt_client.publish(f'{topic}/emeter/{phase_id}/voltage', phase['voltage']) |
||||
|
self.mqtt_client.publish(f'{topic}/emeter/{phase_id}/current', phase['current']) |
||||
|
phase_id = phase_id + 1 |
||||
|
|
||||
|
# DC Data |
||||
|
string_id = 0 |
||||
|
for string in data['strings']: |
||||
|
self.mqtt_client.publish(f'{topic}/emeter-dc/{string_id}/total', string['energy_total']/1000) |
||||
|
self.mqtt_client.publish(f'{topic}/emeter-dc/{string_id}/power', string['power']) |
||||
|
self.mqtt_client.publish(f'{topic}/emeter-dc/{string_id}/voltage', string['voltage']) |
||||
|
self.mqtt_client.publish(f'{topic}/emeter-dc/{string_id}/current', string['current']) |
||||
|
string_id = string_id + 1 |
||||
|
# Global |
||||
|
self.mqtt_client.publish(f'{topic}/frequency', data['frequency']) |
||||
|
self.mqtt_client.publish(f'{topic}/temperature', data['temperature']) |
@ -0,0 +1 @@ |
|||||
|
influxdb-client>=1.28.0 |
Loading…
Reference in new issue