diff options
author | Evgeny Zinoviev <me@ch1p.io> | 2021-11-27 16:17:05 +0300 |
---|---|---|
committer | Evgeny Zinoviev <me@ch1p.io> | 2022-04-24 01:33:04 +0300 |
commit | c412bf2ee0a3fbf9032fc32a26837d4fbc7585c5 (patch) | |
tree | 5cca6bcab79331ad82cab4219c7692b9dd4eea21 /src/home/mqtt |
initial public
Diffstat (limited to 'src/home/mqtt')
-rw-r--r-- | src/home/mqtt/__init__.py | 2 | ||||
-rw-r--r-- | src/home/mqtt/message/__init__.py | 2 | ||||
-rw-r--r-- | src/home/mqtt/message/inverter.py | 86 | ||||
-rw-r--r-- | src/home/mqtt/message/sensors.py | 19 | ||||
-rw-r--r-- | src/home/mqtt/mqtt.py | 61 | ||||
-rw-r--r-- | src/home/mqtt/util.py | 8 |
6 files changed, 178 insertions, 0 deletions
diff --git a/src/home/mqtt/__init__.py b/src/home/mqtt/__init__.py new file mode 100644 index 0000000..c0ef9ba --- /dev/null +++ b/src/home/mqtt/__init__.py @@ -0,0 +1,2 @@ +from .mqtt import MQTTBase +from .util import poll_tick diff --git a/src/home/mqtt/message/__init__.py b/src/home/mqtt/message/__init__.py new file mode 100644 index 0000000..2a2221b --- /dev/null +++ b/src/home/mqtt/message/__init__.py @@ -0,0 +1,2 @@ +from .inverter import Status, Generation +from .sensors import Temperature diff --git a/src/home/mqtt/message/inverter.py b/src/home/mqtt/message/inverter.py new file mode 100644 index 0000000..2df17e5 --- /dev/null +++ b/src/home/mqtt/message/inverter.py @@ -0,0 +1,86 @@ +import struct + +from typing import Tuple + + +class Status: + # 46 bytes + format = 'IHHHHHHBHHHHHBHHHHHHHH' + + def pack(self, time: int, data: dict) -> bytes: + bits = 0 + bits |= (data['mppt1_charger_status'] & 0x3) + bits |= (data['mppt2_charger_status'] & 0x3) << 2 + bits |= (data['battery_power_direction'] & 0x3) << 4 + bits |= (data['dc_ac_power_direction'] & 0x3) << 6 + bits |= (data['line_power_direction'] & 0x3) << 8 + bits |= (data['load_connected'] & 0x1) << 10 + + return struct.pack( + self.format, + time, + int(data['grid_voltage'] * 10), + int(data['grid_freq'] * 10), + int(data['ac_output_voltage'] * 10), + int(data['ac_output_freq'] * 10), + data['ac_output_apparent_power'], + data['ac_output_active_power'], + data['output_load_percent'], + int(data['battery_voltage'] * 10), + int(data['battery_voltage_scc'] * 10), + int(data['battery_voltage_scc2'] * 10), + data['battery_discharging_current'], + data['battery_charging_current'], + data['battery_capacity'], + data['inverter_heat_sink_temp'], + data['mppt1_charger_temp'], + data['mppt2_charger_temp'], + data['pv1_input_power'], + data['pv2_input_power'], + int(data['pv1_input_voltage'] * 10), + int(data['pv2_input_voltage'] * 10), + bits + ) + + def unpack(self, buf: bytes) -> Tuple[int, dict]: + data = struct.unpack(self.format, buf) + return data[0], { + 'grid_voltage': data[1] / 10, + 'grid_freq': data[2] / 10, + 'ac_output_voltage': data[3] / 10, + 'ac_output_freq': data[4] / 10, + 'ac_output_apparent_power': data[5], + 'ac_output_active_power': data[6], + 'output_load_percent': data[7], + 'battery_voltage': data[8] / 10, + 'battery_voltage_scc': data[9] / 10, + 'battery_voltage_scc2': data[10] / 10, + 'battery_discharging_current': data[11], + 'battery_charging_current': data[12], + 'battery_capacity': data[13], + 'inverter_heat_sink_temp': data[14], + 'mppt1_charger_temp': data[15], + 'mppt2_charger_temp': data[16], + 'pv1_input_power': data[17], + 'pv2_input_power': data[18], + 'pv1_input_voltage': data[19] / 10, + 'pv2_input_voltage': data[20] / 10, + 'mppt1_charger_status': data[21] & 0x03, + 'mppt2_charger_status': (data[21] >> 2) & 0x03, + 'battery_power_direction': (data[21] >> 4) & 0x03, + 'dc_ac_power_direction': (data[21] >> 6) & 0x03, + 'line_power_direction': (data[21] >> 8) & 0x03, + 'load_connected': (data[21] >> 10) & 0x01, + } + + +class Generation: + # 8 bytes + format = 'II' + + def pack(self, time: int, wh: int) -> bytes: + return struct.pack(self.format, int(time), wh) + + def unpack(self, buf: bytes) -> tuple: + data = struct.unpack(self.format, buf) + return tuple(data) diff --git a/src/home/mqtt/message/sensors.py b/src/home/mqtt/message/sensors.py new file mode 100644 index 0000000..ee522f0 --- /dev/null +++ b/src/home/mqtt/message/sensors.py @@ -0,0 +1,19 @@ +import struct + +from typing import Tuple + + +class Temperature: + format = 'IhH' + + def pack(self, time: int, temp: float, rh: float) -> bytes: + return struct.pack( + self.format, + time, + int(temp*100), + int(rh*100) + ) + + def unpack(self, buf: bytes) -> Tuple[int, float, float]: + data = struct.unpack(self.format, buf) + return data[0], data[1]/100, data[2]/100 diff --git a/src/home/mqtt/mqtt.py b/src/home/mqtt/mqtt.py new file mode 100644 index 0000000..b360d22 --- /dev/null +++ b/src/home/mqtt/mqtt.py @@ -0,0 +1,61 @@ +import os.path +import paho.mqtt.client as mqtt +import ssl +import logging + +from typing import Tuple +from ..config import config + +logger = logging.getLogger(__name__) + + +def username_and_password() -> Tuple[str, str]: + username = config['mqtt']['username'] if 'username' in config['mqtt'] else None + password = config['mqtt']['password'] if 'password' in config['mqtt'] else None + return username, password + + +class MQTTBase: + def __init__(self, clean_session=True): + self.client = mqtt.Client(client_id=config['mqtt']['client_id'], + protocol=mqtt.MQTTv311, + clean_session=clean_session) + self.client.on_connect = self.on_connect + self.client.on_disconnect = self.on_disconnect + self.client.on_message = self.on_message + + self.home_id = 1 + + username, password = username_and_password() + if username and password: + self.client.username_pw_set(username, password) + + def configure_tls(self): + ca_certs = os.path.realpath(os.path.join( + os.path.dirname(os.path.realpath(__file__)), + '..', + '..', + '..', + 'assets', + 'mqtt_ca.crt' + )) + self.client.tls_set(ca_certs=ca_certs, cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2) + + def connect_and_loop(self, loop_forever=True): + host = config['mqtt']['host'] + port = config['mqtt']['port'] + + self.client.connect(host, port, 60) + if loop_forever: + self.client.loop_forever() + else: + self.client.loop_start() + + def on_connect(self, client: mqtt.Client, userdata, flags, rc): + logger.info("Connected with result code " + str(rc)) + + def on_disconnect(self, client: mqtt.Client, userdata, rc): + logger.info("Disconnected with result code " + str(rc)) + + def on_message(self, client: mqtt.Client, userdata, msg): + logger.info(msg.topic + ": " + str(msg.payload)) diff --git a/src/home/mqtt/util.py b/src/home/mqtt/util.py new file mode 100644 index 0000000..f71ffd8 --- /dev/null +++ b/src/home/mqtt/util.py @@ -0,0 +1,8 @@ +import time + + +def poll_tick(freq): + t = time.time() + while True: + t += freq + yield max(t - time.time(), 0) |