summaryrefslogtreecommitdiff
path: root/src/home/mqtt
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2021-11-27 16:17:05 +0300
committerEvgeny Zinoviev <me@ch1p.io>2022-04-24 01:33:04 +0300
commitc412bf2ee0a3fbf9032fc32a26837d4fbc7585c5 (patch)
tree5cca6bcab79331ad82cab4219c7692b9dd4eea21 /src/home/mqtt
initial public
Diffstat (limited to 'src/home/mqtt')
-rw-r--r--src/home/mqtt/__init__.py2
-rw-r--r--src/home/mqtt/message/__init__.py2
-rw-r--r--src/home/mqtt/message/inverter.py86
-rw-r--r--src/home/mqtt/message/sensors.py19
-rw-r--r--src/home/mqtt/mqtt.py61
-rw-r--r--src/home/mqtt/util.py8
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)