diff --git a/bmslib/sampling.py b/bmslib/sampling.py index b618759..7372674 100644 --- a/bmslib/sampling.py +++ b/bmslib/sampling.py @@ -1,9 +1,12 @@ +import os import asyncio import math import random import re import sys import time +import json + from collections import defaultdict from copy import copy from typing import Optional, List, Dict @@ -22,6 +25,7 @@ logger = get_logger(verbose=False) +standalone = os.environ.get('STANDALONE', False) class SampleExpiredError(Exception): pass @@ -378,7 +382,7 @@ async def cached_fetch_voltages(): self.publish_meters() # publish home assistant discovery every 60 samples - if self.period_discov: + if self.period_discov and not standalone: logger.info("Sending HA discovery for %s (num_samples=%d)", bms.name, self.num_samples) if self.device_info is None: await self._try_fetch_device_info() @@ -416,10 +420,20 @@ async def cached_fetch_voltages(): def publish_meters(self): device_topic = self.mqtt_topic_prefix + + if standalone: + result = {} + for meter in self.meters: - topic = f"{device_topic}/meter/{meter.name}" - s = round(meter.get(), 3) - mqtt_single_out(self.mqtt_client, topic, s) + value = round(meter.get(), 3) + if standalone: + result[meter.name] = value + else: + topic = f"{device_topic}/meter/{meter.name}" + mqtt_single_out(self.mqtt_client, topic, value) + + if standalone: + mqtt_single_out(self.mqtt_client, f"{device_topic}/meter", json.dumps(result)) if self.sinks: readings = {m.name: m.get() for m in self.meters} @@ -480,3 +494,4 @@ def pop(self): self._last = None return s + diff --git a/main.py b/main.py index ada25a7..4cf49c6 100644 --- a/main.py +++ b/main.py @@ -182,8 +182,10 @@ async def main(): # import env vars from addon_main.sh for k, en in dict(mqtt_broker='MQTT_HOST', mqtt_user='MQTT_USER', mqtt_password='MQTT_PASSWORD').items(): - if not user_config.get(k) and os.environ.get(en): - user_config[k] = os.environ[en] + env_value = os.environ.get(en) + if env_value is not None or k not in user_config: + logger.info(f"Setting {k} to user_config: {env_value}") + user_config[k] = env_value if user_config.get('mqtt_broker'): port_idx = user_config.mqtt_broker.rfind(':') diff --git a/mqtt_util.py b/mqtt_util.py index 94aee26..edd6182 100644 --- a/mqtt_util.py +++ b/mqtt_util.py @@ -4,6 +4,7 @@ """ +import os import asyncio import json import math @@ -11,6 +12,7 @@ import statistics import time import traceback +import json import paho.mqtt.client as paho @@ -22,6 +24,7 @@ no_publish_fail_warn = False +standalone = os.environ.get('STANDALONE', False) def round_to_n(x, n): if isinstance(x, str) or not math.isfinite(x) or not x: @@ -234,20 +237,35 @@ def is_none_or_nan(val): "icon": "counter"}, } - def publish_sample(client, device_topic, sample: BmsSample): + if standalone: + transformed_values = {} + + # Process sensor values for k, v in sample_desc.items(): - topic = f"{device_topic}/{k}" s = round_to_n(getattr(sample, v['field']), v.get('precision', 5)) if not is_none_or_nan(s): - mqtt_single_out(client, topic, s) + if standalone: + transformed_values[k] = s + else: + topic = f"{device_topic}/{k}" + mqtt_single_out(client, topic, s) + # Process switches if sample.switches: + if standalone: + transformed_values['switches'] = {} for switch_name, switch_state in sample.switches.items(): assert isinstance(switch_state, bool) - topic = f"{device_topic}/switch/{switch_name}" - mqtt_single_out(client, topic, 'ON' if switch_state else 'OFF') + if standalone: + transformed_values['switches'][switch_name] = 'ON' if switch_state else 'OFF' + else: + topic = f"{device_topic}/switch/{switch_name}" + mqtt_single_out(client, topic, 'ON' if switch_state else 'OFF') + if standalone: + topic = f"{device_topic}/sample" + mqtt_single_out(client, topic, json.dumps(transformed_values)) def publish_cell_voltages(client, device_topic, voltages): # "highest_voltage": parts[0] / 1000, @@ -258,11 +276,19 @@ def publish_cell_voltages(client, device_topic, voltages): if not voltages: return - for i in range(0, len(voltages)): - topic = f"{device_topic}/cell_voltages/{i + 1}" - mqtt_single_out(client, topic, voltages[i] / 1000) + if standalone: + result = {} - if len(voltages) > 1: + for i in range(0, len(voltages)): + if standalone: + result[i] = voltages[i] / 1000 + else: + topic = f"{device_topic}/cell_voltages/{i + 1}" + mqtt_single_out(client, topic, voltages[i] / 1000) + + if standalone: + mqtt_single_out(client, f"{device_topic}/cell_voltages", json.dumps(result)) + elif len(voltages) > 1: x = range(len(voltages)) high_i = max(x, key=lambda i: voltages[i]) low_i = min(x, key=lambda i: voltages[i]) @@ -276,10 +302,19 @@ def publish_cell_voltages(client, device_topic, voltages): def publish_temperatures(client, device_topic, temperatures): + if standalone: + result = [] + for i in range(0, len(temperatures)): - topic = f"{device_topic}/temperatures/{i + 1}" if not is_none_or_nan(temperatures[i]): - mqtt_single_out(client, topic, round_to_n(temperatures[i], 4)) + if standalone: + result.append(round_to_n(temperatures[i], 4)) + else: + topic = f"{device_topic}/temperatures/{i + 1}" + mqtt_single_out(client, topic, round_to_n(temperatures[i], 4)) + + if standalone: + mqtt_single_out(client, f"{device_topic}/temperatures", json.dumps(result)) def publish_hass_discovery(client, device_topic, expire_after_seconds: int, sample: BmsSample, num_cells,