# specification, which can use different link layers.
# TODO
-# - Handle RX and TX identifiers in separate routines, including the
-# optional bus identifers. Track those addresses, add formatters for
-# them, and emit "communication relation" details.
-# - Check for correct endianess in variable width fields. The spec says
-# "network order", that's what this implementation uses.
# - Check for the correct order of optional fields (the spec is not as
# explicit on these details as I'd expect).
# - Check decoder's robustness, completeness, and correctness when more
self.reset_frame()
def reset_frame(self):
+ self.frame_ss = None
+ self.frame_es = None
+ self.frame_rx_id = None
+ self.frame_tx_id = None
+ self.frame_payload_text = None
self.frame_bytes = None
+ self.frame_has_ack = None
self.ack_bytes = None
self.ann_ss = None
self.ann_es = None
def frame_flush(self):
if not self.frame_bytes:
return
- # TODO Emit "communication relation" details here?
+ if not self.frame_ss or not self.frame_es:
+ return
+
+ # Emit "communication relation" details.
+ text = []
+ if self.frame_rx_id is not None:
+ text.append("RX {}".format(self.frame_rx_id[-1]))
+ if self.frame_tx_id is not None:
+ text.append("TX {}".format(self.frame_tx_id[-1]))
+ if self.frame_payload_text is not None:
+ text.append("DATA {}".format(self.frame_payload_text))
+ if self.frame_has_ack is not None:
+ text.append("ACK {:02x}".format(self.frame_has_ack))
+ if text:
+ text = " - ".join(text)
+ self.putg(self.frame_ss, self.frame_es, ANN_RELATION, [text])
def handle_field_get_desc(self, idx = None):
'''Lookup description of a PJON frame field.'''
self.field_desc_idx = 0
self.field_desc_got = 0
+ self.frame_ss = None
+ self.frame_es = None
self.frame_rx_id = None
self.frame_is_broadcast = None
self.frame_tx_id = None
self.frame_payload = None
+ self.frame_payload_text = None
self.frame_has_ack = None
def handle_field_rx_id(self, b):
b = b[0]
- # Track RX info for communication relation emission.
- self.frame_rx_id = b
- self.frame_is_broadcast = b == 0
-
# Provide text presentation, caller emits frame field annotation.
if b == 255: # "not assigned"
id_txt = 'NA'
'RX_ID {}'.format(id_txt),
'{}'.format(id_txt),
]
+
+ # Track RX info for communication relation emission.
+ self.frame_rx_id = (b, id_txt)
+ self.frame_is_broadcast = b == 0
+
return texts
def handle_field_config(self, b):
u16_fmt = '>H'
u32_fmt = '>L'
len_fmt = u16_fmt if self.cfg_len16 else u8_fmt
+ bus_fmt = '>4B'
crc_fmt = u32_fmt if self.cfg_crc32 else u8_fmt
self.cfg_overhead = 0
self.cfg_overhead += struct.calcsize(u8_fmt) # receiver ID
# condition checks, and raise awareness for incomplete sequences
# during maintenance.
self.handle_field_add_desc(len_fmt, self.handle_field_pkt_len, ANN_PKT_LEN)
- self.handle_field_add_desc(u8_fmt, self.handle_field_init_crc, ANN_META_CRC)
+ self.handle_field_add_desc(u8_fmt, self.handle_field_meta_crc, ANN_META_CRC)
if self.cfg_shared:
- self.handle_field_add_desc(u32_fmt, ['RX_BUS {:08x}', '{:08x}'], ANN_ANON_DATA)
+ self.handle_field_add_desc(bus_fmt, self.handle_field_rx_bus, ANN_ANON_DATA)
if self.cfg_tx_info:
if self.cfg_shared:
- self.handle_field_add_desc(u32_fmt, ['TX_BUS {:08x}', '{:08x}'], ANN_ANON_DATA)
- self.handle_field_add_desc(u8_fmt, ['TX_ID {:d}', '{:d}'], ANN_ANON_DATA)
+ self.handle_field_add_desc(bus_fmt, self.handle_field_tx_bus, ANN_ANON_DATA)
+ self.handle_field_add_desc(u8_fmt, self.handle_field_tx_id, ANN_ANON_DATA)
if self.cfg_port:
self.handle_field_add_desc(u16_fmt, ['PORT {:d}', '{:d}'], ANN_ANON_DATA)
if self.cfg_pkt_id:
]
return texts
- def handle_field_init_crc(self, b):
+ def handle_field_meta_crc(self, b):
'''Process initial CRC (meta) field of a PJON frame.'''
# Caller provides a list of values. We want a single scalar.
b = b[0]
b = b[0]
return self.handle_field_common_crc(b, False)
+ def handle_field_common_bus(self, b):
+ '''Common handling of bus ID details. Used for RX and TX.'''
+ bus_id = b[:4]
+ bus_num = struct.unpack('>L', bytearray(bus_id))
+ bus_txt = '.'.join(['{:d}'.format(b) for b in bus_id])
+ return bus_num, bus_txt
+
+ def handle_field_rx_bus(self, b):
+ '''Process receiver bus ID field of a PJON frame.'''
+
+ # When we get here, there always should be an RX ID already.
+ bus_num, bus_txt = self.handle_field_common_bus(b[:4])
+ rx_txt = "{} {}".format(bus_txt, self.frame_rx_id[-1])
+ self.frame_rx_id = (bus_num, self.frame_rx_id[0], rx_txt)
+
+ # Provide text representation for frame field, caller emits
+ # the annotation.
+ texts = [
+ 'RX_BUS {}'.format(bus_txt),
+ bus_txt,
+ ]
+ return texts
+
+ def handle_field_tx_bus(self, b):
+ '''Process transmitter bus ID field of a PJON frame.'''
+
+ # The TX ID field is optional, as is the use of bus ID fields.
+ # In the TX info case the TX bus ID is seen before the TX ID.
+ bus_num, bus_txt = self.handle_field_common_bus(b[:4])
+ self.frame_tx_id = (bus_num, None, bus_txt)
+
+ # Provide text representation for frame field, caller emits
+ # the annotation.
+ texts = [
+ 'TX_BUS {}'.format(bus_txt),
+ bus_txt,
+ ]
+ return texts
+
+ def handle_field_tx_id(self, b):
+ '''Process transmitter ID field of a PJON frame.'''
+
+ b = b[0]
+
+ id_txt = "{:d}".format(b)
+ if self.frame_tx_id is None:
+ self.frame_tx_id = (b, id_txt)
+ else:
+ tx_txt = "{} {}".format(self.frame_tx_id[-1], id_txt)
+ self.frame_tx_id = (self.frame_tx_id[0], b, tx_txt)
+
+ # Provide text representation for frame field, caller emits
+ # the annotation.
+ texts = [
+ 'TX_ID {}'.format(id_txt),
+ id_txt,
+ ]
+ return texts
+
def handle_field_payload(self, b):
'''Process payload data field of a PJON frame.'''
+ text = ' '.join(['{:02x}'.format(v) for v in b])
self.frame_payload = b[:]
+ self.frame_payload_text = text
- text = ' '.join(['{:02x}'.format(v) for v in b])
texts = [
'PAYLOAD {}'.format(text),
text,
self.reset_frame()
self.frame_bytes = []
self.handle_field_seed_desc()
+ self.frame_ss = ss
+ self.frame_es = es
return
# Use IDLE as another (earlier) trigger to flush frames. Also
# which corresponds to its most recently seen leader.
if ptype == 'DATA_BYTE':
b = pdata
+ self.frame_es = es
# Are we collecting response bytes (ACK)?
if self.ack_bytes is not None: