From 141d53ff9ccb9598a52ef589adaaa848e4fcc6ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Jonas=20S=C3=A4mann?= Date: Mon, 4 Apr 2022 23:18:34 +0200 Subject: [PATCH 1/9] Update ahoy.py decode cmd=131 CMD 131 seem to contain temperatures --- tools/rpi/ahoy.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tools/rpi/ahoy.py b/tools/rpi/ahoy.py index 74f4b23b..2757a227 100644 --- a/tools/rpi/ahoy.py +++ b/tools/rpi/ahoy.py @@ -163,6 +163,17 @@ def on_receive(p): d['wday2_Wh'] = uk5 d['uk2'] = uk2 + elif cmd==131: + name = 'statedata' + uk1, uk2, uk3, uk4, uk5, uk6 = struct.unpack('>HHHHHH', p[10:22]) + print(f'uk1={uk1}, ', end='') + print(f'uk2={uk2}, ', end='') + print(f'uk3={uk3}, ', end='') + print(f'uk4={uk4}, ', end='') + print(f'uk5={uk5}, ', end='') + print(f'uk6={uk6}') + d['t_C'] = uk4/10 + else: print(f'unknown cmd {cmd}') else: From 51afd1298d6a2a01934b3778f3993dd74f04bc02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Jonas=20S=C3=A4mann?= Date: Mon, 4 Apr 2022 23:31:22 +0200 Subject: [PATCH 2/9] Update python requirements add crcmod --- tools/rpi/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/rpi/requirements.txt b/tools/rpi/requirements.txt index 8579e8b2..a26b1266 100644 --- a/tools/rpi/requirements.txt +++ b/tools/rpi/requirements.txt @@ -1 +1,2 @@ paho-mqtt +crcmod From 0fdce24a518499d4298ac7501d0c3f9e772f47f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Jonas=20S=C3=A4mann?= Date: Mon, 4 Apr 2022 23:33:20 +0200 Subject: [PATCH 3/9] Update ahoy.py make dtu and inverter serial configurable Adds config file support for dtu and inverter serial numbers to keep them outside this repo --- tools/rpi/ahoy.conf.example | 9 +++++++++ tools/rpi/ahoy.py | 5 ++--- 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 tools/rpi/ahoy.conf.example diff --git a/tools/rpi/ahoy.conf.example b/tools/rpi/ahoy.conf.example new file mode 100644 index 00000000..77338d00 --- /dev/null +++ b/tools/rpi/ahoy.conf.example @@ -0,0 +1,9 @@ +[mqtt] +host = 192.168.84.2 +port = 1883 + +[dtu] +serial = 99978563412 + +[inverter] +serial = 444473104619 diff --git a/tools/rpi/ahoy.py b/tools/rpi/ahoy.py index 2757a227..6ae698d9 100644 --- a/tools/rpi/ahoy.py +++ b/tools/rpi/ahoy.py @@ -24,11 +24,10 @@ mqtt_client = paho.mqtt.client.Client() mqtt_client.connect(mqtt_host, mqtt_port) # Master Address ('DTU') -dtu_ser = 99978563412 # identical to fc22's +dtu_ser = cfg.get('dtu', 'serial', fallback='99978563412') # identical to fc22's # inverter serial numbers -#inv_ser = 444473104619 # identical to fc22's #99972220200 -inv_ser = 114174608145 # my inverter +inv_ser = cfg.get('inverter', 'serial', fallback='444473104619') # my inverter # all inverters #... From d83b9e1a8dcbd3cccc470420a5e5480fc494be4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Jonas=20S=C3=A4mann?= Date: Mon, 4 Apr 2022 23:38:38 +0200 Subject: [PATCH 4/9] Update ahoy.py alter mqtt topics Refine mqtt topic's to be more usable. Make message layout kind of compatible with shelly EM's. --- tools/rpi/ahoy.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tools/rpi/ahoy.py b/tools/rpi/ahoy.py index 6ae698d9..ab45c71c 100644 --- a/tools/rpi/ahoy.py +++ b/tools/rpi/ahoy.py @@ -183,11 +183,20 @@ def on_receive(p): j = json.dumps(d) mqtt_client.publish(f'ahoy/{src}/{name}', j) if d['cmd']==2: - mqtt_client.publish(f'ahoy/{src}/{name}/p_W', d['p_W']) + mqtt_client.publish(f'ahoy/{src}/emeter/0/voltage', d['u_V']) + mqtt_client.publish(f'ahoy/{src}/emeter/0/power', d['p_W']) + mqtt_client.publish(f'ahoy/{src}/emeter/0/total', d['wtot1_Wh']) + mqtt_client.publish(f'ahoy/{src}/frequency', d['f_Hz']) if d['cmd']==1: - mqtt_client.publish(f'ahoy/{src}/{name}/p1_W', d['p1_W']) - mqtt_client.publish(f'ahoy/{src}/{name}/p2_W', d['p2_W']) - mqtt_client.publish(f'ahoy/{src}/{name}/p_W', d['p1_W']+d['p2_W']) + mqtt_client.publish(f'ahoy/{src}/emeter-dc/0/power', d['p1_W']) + mqtt_client.publish(f'ahoy/{src}/emeter-dc/0/voltage', d['u1_V']) + mqtt_client.publish(f'ahoy/{src}/emeter-dc/0/current', d['i1_A']) + mqtt_client.publish(f'ahoy/{src}/emeter-dc/1/power', d['p2_W']) + mqtt_client.publish(f'ahoy/{src}/emeter-dc/1/voltage', d['u2_V']) + mqtt_client.publish(f'ahoy/{src}/emeter-dc/1/current', d['i2_A']) + if d['cmd']==131: + mqtt_client.publish(f'ahoy/{src}/temperature', d['t_C']) + def main_loop(): From 1425d46b7ee745e9af2cddab423b7996a5f52f0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Jonas=20S=C3=A4mann?= Date: Mon, 4 Apr 2022 23:42:02 +0200 Subject: [PATCH 5/9] Update ahoy.py add timestamps --- tools/rpi/ahoy.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/rpi/ahoy.py b/tools/rpi/ahoy.py index ab45c71c..9c409bc8 100644 --- a/tools/rpi/ahoy.py +++ b/tools/rpi/ahoy.py @@ -235,7 +235,8 @@ def main_loop(): if has_payload: size = radio.getDynamicPayloadSize() payload = radio.read(size) - print(f"Received {size} bytes on pipe {pipe_number}: " + + dt = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") + print(f"{dt} Received {size} bytes on pipe {pipe_number}: " + " ".join([f"{b:02x}" for b in payload])) on_receive(payload) else: From 86715ac116188f27247d2beb65fe0f3a39a2eeab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Jonas=20S=C3=A4mann?= Date: Tue, 5 Apr 2022 14:49:57 +0200 Subject: [PATCH 6/9] Update ahoy.py implement poor channel hopping This will catch alot more packes --- tools/rpi/ahoy.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tools/rpi/ahoy.py b/tools/rpi/ahoy.py index 9c409bc8..eb4c067b 100644 --- a/tools/rpi/ahoy.py +++ b/tools/rpi/ahoy.py @@ -211,9 +211,12 @@ def main_loop(): ctr = 1 ts = int(time.time()) # see what happens if we always send one and the same (constant) time! + rx_channels = [3,23,61,75] + chn_id = 0 + rx_channel = rx_channels[chn_id] while True: - radio.setChannel(3) + radio.setChannel(rx_channel) radio.enableDynamicPayloads() radio.setAutoAck(False) radio.setPALevel(RF24_PA_MAX) @@ -236,10 +239,17 @@ def main_loop(): size = radio.getDynamicPayloadSize() payload = radio.read(size) dt = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") - print(f"{dt} Received {size} bytes on pipe {pipe_number}: " + + print(f"{dt} Received {size} bytes on channel {rx_channel} pipe {pipe_number}: " + " ".join([f"{b:02x}" for b in payload])) on_receive(payload) else: + radio.stopListening() + radio.setChannel(rx_channel) + radio.startListening() + chn_id = chn_id + 1 + if chn_id >= len(rx_channels): + chn_id = 0 + rx_channel = rx_channels[chn_id] time.sleep(0.01) radio.stopListening() # put radio in TX mode From ec18712583ed5cdf59857e1a14a1ac17e054b998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Jonas=20S=C3=A4mann?= Date: Wed, 6 Apr 2022 11:57:56 +0200 Subject: [PATCH 7/9] Update ahoy.py decode cmd 129 as error response --- tools/rpi/ahoy.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/rpi/ahoy.py b/tools/rpi/ahoy.py index eb4c067b..bb43149c 100644 --- a/tools/rpi/ahoy.py +++ b/tools/rpi/ahoy.py @@ -162,6 +162,10 @@ def on_receive(p): d['wday2_Wh'] = uk5 d['uk2'] = uk2 + elif cmd==129: + name = 'error' + print('Command error') + elif cmd==131: name = 'statedata' uk1, uk2, uk3, uk4, uk5, uk6 = struct.unpack('>HHHHHH', p[10:22]) From 5c7168b984eaaadcb3a821a105c83e9917d09d22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Jonas=20S=C3=A4mann?= Date: Wed, 6 Apr 2022 12:00:10 +0200 Subject: [PATCH 8/9] Update ahoy.py decode cmd 131, 132 * cmd=131 seem to appear on HM-600 * cmd=132 seem to appear in HM-1200 --- tools/rpi/ahoy.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/tools/rpi/ahoy.py b/tools/rpi/ahoy.py index bb43149c..2854a396 100644 --- a/tools/rpi/ahoy.py +++ b/tools/rpi/ahoy.py @@ -168,14 +168,27 @@ def on_receive(p): elif cmd==131: name = 'statedata' - uk1, uk2, uk3, uk4, uk5, uk6 = struct.unpack('>HHHHHH', p[10:22]) + uk1, l, uk3, t, uk5, uk6 = struct.unpack('>HHHHHH', p[10:22]) + print(f'l={l}%, t={t/10:.2f}C, ', end='') + print(f'uk1={uk1}, ', end='') + print(f'uk3={uk3}, ', end='') + print(f'uk5={uk5}, ', end='') + print(f'uk6={uk6}') + d['l_Pct'] = l + d['t_C'] = t/10 + + elif cmd==132: + name = 'unknown' + uk1, uk2, uk3, uk4, uk5, uk6, uk7, uk8 = struct.unpack( + '>HHHHHHHH', p[10:26]) print(f'uk1={uk1}, ', end='') print(f'uk2={uk2}, ', end='') print(f'uk3={uk3}, ', end='') print(f'uk4={uk4}, ', end='') print(f'uk5={uk5}, ', end='') - print(f'uk6={uk6}') - d['t_C'] = uk4/10 + print(f'uk6={uk6}, ', end='') + print(f'uk7={uk7}, ', end='') + print(f'uk8={uk8}') else: print(f'unknown cmd {cmd}') From 030af13c969535687c887e319c8cfd6c1bac33b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Jonas=20S=C3=A4mann?= Date: Wed, 6 Apr 2022 12:02:01 +0200 Subject: [PATCH 9/9] Update ahoy.py channel hopping and logging * Improve channel hopping by rotating rx start channel for each transaction * Set autoack True gives nearly 100% transmission success rate * Cleanup unused lines --- tools/rpi/ahoy.py | 51 ++++++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/tools/rpi/ahoy.py b/tools/rpi/ahoy.py index 2854a396..51210c5b 100644 --- a/tools/rpi/ahoy.py +++ b/tools/rpi/ahoy.py @@ -226,35 +226,40 @@ def main_loop(): print_addr(dtu_ser) ctr = 1 + last_tx_message = '' - ts = int(time.time()) # see what happens if we always send one and the same (constant) time! rx_channels = [3,23,61,75] - chn_id = 0 - rx_channel = rx_channels[chn_id] + rx_channel_id = 0 + rx_channel = rx_channels[rx_channel_id] + + tx_channels = [40] + tx_channel_id = 0 + tx_channel = tx_channels[tx_channel_id] while True: + # Sweep receive start channel + rx_channel_id = ctr % len(rx_channels) + rx_channel = rx_channels[rx_channel_id] + radio.setChannel(rx_channel) radio.enableDynamicPayloads() - radio.setAutoAck(False) + radio.setAutoAck(True) radio.setPALevel(RF24_PA_MAX) radio.setDataRate(RF24_250KBPS) radio.openWritingPipe(ser_to_esb_addr(inv_ser)) radio.flush_rx() radio.flush_tx() radio.openReadingPipe(1,ser_to_esb_addr(dtu_ser)) - #radio.openReadingPipe(1,ser_to_esb_addr(inv_ser)) radio.startListening() - if ctr<3: - pass - # radio.printPrettyDetails() - t_end = time.monotonic_ns()+1e9 while time.monotonic_ns() < t_end: has_payload, pipe_number = radio.available_pipe() if has_payload: size = radio.getDynamicPayloadSize() payload = radio.read(size) + print(last_tx_message, end='') + last_tx_message = '' dt = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") print(f"{dt} Received {size} bytes on channel {rx_channel} pipe {pipe_number}: " + " ".join([f"{b:02x}" for b in payload])) @@ -263,27 +268,31 @@ def main_loop(): radio.stopListening() radio.setChannel(rx_channel) radio.startListening() - chn_id = chn_id + 1 - if chn_id >= len(rx_channels): - chn_id = 0 - rx_channel = rx_channels[chn_id] + rx_channel_id = rx_channel_id + 1 + if rx_channel_id >= len(rx_channels): + rx_channel_id = 0 + rx_channel = rx_channels[rx_channel_id] time.sleep(0.01) + tx_channel_id = tx_channel_id + 1 + if tx_channel_id >= len(tx_channels): + tx_channel_id = 0 + tx_channel = tx_channels[tx_channel_id] + radio.stopListening() # put radio in TX mode - radio.setChannel(40) + radio.setChannel(tx_channel) radio.openWritingPipe(ser_to_esb_addr(inv_ser)) - if ctr<3: - pass - # radio.printPrettyDetails() - - # ts = int(time.time()) + ts = int(time.time()) payload = compose_0x80_msg(src_ser_no=dtu_ser, dst_ser_no=inv_ser, ts=ts) - print(f"{ctr:5d}: len={len(payload)} | " + " ".join([f"{b:02x}" for b in payload]), - flush=True) + dt = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") + last_tx_message = f"{dt} Transmit {ctr:5d}: channel={tx_channel} len={len(payload)} | " + \ + " ".join([f"{b:02x}" for b in payload]) + "\n" radio.write(payload) # will always yield 'True' because auto-ack is disabled ctr = ctr + 1 + print(flush=True, end='') +