2 ## This file is part of the libsigrokdecode project.
4 ## Copyright (C) 2020 Gerhard Sittig <gerhard.sittig@gmx.net>
6 ## This program is free software; you can redistribute it and/or modify
7 ## it under the terms of the GNU General Public License as published by
8 ## the Free Software Foundation; either version 2 of the License, or
9 ## (at your option) any later version.
11 ## This program is distributed in the hope that it will be useful,
12 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ## GNU General Public License for more details.
16 ## You should have received a copy of the GNU General Public License
17 ## along with this program; if not, see <http://www.gnu.org/licenses/>.
20 # See the https://www.pjon.org/ PJON project page and especially the
21 # https://www.pjon.org/PJDL-specification-v4.1.php PJDL v4.1 spec for
22 # the "Padded Jittering Data Link" single wire serial data link layer.
25 # - Improve (fix, and extend) carrier sense support. Detection of the
26 # idle/busy connection state is incomplete and fragile. Getting 'IDLE'
27 # operational in the PJDL decoder would greatly help the PJON decoder
28 # to flush ACK details before the start of new frames.
29 # - Check the correctness of timing assumptions. This implementation has
30 # support for tolerances, which the spec does not discuss. Though real
31 # world traffic was found to not decode at all with strict spec values
32 # and without tolerances, while communication peers were able to talk
33 # to each other. This needs more attention.
34 # - Check robustness when input data contains glitches. The spec does
35 # not discuss how to handle these. Data bit sampling happens to work
36 # because their value is taken at the center of the bit time. But
37 # pad bits suffer badly from glitches, which breaks frame inspection
39 # - Cleanup the decoder implementation in general terms. Some details
40 # have become obsolete ("edges", "pads"), and/or are covered by other
42 # - Implement more data link decoders which can feed their output into
43 # the PJON protocol decoder. Candidates are: PJDLR, PJDLS, TSDL.
44 # - Determine whether or not the data link layer should interpret any
45 # frame content. From my perspective it should not, and needs not in
46 # the strict sense. Possible gains would be getting the (expected!)
47 # packet length, or whether a synchronous response is requested. But
48 # this would duplicate knowledge which should remain internal to the
49 # PJON decoder. And this link layer decoder neither shall assume that
50 # the input data would be correct, or complete. Instead the decoder
51 # shall remain usable on captures which demonstrate faults, and be
52 # helpful in pointing them out. The design goal is to extract the
53 # maximum of information possible, and pass it on as transparently
56 import sigrokdecode as srd
57 from common.srdhelper import bitpack
58 from math import ceil, floor
61 OUTPUT_PYTHON format for stacked decoders:
63 General packet format:
66 This is the list of <ptype>s and their respective <pdata> values:
69 - 'IDLE': <pdata> is the pin level (always 0).
70 - 'BUSY': <pdata> is always True.
73 - 'PAD_BIT': <pdata> is the pin level (always 1).
74 - 'DATA_BIT': <pdata> is the pin level (0, or 1).
75 - 'SHORT_BIT': <pdata> is the pin level (always 1).
76 - 'SYNC_LOSS': <pdata> is an arbitrary text (internal use only).
78 Date bytes and frames:
79 - 'SYNC_PAD': <pdata> is True. Spans the high pad bit as well as the
81 - 'DATA_BYTE': <pdata> is the byte value (0..255).
82 - 'FRAME_INIT': <pdata> is True. Spans three sync pads.
83 - 'FRAME_DATA': <pdata> is the sequence of bytes in the frame. Non-data
84 phases in the frame get represented by strings instead of numbers
85 ('INIT', 'SYNC', 'SHORT', 'WAIT'). Frames can be incomplete, depending
86 on the decoder's input data.
87 - 'SYNC_RESP_WAIT': <pdata> is always True.
89 Notice that this link layer decoder is not aware of frame content. Will
90 neither check packet length, nor variable width fields, nor verify the
91 presence of requested synchronous responses. Cannot tell the sequence of
92 frame bytes then ACK bytes (without wait phase) from just frame bytes.
93 An upper layer protocol decoder will interpret content, the link layer
94 decoder remains as transparent as possible, and will neither assume
95 correct nor complete input data.
98 # Carrier sense, and synchronization loss implementation is currently
99 # incomplete, and results in too many too short annotations, some of
100 # them spurious and confusing. TODO Improve the implementation.
101 _with_ann_carrier = False
102 _with_ann_sync_loss = False
105 ANN_CARRIER_BUSY, ANN_CARRIER_IDLE, \
106 ANN_PAD_BIT, ANN_LOW_BIT, ANN_DATA_BIT, ANN_SHORT_DATA, ANN_SYNC_LOSS, \
108 ANN_FRAME_INIT, ANN_FRAME_BYTES, ANN_FRAME_WAIT, \
111 class SamplerateError(Exception):
114 class Decoder(srd.Decoder):
118 longname = 'Padded Jittering Data Link'
119 desc = 'PJDL, a single wire serial link layer for PJON.'
122 outputs = ['pjon-link']
125 {'id': 'data' , 'name': 'DATA', 'desc': 'Single wire data'},
128 {'id': 'mode', 'desc': 'Communication mode',
129 'default': 1, 'values': (1, 2, 3, 4)},
130 {'id': 'idle_add_us', 'desc': 'Added idle time (us)', 'default': 4},
133 ('cs_busy', 'Carrier busy'),
134 ('cs_idle', 'Carrier idle'),
135 ('bit_pad', 'Pad bit'),
136 ('bit_low', 'Low bit'),
137 ('bit_data', 'Data bit'),
138 ('bit_short', 'Short data'),
139 ('sync_loss', 'Sync loss'),
140 ('byte', 'Data byte'),
141 ('frame_init', 'Frame init'),
142 ('frame_bytes', 'Frame bytes'),
143 ('frame_wait', 'Frame wait'),
146 ('carriers', 'Carriers', (ANN_CARRIER_BUSY, ANN_CARRIER_IDLE,)),
147 ('bits', 'Bits', (ANN_PAD_BIT, ANN_LOW_BIT, ANN_DATA_BIT, ANN_SHORT_DATA,)),
148 ('bytes', 'Bytes', (ANN_FRAME_INIT, ANN_DATA_BYTE, ANN_FRAME_WAIT,)),
149 ('frames', 'Frames', (ANN_FRAME_BYTES,)),
150 ('warns', 'Warnings', (ANN_SYNC_LOSS,)),
153 # Communication modes' data bit and pad bit duration (in us), and
154 # tolerances in percent and absolute (us).
170 def reset_state(self):
171 self.carrier_want_idle = True
172 self.carrier_is_busy = False
173 self.carrier_is_idle = False
174 self.carrier_idle_ss = None
175 self.carrier_busy_ss = None
176 self.syncpad_fall_ss = None
180 self.sync_pads = None
181 self.data_bits = None
182 self.frame_bytes = None
183 self.short_bits = None
186 self.out_ann = self.register(srd.OUTPUT_ANN)
187 self.out_python = self.register(srd.OUTPUT_PYTHON)
189 def metadata(self, key, value):
190 if key == srd.SRD_CONF_SAMPLERATE:
191 self.samplerate = value
194 def putg(self, ss, es, data):
196 if not _with_ann_carrier and cls in (ANN_CARRIER_BUSY, ANN_CARRIER_IDLE):
198 if not _with_ann_sync_loss and cls in (ANN_SYNC_LOSS,):
200 self.put(ss, es, self.out_ann, data)
202 def putpy(self, ss, es, ptype, pdata):
203 self.put(ss, es, self.out_python, [ptype, pdata])
205 def symbols_clear(self):
206 syms = self.symbols or []
210 def symbols_append(self, ss, es, symbol, data = None):
211 if self.symbols is None:
213 item = (ss, es, symbol, data)
214 self.symbols.append(item)
216 def symbols_get_last(self, count = None):
221 if len(self.symbols) < count:
223 items = self.symbols[-count:]
228 def symbols_update_last(self, ss, es, symbol, data = None):
231 item = list(self.symbols[-1])
236 if symbol is not None:
240 self.symbols[-1] = tuple(item)
242 def symbols_has_prev(self, want_items):
243 if not isinstance(want_items, (list, tuple,)):
244 want_items = [want_items]
245 if self.symbols is None:
247 if len(self.symbols) < len(want_items):
249 sym_off = len(self.symbols) - len(want_items)
250 for idx, want_item in enumerate(want_items):
251 if self.symbols[sym_off + idx][2] != want_item:
255 def symbols_collapse(self, count, symbol, data = None, squeeze = None):
256 if self.symbols is None:
258 if len(self.symbols) < count:
260 self.symbols, last_data = self.symbols[:-count], self.symbols[-count:]
261 while squeeze and self.symbols and self.symbols[-1][2] == squeeze:
262 last_data.insert(0, self.symbols.pop())
263 ss, es = last_data[0][0], last_data[-1][1]
266 item = (ss, es, symbol, data)
267 self.symbols.append(item)
269 def frame_flush(self):
270 syms = self.symbols_clear()
271 while syms and syms[0][2] == 'IDLE':
273 while syms and syms[-1][2] == 'IDLE':
280 if sym[2] == 'FRAME_INIT':
284 if sym[2] == 'SYNC_PAD':
285 if not text or text[-1] != 'SYNC':
289 if sym[2] == 'DATA_BYTE':
290 b = [bit[3] for bit in sym[3] if bit[2] == 'DATA_BIT']
292 text.append('{:02x}'.format(b))
295 if sym[2] == 'SHORT_BIT':
296 if not text or text[-1] != 'SHORT':
300 if sym[2] == 'WAIT_ACK':
304 text = ' '.join(text)
305 ss, es = syms[0][0], syms[-1][1]
306 self.putg(ss, es, [ANN_FRAME_BYTES, [text]])
307 self.putpy(ss, es, 'FRAME_DATA', data)
309 def carrier_flush(self):
310 # Force annotations if BUSY started, or if IDLE tracking started
311 # and kept running for long enough. This will be called before
312 # internal state reset, so we won't manipulate internal variables,
313 # and can afford to emit annotations which haven't met their
314 # proper end condition yet.
315 if self.carrier_busy_ss:
316 ss, es = self.carrier_busy_ss, self.samplenum
317 self.putg(ss, es, [ANN_CARRIER_BUSY, ['BUSY']])
318 if self.carrier_idle_ss:
319 ss, es = self.carrier_idle_ss, self.samplenum
320 ss += int(self.idle_width)
322 self.putg(ss, es, [ANN_CARRIER_IDLE, ['IDLE']])
324 def carrier_set_idle(self, on, ss, es):
326 # IDLE starts here, or continues.
327 if not self.carrier_idle_ss:
328 self.carrier_idle_ss = int(ss)
329 if not self.symbols_has_prev('IDLE'):
330 self.symbols_append(ss, ss, 'IDLE')
331 self.symbols_update_last(None, es, None)
332 # HACK We have seen an IDLE condition. This implementation
333 # loses details which are used to track IDLE, but it's more
334 # important to start accumulation of a new frame here.
338 self.carrier_is_idle = True
339 self.carrier_want_idle = False
342 if self.symbols_has_prev('IDLE'):
343 self.symbols_update_last(None, es, None)
345 self.carrier_is_idle = False
346 self.carrier_idle_ss = None
348 def carrier_set_busy(self, on, snum):
349 self.carrier_is_busy = on
351 self.carrier_is_idle = None
352 if not self.carrier_busy_ss:
353 self.carrier_busy_ss = snum
355 if self.carrier_busy_ss:
356 self.putg(self.carrier_busy_ss, snum, [ANN_CARRIER_BUSY, ['BUSY']])
357 self.carrier_busy_ss = None
358 self.carrier_is_busy = False
360 def carrier_check(self, level, snum):
362 # When HIGH is seen, immediately end IDLE and switch to BUSY.
364 self.carrier_set_idle(False, snum, snum)
365 self.carrier_set_busy(True, snum)
368 # LOW is seen. Start tracking an IDLE period if not done yet.
369 if not self.carrier_idle_ss:
370 self.carrier_idle_ss = int(snum)
372 # End BUSY when LOW persisted for an exact data byte's length.
373 # Start IDLE when LOW persisted for a data byte's length plus
374 # the user specified additional period.
375 span = snum - self.carrier_idle_ss
376 if span >= self.byte_width:
377 self.carrier_set_busy(False, snum)
378 if span >= self.idle_width:
379 self.carrier_set_idle(True, self.carrier_idle_ss + self.idle_width, snum)
381 def span_prepare(self):
382 '''Prepare calculation of durations in terms of samples.'''
384 # Determine samples per microsecond, and sample counts for
385 # several bit types, and sample count for a data byte's
386 # length, including optional extra time. Determine ranges
387 # for bit widths (tolerance margin).
389 # Get times in microseconds.
390 self.data_width, self.pad_width = self.mode_times[self.options['mode']]
391 self.byte_width = self.pad_width + 9 * self.data_width
392 self.add_idle_width = self.options['idle_add_us']
393 self.idle_width = self.byte_width + self.add_idle_width
395 # Derive ranges (add tolerance) and scale to sample counts.
396 self.usec_width = self.samplerate / 1e6
397 self.hold_high_width = 9 * self.time_tol_abs * self.usec_width
399 def _get_range(width):
400 reladd = self.time_tol_perc / 100
401 absadd = self.time_tol_abs
402 lower = min(width * (1 - reladd), width - absadd)
403 upper = max(width * (1 + reladd), width + absadd)
404 lower = floor(lower * self.usec_width)
405 upper = ceil(upper * self.usec_width)
406 return (lower, upper + 1)
408 self.data_bit_1_range = _get_range(self.data_width * 1)
409 self.data_bit_2_range = _get_range(self.data_width * 2)
410 self.data_bit_3_range = _get_range(self.data_width * 3)
411 self.data_bit_4_range = _get_range(self.data_width * 4)
412 self.short_data_range = _get_range(self.data_width / 4)
413 self.pad_bit_range = _get_range(self.pad_width)
415 self.data_width *= self.usec_width
416 self.pad_width *= self.usec_width
417 self.byte_width *= self.usec_width
418 self.idle_width *= self.usec_width
420 self.lookahead_width = int(4 * self.data_width)
422 def span_snum_to_us(self, count):
423 return count / self.usec_width
425 def span_is_pad(self, span):
426 return span in range(*self.pad_bit_range)
428 def span_is_data(self, span):
429 if span in range(*self.data_bit_1_range):
431 if span in range(*self.data_bit_2_range):
433 if span in range(*self.data_bit_3_range):
435 if span in range(*self.data_bit_4_range):
439 def span_is_short(self, span):
440 return span in range(*self.short_data_range)
442 def wait_until(self, want):
443 '''Wait until a given location, but keep sensing carrier.'''
445 # Implementor's note: Avoids skip values below 1. This version
446 # "may overshoot" by one sample. Which should be acceptable for
447 # this specific use case (can put the sample point of a bit time
448 # out of the center by some 4% under worst case conditions).
452 diff = max(want - self.samplenum, 1)
453 pins = self.wait([{PIN_DATA: 'e'}, {'skip': diff}])
454 self.carrier_check(pins[PIN_DATA], self.samplenum)
455 if self.samplenum >= want:
460 if not self.samplerate or self.samplerate < 1e6:
461 raise SamplerateError('Need a samplerate of at least 1MSa/s')
463 # As a special case the first low period in the input capture is
464 # saught regardless of whether we can see its falling edge. This
465 # approach is also used to recover after synchronization was lost.
467 # The important condition here in the main loop is: Get the next
468 # edge's position, but time out after a maximum period of four
469 # data bits. This allows for the detection of SYNC pulses, also
470 # responds "soon enough" to DATA bits where edges can be few
471 # within a data byte. Also avoids excessive waits for unexpected
472 # communication errors.
474 # DATA bits within a byte are taken at fixed intervals relative
475 # to the SYNC-PAD's falling edge. It's essential to check the
476 # carrier at every edge, also during DATA bit sampling. Simple
477 # skips to the desired sample point could break that feature.
480 # Help kick-start the IDLE condition detection after
481 # decoder state reset.
483 curr_level, = self.wait({PIN_DATA: 'l'})
484 self.carrier_check(curr_level, self.samplenum)
485 self.edges = [self.samplenum]
488 # Advance to the next edge, or over a medium span without an
489 # edge. Prepare to classify the distance to derive bit types
490 # from these details.
491 last_snum = self.samplenum
492 curr_level, = self.wait([{PIN_DATA: 'e'}, {'skip': self.lookahead_width}])
493 self.carrier_check(curr_level, self.samplenum)
494 bit_level = curr_level
495 edge_seen = self.matched[0]
497 bit_level = 1 - bit_level
499 self.edges = [self.samplenum]
501 self.edges.append(self.samplenum)
502 curr_snum = self.samplenum
504 # Check bit width (can also be multiple data bits).
505 span = self.edges[-1] - self.edges[-2]
506 is_pad = bit_level and self.span_is_pad(span)
507 is_data = self.span_is_data(span)
508 is_short = bit_level and self.span_is_short(span)
511 ss, es = last_snum, curr_snum
512 texts = ['PAD', '{:d}'.format(bit_level)]
513 self.putg(ss, es, [ANN_PAD_BIT, texts])
514 self.symbols_append(ss, es, 'PAD_BIT', bit_level)
515 ss, es = self.symbols_get_last()[:2]
516 self.putpy(ss, es, 'PAD_BIT', bit_level)
520 ss, es = last_snum, curr_snum
521 texts = ['SHORT', '{:d}'.format(bit_level)]
522 self.putg(ss, es, [ANN_SHORT_DATA, texts])
523 self.symbols_append(ss, es, 'SHORT_BIT', bit_level)
524 ss, es = self.symbols_get_last()[:2]
525 self.putpy(ss, es, 'SHORT_BIT', bit_level)
528 # Force IDLE period check when the decoder seeks to sync
529 # to the input data stream.
530 if not bit_level and not self.symbols and self.carrier_want_idle:
533 # Accept arbitrary length LOW phases after DATA bytes(!) or
534 # SHORT pulses, but not within a DATA byte or SYNC-PAD etc.
535 # This covers the late start of the next SYNC-PAD (byte of
536 # a frame, or ACK byte after a frame, or the start of the
539 if self.symbols_has_prev('DATA_BYTE'):
541 if self.symbols_has_prev('SHORT_BIT'):
543 if self.symbols_has_prev('WAIT_ACK'):
546 # Get (consume!) the LOW DATA bit after a PAD.
548 if is_data and not bit_level and self.symbols_has_prev('PAD_BIT'):
551 next_snum = int(last_snum + self.data_width)
552 ss, es = last_snum, next_snum
553 texts = ['ZERO', '{:d}'.format(bit_level)]
554 self.putg(ss, es, [ANN_LOW_BIT, texts])
555 self.symbols_append(ss, es, 'ZERO_BIT', bit_level)
556 ss, es = self.symbols_get_last()[:2]
557 self.putpy(ss, es, 'DATA_BIT', bit_level)
558 self.data_fall_time = last_snum
559 last_snum = next_snum
560 # Turn the combination of PAD and LOW DATA into SYNC-PAD.
561 # Start data bit accumulation after a SYNC-PAD was seen.
562 sync_pad_seq = ['PAD_BIT', 'ZERO_BIT']
563 if self.symbols_has_prev(sync_pad_seq):
564 self.symbols_collapse(len(sync_pad_seq), 'SYNC_PAD')
565 ss, es = self.symbols_get_last()[:2]
566 self.putpy(ss, es, 'SYNC_PAD', True)
568 # Turn three subsequent SYNC-PAD into FRAME-INIT. Start the
569 # accumulation of frame bytes when FRAME-INIT was seen.
570 frame_init_seq = 3 * ['SYNC_PAD']
571 if self.symbols_has_prev(frame_init_seq):
572 self.symbols_collapse(len(frame_init_seq), 'FRAME_INIT')
573 # Force a flush of the previous frame after we have
574 # reliably detected the start of another one. This is a
575 # workaround for this decoder's inability to detect the
576 # end of a frame after an ACK was seen or byte counts
577 # have been reached. We cannot assume perfect input,
578 # thus we leave all interpretation of frame content to
579 # upper layers. Do keep the recently queued FRAME_INIT
580 # symbol across the flush operation.
581 if len(self.symbols) > 1:
582 keep = self.symbols.pop(-1)
585 self.symbols.append(keep)
586 ss, es = self.symbols_get_last()[:2]
587 texts = ['FRAME INIT', 'INIT', 'I']
588 self.putg(ss, es, [ANN_FRAME_INIT, texts])
589 self.putpy(ss, es, 'FRAME_INIT', True)
590 self.frame_bytes = []
591 # Collapse SYNC-PAD after SHORT+ into a WAIT-ACK. Include
592 # all leading SHORT bits in the WAIT as well.
593 wait_ack_seq = ['SHORT_BIT', 'SYNC_PAD']
594 if self.symbols_has_prev(wait_ack_seq):
595 self.symbols_collapse(len(wait_ack_seq), 'WAIT_ACK',
596 squeeze = 'SHORT_BIT')
597 ss, es = self.symbols_get_last()[:2]
598 texts = ['WAIT for sync response', 'WAIT response', 'WAIT', 'W']
599 self.putg(ss, es, [ANN_FRAME_WAIT, texts])
600 self.putpy(ss, es, 'SYNC_RESP_WAIT', True)
601 if took_low and not is_data:
602 # Start at the very next edge if we just consumed a LOW
603 # after a PAD bit, and the DATA bit count is exhausted.
604 # This improves robustness, deals with inaccurate edge
605 # positions. (Motivated by real world captures, the spec
606 # would not discuss bit time tolerances.)
609 # When we get here, the only remaining (the only supported)
610 # activity is the collection of a data byte's DATA bits.
611 # These are not taken by the main loop's "edge search, with
612 # a timeout" approach, which is "too tolerant". Instead all
613 # DATA bits get sampled at a fixed interval and relative to
614 # the SYNC-PAD's falling edge. We expect to have seen the
615 # data byte' SYNC-PAD before. If we haven't, the decoder is
616 # not yet synchronized to the input data.
618 fast_cont = edge_seen and curr_level
619 ss, es = last_snum, curr_snum
620 texts = ['failed pulse length check', 'pulse length', 'length']
621 self.putg(ss, es, [ANN_SYNC_LOSS, texts])
626 self.edges = [self.samplenum]
628 if not self.symbols_has_prev('SYNC_PAD'):
629 # Fast reponse to the specific combination of: no-sync,
630 # edge seen, and current high level. In this case we
631 # can reset internal state, but also can continue the
632 # interpretation right after the most recently seen
633 # rising edge, which could start the next PAD time.
634 # Otherwise continue slow interpretation after reset.
635 fast_cont = edge_seen and curr_level
640 self.edges = [self.samplenum]
643 # The main loop's "edge search with period timeout" approach
644 # can have provided up to three more DATA bits after the LOW
645 # bit of the SYNC-PAD. Consume them immediately in that case,
646 # otherwise .wait() for their sample point. Stick with float
647 # values for bit sample points and bit time boundaries for
648 # improved accuracy, only round late to integers when needed.
650 bit_ss = self.data_fall_time + self.data_width
651 for bit_idx in range(8):
652 bit_es = bit_ss + self.data_width
653 bit_snum = (bit_es + bit_ss) / 2
654 if bit_snum > self.samplenum:
655 bit_level, = self.wait_until(bit_snum)
656 ss, es = ceil(bit_ss), floor(bit_es)
657 texts = ['{:d}'.format(bit_level)]
658 self.putg(ss, es, [ANN_DATA_BIT, texts])
659 self.symbols_append(ss, es, 'DATA_BIT', bit_level)
660 ss, es = self.symbols_get_last()[:2]
661 self.putpy(ss, es, 'DATA_BIT', bit_level)
662 bit_field.append(bit_level)
663 if self.data_bits is not None:
664 self.data_bits.append(bit_level)
667 curr_level, = self.wait_until(end_snum)
668 curr_snum = self.samplenum
670 # We are at the exact _calculated_ boundary of the last DATA
671 # bit time. Improve robustness for those situations where
672 # the transmitter's and the sender's timings differ within a
673 # margin, and the transmitter may hold the last DATA bit's
674 # HIGH level for a little longer.
676 hold = self.hold_high_width
677 curr_level, = self.wait([{PIN_DATA: 'l'}, {'skip': int(hold)}])
678 self.carrier_check(curr_level, self.samplenum)
679 curr_snum = self.samplenum
681 # Get the byte value from the bits (when available).
682 # TODO Has the local 'bit_field' become obsolete, or should
683 # self.data_bits go away?
684 data_byte = bitpack(bit_field)
685 if self.data_bits is not None:
686 data_byte = bitpack(self.data_bits)
687 self.data_bits.clear()
688 if self.frame_bytes is not None:
689 self.frame_bytes.append(data_byte)
691 # Turn a sequence of a SYNC-PAD and eight DATA bits into a
693 byte_seq = ['SYNC_PAD'] + 8 * ['DATA_BIT']
694 if self.symbols_has_prev(byte_seq):
695 self.symbols_collapse(len(byte_seq), 'DATA_BYTE')
696 ss, es = self.symbols_get_last()[:2]
697 texts = ['{:02x}'.format(data_byte)]
698 self.putg(ss, es, [ANN_DATA_BYTE, texts])
699 self.putpy(ss, es, 'DATA_BYTE', data_byte)
701 # Optionally terminate the accumulation of a frame when a
702 # WAIT-ACK period was followed by a DATA-BYTE? This could
703 # flush the current packet before the next FRAME-INIT or
704 # IDLE are seen, and increases usability for short input
705 # data (aggressive trimming). It won't help when WAIT is
707 sync_resp_seq = ['WAIT_ACK'] + ['DATA_BYTE']
708 if self.symbols_has_prev(sync_resp_seq):