Hook MQTT up to the live data feed

This commit is contained in:
Odd Stråbø 2021-11-14 04:46:17 +01:00
parent a5eb518b8b
commit 50978111c5
4 changed files with 126 additions and 82 deletions

3
.gitignore vendored
View File

@ -1,5 +1,8 @@
*.py[ocd]
.mypy_cache/
__pycache__/
*.log
*.zip

View File

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import json
from typing import Any, Dict, List, Optional
from uuid import uuid4
@ -34,11 +35,13 @@ MAP_VALUES: Dict[str, Dict[str, Any]] = {
"unit": "Wh",
"type": "energy",
"state_class": "total_increasing",
"expiry": 180,
},
"total_consumption_power": {
"unit": "Wh",
"type": "energy",
"state_class": "total_increasing",
"expiry": 180,
},
#
"battery_charge": {"unit": "%", "type": "battery", "state_class": "measurement"},
@ -128,6 +131,7 @@ class MqttConsumer(BaseConsumer):
# TODO: Get charger serial and use for identifier instead
# See: https://www.home-assistant.io/integrations/sensor.mqtt/#device
# "via_device": self.settings["device_id"],
"suggested_area": "Solar panel",
},
"force_update": True,
"expire_after": expiry,
@ -140,6 +144,8 @@ class MqttConsumer(BaseConsumer):
if state_class:
res["state_class"] = state_class
return res
# The callback for when the client receives a CONNACK response from the server.
@staticmethod
def on_connect(client: mqtt.Client, userdata: "MqttConsumer", flags, rc):
@ -162,15 +168,17 @@ class MqttConsumer(BaseConsumer):
return super().poll()
def write(self, data: Dict[str, Any]):
self.client.publish(f"{self.topic_prefix}/raw", payload=data)
self.client.publish(f"{self.topic_prefix}/raw", payload=json.dumps(data))
for k, v in data.items():
if k in MAP_VALUES:
if k not in self.initialized:
pretty_name = k.replace("_", " ").capitalize()
self.client.publish(
f"{self.settings['discovery_prefix']}/sensor/{self.settings['device_id']}/config", # noqa: E501
self.get_ha_config(k, pretty_name, **MAP_VALUES[k]),
f"{self.settings['discovery_prefix']}/sensor/{self.settings['device_id']}_{k}/config", # noqa: E501
payload=json.dumps(
self.get_ha_config(k, pretty_name, **MAP_VALUES[k])
),
retain=True,
)
self.initialized.append(k)

View File

@ -12,6 +12,7 @@ from bluepy import btle
from libscrc import modbus
from feasycom_ble import BTLEUart
from test_config import get_config, get_consumers
MAC = "DC:0D:30:9C:61:BA"
@ -366,10 +367,13 @@ class Periodical:
if __name__ == "__main__":
conf = get_config()
consumers = get_consumers(conf)
per_voltages = Periodical(interval=15)
per_current_hist = Periodical(interval=60)
try:
while True:
try:
log("Connecting...")
@ -387,6 +391,8 @@ if __name__ == "__main__":
if res:
d = parse_historical_entry(res)
log(d)
for consumer in consumers:
consumer.write(d)
days = cast(int, d.get("run_days", 7))
for i in range(days):
@ -394,6 +400,8 @@ if __name__ == "__main__":
if res:
d = parse_historical_entry(res)
log({i: d})
for consumer in consumers:
consumer.write({str(i): d})
while True:
now = time.time()
@ -404,22 +412,34 @@ if __name__ == "__main__":
try:
d = parse_battery_state(res)
log(d)
for consumer in consumers:
consumer.write(d)
except struct.error as e:
log(e)
log("0x0100 Unpack error:", len(res), res)
log("Flushed from read buffer; ", dev.read(timeout=0.5))
log(
"Flushed from read buffer; ",
dev.read(timeout=0.5),
)
if per_current_hist(now):
res = readMemory(dev, 0x010B, 21)
if res:
try:
d = parse_historical_entry(res)
log(d)
for consumer in consumers:
consumer.write(d)
except struct.error as e:
log(e)
log("0x010B Unpack error:", len(res), res)
log("Flushed from read buffer; ", dev.read(timeout=0.5))
log(
"Flushed from read buffer; ",
dev.read(timeout=0.5),
)
# print(".")
time.sleep(1)
for consumer in consumers:
consumer.poll()
time.sleep(max(0, 1 - time.time() - now))
# if STATUS.get('load_enabled'):
# write(wd, CMD_DISABLE_LOAD)
@ -429,5 +449,10 @@ if __name__ == "__main__":
except btle.BTLEDisconnectError:
log("ERROR: Disconnected")
time.sleep(1)
except KeyboardInterrupt:
break
except (KeyboardInterrupt, SystemExit, Exception) as e:
for consumer in consumers:
consumer.exit()
if type(e) is not KeyboardInterrupt:
raise

View File

@ -2,7 +2,7 @@
import importlib
import os
from time import sleep
from typing import Any, Dict, Optional, Type
from typing import Any, Dict, List, Optional, Type
import yaml
@ -14,8 +14,8 @@ def get_consumer(name: str) -> Optional[Type[BaseConsumer]]:
mod = importlib.import_module(f"consumers.{mod_name}")
print(mod)
print(dir(mod))
# print(mod)
# print(dir(mod))
res = getattr(mod, cls_name)
assert issubclass(res, BaseConsumer)
@ -36,25 +36,33 @@ def write_config(conf: Dict[str, Any]):
os.rename(".config.yaml~writing", "config.yaml")
conf = get_config()
def get_consumers(conf: Optional[Dict[str, Any]] = None) -> List[BaseConsumer]:
if conf is None:
conf = get_config()
consumers = []
for name, consumer_config in conf["consumers"].items():
print(name, consumer_config)
consumers = []
for name, consumer_config in conf["consumers"].items():
# print(name, consumer_config)
mod = get_consumer(name)
if mod:
print(mod)
# print(mod)
consumers.append(mod(consumer_config))
write_config(conf)
write_config(conf)
return consumers
try:
if __name__ == "__main__":
conf = get_config()
consumers = get_consumers(conf)
try:
while True:
for consumer in consumers:
consumer.poll()
sleep(1)
except (KeyboardInterrupt, SystemExit, Exception) as e:
except (KeyboardInterrupt, SystemExit, Exception) as e:
for consumer in consumers:
consumer.exit()