Browse Source

Update ahoy.py split and relocate code

Split on_receive from actual device specific decoder.
Rename decoder to hm600 because thats my dut.
Alters debug prints, remove json dump from console, makes it more readable.
Move Tx before Rx in main loop, change timing
pull/3/head
Jan-Jonas Sämann 3 years ago
parent
commit
0309dcb41a
  1. 453
      tools/rpi/ahoy.py

453
tools/rpi/ahoy.py

@ -107,9 +107,10 @@ def print_addr(a):
# time of last transmission - to calculcate response time
t_last_tx = 0
def on_receive(p, ch_rx=None, ch_tx=None):
def on_receive(p=None, ctr=None, ch_rx=None, ch_tx=None, time_rx=datetime.now(), latency=None):
"""
Callback: get's invoked whenever a packet has been received.
Callback: get's invoked whenever a Nordic ESB packet has been received.
:param p: Payload of the received packet.
"""
@ -118,10 +119,15 @@ def on_receive(p, ch_rx=None, ch_tx=None):
t_now_ns = time.monotonic_ns()
ts = datetime.utcnow()
ts_unixtime = ts.timestamp()
size = len(p)
d['ts_unixtime'] = ts_unixtime
d['isodate'] = ts.isoformat()
d['rawdata'] = " ".join([f"{b:02x}" for b in p])
print(ts.isoformat(), end='Z ')
d['trans_id'] = ctr
dt = time_rx.strftime("%Y-%m-%d %H:%M:%S.%f")
print(f"{dt} Received {size} bytes on channel {ch_rx} after tx {latency}ns: " +
" ".join([f"{b:02x}" for b in p]))
# check crc8
crc8 = f_crc8(p[:-1])
@ -136,109 +142,253 @@ def on_receive(p, ch_rx=None, ch_tx=None):
d['ch_tx'] = ch_tx
if mid == 0x95:
src, dst, cmd = struct.unpack('>LLB', p[1:10])
src_s = f'{src:08x}'
dst_s = f'{dst:08x}'
d['src'] = src_s
d['dst'] = dst_s
d['cmd'] = cmd
print(f'MSG src={src_s}, dst={dst_s}, cmd={cmd}, ', end=' ')
if cmd==1:
name = 'dcdata'
unknown1, u1, i1, p1, u2, i2, p2, unknown2 = struct.unpack(
'>HHHHHHHH', p[10:26])
print(f'u1={u1/10}V, i1={i1/100}A, p1={p1/10}W, ', end='')
print(f'u2={u2/10}V, i2={i2/100}A, p2={p2/10}W, ', end='')
print(f'unknown1={unknown1}, unknown2={unknown2}')
d['u1_V'] = u1/10
d['i1_A'] = i1/100
d['p1_W'] = p1/10
d['u2_V'] = u2/10
d['i2_A'] = i2/100
d['p2_W'] = p2/10
d['unknown1'] = unknown1
d['unknown2'] = unknown2
elif cmd==2:
name = 'acdata'
uk1, uk2, uk3, uk4, uk5, u, f, p = struct.unpack(
'>HHHHHHHH', p[10:26])
print(f'u={u/10:.1f}V, f={f/100:.2f}Hz, p={p/10:.1f}W, ', end='')
print(f'uk1={uk1}, ', end='')
print(f'uk2={uk2}, ', end='')
print(f'uk3={uk3}, ', end='')
print(f'uk4={uk4}, ', end='')
print(f'uk5={uk5}')
d['u_V'] = u/10
d['f_Hz'] = f/100
d['p_W'] = p/10
d['wtot1_Wh'] = uk1
d['wtot2_Wh'] = uk3
d['wday1_Wh'] = uk4
d['wday2_Wh'] = uk5
d['uk2'] = uk2
elif cmd==129:
name = 'error'
print('Command error')
elif cmd==131: # 0x83
name = 'statedata'
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
d['uk1'] = uk1
d['uk3'] = uk3
d['uk5'] = uk5
d['uk6'] = uk6
elif cmd==132: # 0x84
name = 'unknown0x84'
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}, ', end='')
print(f'uk7={uk7}, ', end='')
print(f'uk8={uk8}')
else:
print(f'unknown cmd {cmd}')
decode_hoymiles_hm600(d, p, time_rx=time_rx)
else:
print(f'unknown frame id {p[0]}')
# output to stdout
if d:
print(json.dumps(d))
def decode_hoymiles_hm600(d, p, time_rx=datetime.now()):
"""
Decode payload from Hoymiles HM-600
:param d: Pre parsed data from on_receive
:param p: raw payload byte array
:param time_rx: datetime object when packet was received
"""
src, dst, cmd = struct.unpack('>LLB', p[1:10])
src_s = f'{src:08x}'
dst_s = f'{dst:08x}'
d['src'] = src_s
d['dst'] = dst_s
d['cmd'] = cmd
dt = time_rx.strftime("%Y-%m-%d %H:%M:%S.%f")
print(f'{dt} Decoder src={src_s}, dst={dst_s}, cmd={cmd}, ', end=' ')
if cmd==1: # 0x01
"""
On HM600 Response to
0x80 0x0b
0x80 0x0c
0x80 0x0d
0x80 0x0f
0x80 0x03 (garbled data)
"""
name = 'dcdata'
uk1, u1, i1, p1, u2, i2, p2, uk2 = struct.unpack(
'>HHHHHHHH', p[10:26])
print(f'u1={u1/10}V, i1={i1/100}A, p1={p1/10}W, ', end='')
print(f'u2={u2/10}V, i2={i2/100}A, p2={p2/10}W, ', end='')
print(f'uk1={uk1}, uk2={uk2}')
d['dc'] = {0: {}, 1: {}}
d['dc'][0]['voltage'] = u1/10
d['dc'][0]['current'] = i1/100
d['dc'][0]['power'] = p1/10
d['dc'][1]['voltage'] = u2/10
d['dc'][1]['current'] = i2/100
d['dc'][1]['power'] = p2/10
d['uk1'] = uk1
d['uk2'] = uk2
elif cmd==2: # 0x02
"""
On HM600 Response to
0x80 0x0b
0x80 0x0c
0x80 0x0d
0x80 0x0f
0x80 0x03 (garbled data)
"""
name = 'acdata'
uk1, uk2, uk3, uk4, uk5, ac_u1, f, ac_p1 = struct.unpack(
'>HHHHHHHH', p[10:26])
print(f'ac_u1={ac_u1/10:.1f}V, ac_f={f/100:.2f}Hz, ac_p1={ac_p1/10:.1f}W, ', end='')
print(f'uk1={uk1}, ', end='')
print(f'uk2={uk2}, ', end='')
print(f'uk3={uk3}, ', end='')
print(f'uk4={uk4}, ', end='')
print(f'uk5={uk5}')
d['ac'] = {0: {}}
d['ac'][0]['voltage'] = ac_u1/10
d['frequency'] = f/100
d['ac'][0]['power'] = ac_p1/10
d['wtot1_Wh'] = uk1
d['wtot2_Wh'] = uk3
d['wday1_Wh'] = uk4
d['wday2_Wh'] = uk5
d['uk2'] = uk2
elif cmd==3: # 0x03
"""
On HM600 Response to
0x80 0x03 (garbled data)
"""
uk1, uk2, uk3, uk4, uk5, uk6, uk7, uk8 = struct.unpack(
'>HHHHHHHH', p[10:26])
name = 'error'
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}, ', end='')
print(f'uk7={uk7}, ', end='')
print(f'uk8={uk8}')
elif cmd==4: # 0x04
"""
On HM600 Response to
0x80 0x03 (garbled data)
"""
uk1, uk2, uk3, uk4, uk5, uk6, uk7, uk8 = struct.unpack(
'>HHHHHHHH', p[10:26])
name = 'error'
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}, ', end='')
print(f'uk7={uk7}, ', end='')
print(f'uk8={uk8}')
elif cmd==5: # 0x05
"""
On HM600 Response to
0x80 0x03 (garbled data)
"""
uk1, uk2, uk3, uk4, uk5, uk6, uk7, uk8 = struct.unpack(
'>HHHHHHHH', p[10:26])
name = 'error'
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}, ', end='')
print(f'uk7={uk7}, ', end='')
print(f'uk8={uk8}')
elif cmd==6: # 0x06
"""
On HM600 Response to
0x80 0x03 (garbled data)
"""
uk1, uk2, uk3, uk4, uk5, uk6, uk7, uk8 = struct.unpack(
'>HHHHHHHH', p[10:26])
name = 'error'
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}, ', end='')
print(f'uk7={uk7}, ', end='')
print(f'uk8={uk8}')
elif cmd==7: # 0x07
"""
On HM600 Response to
0x80 0x03 (garbled data)
"""
uk1, uk2, uk3, uk4, uk5, uk6, uk7, uk8 = struct.unpack(
'>HHHHHHHH', p[10:26])
name = 'error'
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}, ', end='')
print(f'uk7={uk7}, ', end='')
print(f'uk8={uk8}')
elif cmd==129 and len(p) == 17: # 0x81
"""
On HM600 Response to
0x80 0x0a
"""
uk1, uk2, uk3 = struct.unpack(
'>HHH', p[10:16])
name = 'error'
print(f'uk1={uk1}, ', end='')
print(f'uk2={uk2}, ', end='')
print(f'uk3={uk3}, ')
elif cmd==129: # 0x81
"""
On HM600 Response to
0x80 0x02
0x80 0x11
"""
uk1, uk2, uk3, uk4, uk5, uk6, uk7, uk8 = struct.unpack(
'>HHHHHHHH', p[10:26])
name = 'error'
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}, ', end='')
print(f'uk7={uk7}, ', end='')
print(f'uk8={uk8}')
elif cmd==131: # 0x83
"""
On HM600 Response to
0x80 0x0b
0x80 0x0c
0x80 0x0d
0x80 0x0f
"""
name = 'statedata'
uk1, ac_i1, uk3, t, uk5, uk6 = struct.unpack('>HHHHHH', p[10:22])
print(f'ac_i1={ac_i1/100}A, 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['ac'] = {0: {}}
d['ac'][0]['current'] = ac_i1/100
d['temperature'] = t/10
d['uk1'] = uk1
d['uk3'] = uk3
d['uk5'] = uk5
d['uk6'] = uk6
elif cmd==132: # 0x84
name = 'unknown0x84'
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}, ', end='')
print(f'uk7={uk7}, ', end='')
print(f'uk8={uk8}')
else:
print(f'unknown cmd {cmd}')
# output to MQTT
if d:
j = json.dumps(d)
mqtt_client.publish(f'ahoy/{src}/{name}', j)
mqtt_client.publish(f'ahoy/{src}/debug', j)
if d['cmd']==2:
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/voltage', d['ac'][0]['voltage'])
mqtt_client.publish(f'ahoy/{src}/emeter/0/power', d['ac'][0]['power'])
mqtt_client.publish(f'ahoy/{src}/emeter/0/total', d['wtot1_Wh'])
mqtt_client.publish(f'ahoy/{src}/frequency', d['f_Hz'])
mqtt_client.publish(f'ahoy/{src}/frequency', d['frequency'])
if d['cmd']==1:
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'])
mqtt_client.publish(f'ahoy/{src}/emeter-dc/0/power', d['dc'][0]['power'])
mqtt_client.publish(f'ahoy/{src}/emeter-dc/0/voltage', d['dc'][0]['voltage'])
mqtt_client.publish(f'ahoy/{src}/emeter-dc/0/current', d['dc'][0]['current'])
mqtt_client.publish(f'ahoy/{src}/emeter-dc/1/power', d['dc'][1]['power'])
mqtt_client.publish(f'ahoy/{src}/emeter-dc/1/voltage', d['dc'][1]['voltage'])
mqtt_client.publish(f'ahoy/{src}/emeter-dc/1/current', d['dc'][1]['current'])
if d['cmd']==131:
mqtt_client.publish(f'ahoy/{src}/temperature', d['t_C'])
mqtt_client.publish(f'ahoy/{src}/temperature', d['temperature'])
mqtt_client.publish(f'ahoy/{src}/emeter/0/current', d['ac'][0]['current'])
def main_loop():
@ -260,69 +410,96 @@ def main_loop():
rx_channels = [3,23,61,75]
rx_channel_id = 0
rx_channel = rx_channels[rx_channel_id]
rx_channel_ack = None
rx_error = 0
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(True)
radio.setRetries(15, 2)
radio.setPALevel(RF24_PA_LOW)
#radio.setPALevel(RF24_PA_MAX)
radio.setDataRate(RF24_250KBPS)
radio.openReadingPipe(1,ser_to_esb_addr(dtu_ser))
radio.openWritingPipe(ser_to_esb_addr(inv_ser))
radio.setChannel(rx_channel)
radio.enableDynamicPayloads()
radio.setAutoAck(True)
radio.setPALevel(RF24_PA_MAX)
radio.setDataRate(RF24_250KBPS)
radio.openWritingPipe(ser_to_esb_addr(inv_ser))
while True:
radio.flush_rx()
radio.flush_tx()
radio.openReadingPipe(1,ser_to_esb_addr(dtu_ser))
radio.startListening()
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]))
on_receive(payload, ch_rx=rx_channel, ch_tx=tx_channel)
else:
# pass
# time.sleep(0.01)
radio.stopListening()
radio.setChannel(rx_channel)
radio.startListening()
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)
m_buf = []
# Sweep receive start channel
if not rx_channel_ack:
rx_channel_id = ctr % len(rx_channels)
rx_channel = rx_channels[rx_channel_id]
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]
# Transmit
ts = int(time.time())
payload = compose_0x80_msg(src_ser_no=dtu_ser, dst_ser_no=inv_ser, ts=ts, subtype=b'\x0b')
dt = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
radio.stopListening() # put radio in TX mode
radio.setChannel(tx_channel)
radio.openWritingPipe(ser_to_esb_addr(inv_ser))
t_tx_start = time.monotonic_ns()
tx_status = radio.write(payload) # will always yield 'True' because auto-ack is disabled
t_last_tx = t_tx_end = time.monotonic_ns()
radio.setChannel(rx_channel)
radio.startListening()
ts = int(time.time())
payload = compose_0x80_msg(src_ser_no=dtu_ser, dst_ser_no=inv_ser, ts=ts)
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)} | " + \
last_tx_message = f"{dt} Transmit {ctr:5d}: channel={tx_channel} len={len(payload)} ack={tx_status} | " + \
" ".join([f"{b:02x}" for b in payload]) + "\n"
radio.write(payload) # will always yield 'True' because auto-ack is disabled
t_last_tx = time.monotonic_ns()
ctr = ctr + 1
# Receive loop
t_end = time.monotonic_ns()+1e9
while time.monotonic_ns() < t_end:
has_payload, pipe_number = radio.available_pipe()
if has_payload:
# Data in nRF24 buffer, read it
rx_error = 0
rx_channel_ack = rx_channel
t_end = time.monotonic_ns()+6e7
size = radio.getDynamicPayloadSize()
payload = radio.read(size)
m_buf.append( {
'p': payload,
'ch_rx': rx_channel, 'ch_tx': tx_channel,
'time_rx': datetime.now(), 'latency': time.monotonic_ns()-t_last_tx} )
# Only print last transmittet message if we got any response
print(last_tx_message, end='')
last_tx_message = ''
else:
# No data in nRF rx buffer, search and wait
# Channel lock in (not currently used)
rx_error = rx_error + 1
if rx_error > 0:
rx_channel_ack = None
# Channel hopping
if not rx_channel_ack:
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]
radio.stopListening()
radio.setChannel(rx_channel)
radio.startListening()
time.sleep(0.005)
# Process receive buffer outside time critical receive loop
for param in m_buf:
on_receive(**param)
# Flush console
print(flush=True, end='')

Loading…
Cancel
Save