From 78c2fea8654a8afd9d4001bedc2b01b113afeeb1 Mon Sep 17 00:00:00 2001 From: Benedikt Seidl Date: Thu, 14 Oct 2021 20:46:14 +0200 Subject: [PATCH] add openwb support for PR37SB solar wattage is calculated from voltage and current. this seems to be the case for the official software. voltage and current extracted via usb sniffer matched the wattage displayed in the official gui, so we should be quite save. --- prpd_usb/faker.py | 1 + prpd_usb/output/mqtt_openwb.py | 33 ++++++++++++++++++++++++++++----- prpd_usb/prpd.py | 4 ++++ tests/test_openwb.py | 26 +++++++++++++++++++++++++- 4 files changed, 58 insertions(+), 6 deletions(-) diff --git a/prpd_usb/faker.py b/prpd_usb/faker.py index 14d58aa..b1a9f6f 100644 --- a/prpd_usb/faker.py +++ b/prpd_usb/faker.py @@ -18,6 +18,7 @@ h("5a0070020000e1b7a5"), { "PR37SB": h("5aff7002001649dd0000024248450000024af0430882021c0226052404b3a5"), "PR50SB": h("5aff7002001671d4000001a971b6000001aaf04309b0020801d60522150ea5")} + ), ( h("5a00710200001db6a5"), { "PR37SB": h("5aff7102000c009b278c00a10c63013c33ef84f9a5"), diff --git a/prpd_usb/output/mqtt_openwb.py b/prpd_usb/output/mqtt_openwb.py index 3f686b0..d188e92 100644 --- a/prpd_usb/output/mqtt_openwb.py +++ b/prpd_usb/output/mqtt_openwb.py @@ -7,7 +7,8 @@ Direct = namedtuple('Direct', ['key']) Map = namedtuple('Map', ['keys', 'function']) -MultiMap = namedtuple('Map', ['keys', 'functions']) +MultiMap = namedtuple('MultiMap', ['keys', 'functions']) +FunctionCall = namedtuple('FunctionCall', ['keys', 'function']) def _neg(value): return value * -1 @@ -18,12 +19,17 @@ def _sum(*values): def _m_sum(*values): return [sum(values)] -MAPPING = { +def _calc_pv_power_pr37sb(current_1, current_2, voltage_1, voltage_2): + return current_1 * voltage_1 + current_2 * voltage_2 + +DEFAULT_MAPPING = { "openWB/set/evu/W": Map(keys=(('grid', 'power_w_phase_1'), ('grid', 'power_w_phase_2'), ('grid', 'power_w_phase_3')), function=_sum), "openWB/set/evu/APhase1": Direct(('grid', 'current_phase_1')), "openWB/set/evu/APhase2": Direct(('grid', 'current_phase_2')), "openWB/set/evu/APhase3": Direct(('grid', 'current_phase_3')), "openWB/set/evu/WhImported": Map(keys=(('grid', 'total_phase_1'), ('grid', 'total_phase_2'), ('grid', 'total_phase_3')), function=_sum), + # TODO: WhExported should be more complicated: + # this should be (at least) the total_solar - total_battery_consumed "openWB/set/evu/WhExported": Map(keys=(('solar', 'total_phase_1'), ('solar', 'total_phase_2'), ('solar', 'total_phase_3')), function=_sum), "openWB/set/evu/VPhase1": Direct(('grid', 'voltage_phase_1')), "openWB/set/evu/VPhase2": Direct(('grid', 'voltage_phase_2')), @@ -39,17 +45,34 @@ def _m_sum(*values): "openWB/set/houseBattery/%Soc": Direct(('battery', 'soc')), } +MAPPING_BY_MODEL = { + "PR37SB": { + "openWB/set/evu/WhExported": Direct(('solar', 'total')), + "openWB/set/pv/1/W": FunctionCall(keys=(('solar', 'current_string_1'), ('solar', 'current_string_2'), ('solar', 'voltage_string_1'), ('solar', 'voltage_string_2'),), function=_calc_pv_power_pr37sb), + "openWB/set/pv/1/WhCounter": Direct(('solar', 'total')) + } +} -def transform_to_openwb(prpd_data): +def transform_to_openwb(prpd_data, model): messages = [] data = {} for command, field, _time, value in prpd_data: data[(command.name, field.name)] = value - for topic, mapping in MAPPING.items(): + from pprint import pprint + pprint(data) + + mapping = DEFAULT_MAPPING.copy() + if model in MAPPING_BY_MODEL: + mapping.update(MAPPING_BY_MODEL[model]) + + for topic, mapping in mapping.items(): if isinstance(mapping, Direct): payload = data[mapping.key] + elif isinstance(mapping, FunctionCall): + values = [data[k] for k in mapping.keys] + payload = mapping.function(*values) elif isinstance(mapping, MultiMap): values = [data[k] for k in mapping.keys] for function in mapping.functions: @@ -74,7 +97,7 @@ def main(prpd_reader, args): while True: data = prpd_reader.read() - messages = transform_to_openwb(data) + messages = transform_to_openwb(data, prpd_reader.model) publish.multiple(messages, hostname=args.mqtt_hostname, port=args.mqtt_port, auth=auth) time.sleep(args.mqtt_interval) diff --git a/prpd_usb/prpd.py b/prpd_usb/prpd.py index de69895..b40b7ac 100644 --- a/prpd_usb/prpd.py +++ b/prpd_usb/prpd.py @@ -74,6 +74,10 @@ def init(self, model=None): self._model = MODELS[model] logger.info(f"Identified model {self._model}") + @property + def model(self): + return self._model + def read(self): with self.lock: for command in self._get_commands(): diff --git a/tests/test_openwb.py b/tests/test_openwb.py index 5bfab75..b26f994 100644 --- a/tests/test_openwb.py +++ b/tests/test_openwb.py @@ -9,7 +9,7 @@ def test_openwb_37bi(): prpd = _PrPd(SerialFaker("PR37Bi")) prpd.init() data = prpd.read() - messages = transform_to_openwb(data) + messages = transform_to_openwb(data, "PR37Bi") assert messages == [ {"payload": "18", "topic": "openWB/set/evu/W"}, {"payload": "1.17", "topic": "openWB/set/evu/APhase1"}, @@ -28,3 +28,27 @@ def test_openwb_37bi(): {"payload": "5545710", "topic": "openWB/set/houseBattery/WhExported"}, {"payload": "76", "topic": "openWB/set/houseBattery/%Soc"}, ] + +def test_openwb_37sb(): + prpd = _PrPd(SerialFaker("PR37SB")) + prpd.init() + data = prpd.read() + messages = transform_to_openwb(data, "PR37SB") + assert messages == [ + {'payload': '-16', 'topic': 'openWB/set/evu/W'}, + {'payload': '1.06', 'topic': 'openWB/set/evu/APhase1'}, + {'payload': '2.9', 'topic': 'openWB/set/evu/APhase2'}, + {'payload': '3.79', 'topic': 'openWB/set/evu/APhase3'}, + {'payload': '17090500', 'topic': 'openWB/set/evu/WhImported'}, + {'payload': '20722671', 'topic': 'openWB/set/evu/WhExported'}, + {'payload': '227.20000000000002', 'topic': 'openWB/set/evu/VPhase1'}, + {'payload': '227.0', 'topic': 'openWB/set/evu/VPhase2'}, + {'payload': '227.4', 'topic': 'openWB/set/evu/VPhase3'}, + {'payload': '50.01', 'topic': 'openWB/set/evu/HzFrequenz'}, + {'payload': '2177.0987999999998', 'topic': 'openWB/set/pv/1/W'}, + {'payload': '20722671', 'topic': 'openWB/set/pv/1/WhCounter'}, + {'payload': '502', 'topic': 'openWB/set/houseBattery/W'}, + {'payload': '5806854', 'topic': 'openWB/set/houseBattery/WhImported'}, + {'payload': '4497664', 'topic': 'openWB/set/houseBattery/WhExported'}, + {'payload': '78', 'topic': 'openWB/set/houseBattery/%Soc'}, + ]