]> sigrok.org Git - libsigrokdecode.git/blame - decoders/pjdl/pd.py
pjon: use underscore in input/output names for stacked decoders
[libsigrokdecode.git] / decoders / pjdl / pd.py
CommitLineData
db18ba49
GS
1##
2## This file is part of the libsigrokdecode project.
3##
4## Copyright (C) 2020 Gerhard Sittig <gerhard.sittig@gmx.net>
5##
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.
10##
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.
15##
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/>.
18##
19
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.
23
24# TODO
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
38# as well.
39# - Cleanup the decoder implementation in general terms. Some details
40# have become obsolete ("edges", "pads"), and/or are covered by other
41# code paths.
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
54# as possible.
55
56import sigrokdecode as srd
57from common.srdhelper import bitpack
58from math import ceil, floor
59
60'''
61OUTPUT_PYTHON format for stacked decoders:
62
63General packet format:
64[<ptype>, <pdata>]
65
66This is the list of <ptype>s and their respective <pdata> values:
67
68Carrier sense:
69- 'IDLE': <pdata> is the pin level (always 0).
70- 'BUSY': <pdata> is always True.
71
72Raw bit slots:
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).
77
78Date bytes and frames:
79- 'SYNC_PAD': <pdata> is True. Spans the high pad bit as well as the
80 low data bit.
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.
88
89Notice that this link layer decoder is not aware of frame content. Will
90neither check packet length, nor variable width fields, nor verify the
91presence of requested synchronous responses. Cannot tell the sequence of
92frame bytes then ACK bytes (without wait phase) from just frame bytes.
93An upper layer protocol decoder will interpret content, the link layer
94decoder remains as transparent as possible, and will neither assume
95correct nor complete input data.
96'''
97
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
103
104PIN_DATA, = range(1)
105ANN_CARRIER_BUSY, ANN_CARRIER_IDLE, \
106ANN_PAD_BIT, ANN_LOW_BIT, ANN_DATA_BIT, ANN_SHORT_DATA, ANN_SYNC_LOSS, \
107ANN_DATA_BYTE, \
108ANN_FRAME_INIT, ANN_FRAME_BYTES, ANN_FRAME_WAIT, \
109 = range(11)
110
111class SamplerateError(Exception):
112 pass
113
114class Decoder(srd.Decoder):
115 api_version = 3
116 id = 'pjdl'
117 name = 'PJDL'
118 longname = 'Padded Jittering Data Link'
119 desc = 'PJDL, a single wire serial link layer for PJON.'
120 license = 'gplv2+'
121 inputs = ['logic']
12bbd670 122 outputs = ['pjon_link']
db18ba49
GS
123 tags = ['Embedded']
124 channels = (
125 {'id': 'data' , 'name': 'DATA', 'desc': 'Single wire data'},
126 )
127 options = (
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},
131 )
132 annotations = (
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'),
144 )
145 annotation_rows = (
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,)),
151 )
152
153 # Communication modes' data bit and pad bit duration (in us), and
154 # tolerances in percent and absolute (us).
155 mode_times = {
156 1: (44, 116),
157 2: (40, 92),
158 3: (28, 88),
159 4: (26, 60),
160 }
161 time_tol_perc = 10
162 time_tol_abs = 1.5
163
164 def __init__(self):
165 self.reset()
166
167 def reset(self):
168 self.reset_state()
169
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
177
178 self.edges = None
179 self.symbols = None
180 self.sync_pads = None
181 self.data_bits = None
182 self.frame_bytes = None
183 self.short_bits = None
184
185 def start(self):
186 self.out_ann = self.register(srd.OUTPUT_ANN)
187 self.out_python = self.register(srd.OUTPUT_PYTHON)
188
189 def metadata(self, key, value):
190 if key == srd.SRD_CONF_SAMPLERATE:
191 self.samplerate = value
192 self.span_prepare()
193
194 def putg(self, ss, es, data):
195 cls = data[0]
196 if not _with_ann_carrier and cls in (ANN_CARRIER_BUSY, ANN_CARRIER_IDLE):
197 return
198 if not _with_ann_sync_loss and cls in (ANN_SYNC_LOSS,):
199 return
200 self.put(ss, es, self.out_ann, data)
201
202 def putpy(self, ss, es, ptype, pdata):
203 self.put(ss, es, self.out_python, [ptype, pdata])
204
205 def symbols_clear(self):
206 syms = self.symbols or []
207 self.symbols = []
208 return syms
209
210 def symbols_append(self, ss, es, symbol, data = None):
211 if self.symbols is None:
212 self.symbols = []
213 item = (ss, es, symbol, data)
214 self.symbols.append(item)
215
216 def symbols_get_last(self, count = None):
217 if not self.symbols:
218 return None
219 if count is None:
220 count = 1
221 if len(self.symbols) < count:
222 return None
223 items = self.symbols[-count:]
224 if count == 1:
225 items = items[0]
226 return items
227
228 def symbols_update_last(self, ss, es, symbol, data = None):
229 if not self.symbols:
230 return None
231 item = list(self.symbols[-1])
232 if ss is not None:
233 item[0] = ss
234 if es is not None:
235 item[1] = es
236 if symbol is not None:
237 item[2] = symbol
238 if data is not None:
239 item[3] = data
240 self.symbols[-1] = tuple(item)
241
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:
246 return False
247 if len(self.symbols) < len(want_items):
248 return False
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:
252 return False
253 return True
254
255 def symbols_collapse(self, count, symbol, data = None, squeeze = None):
256 if self.symbols is None:
257 return None
258 if len(self.symbols) < count:
259 return None
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]
264 if data is None:
265 data = last_data
266 item = (ss, es, symbol, data)
267 self.symbols.append(item)
268
269 def frame_flush(self):
270 syms = self.symbols_clear()
271 while syms and syms[0][2] == 'IDLE':
272 syms.pop(0)
273 while syms and syms[-1][2] == 'IDLE':
274 syms.pop(-1)
275 if not syms:
276 return
277 text = []
278 data = []
279 for sym in syms:
280 if sym[2] == 'FRAME_INIT':
281 text.append('INIT')
282 data.append('INIT')
283 continue
284 if sym[2] == 'SYNC_PAD':
285 if not text or text[-1] != 'SYNC':
286 text.append('SYNC')
287 data.append('SYNC')
288 continue
289 if sym[2] == 'DATA_BYTE':
290 b = [bit[3] for bit in sym[3] if bit[2] == 'DATA_BIT']
291 b = bitpack(b)
292 text.append('{:02x}'.format(b))
293 data.append(b)
294 continue
295 if sym[2] == 'SHORT_BIT':
296 if not text or text[-1] != 'SHORT':
297 text.append('SHORT')
298 data.append('SHORT')
299 continue
300 if sym[2] == 'WAIT_ACK':
301 text.append('WAIT')
302 data.append('WAIT')
303 continue
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)
308
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)
321 if ss < es:
322 self.putg(ss, es, [ANN_CARRIER_IDLE, ['IDLE']])
323
324 def carrier_set_idle(self, on, ss, es):
325 if on:
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.
335 self.frame_flush()
336 self.reset_state()
337 # end of HACK
338 self.carrier_is_idle = True
339 self.carrier_want_idle = False
340 return
341 # IDLE ends here.
342 if self.symbols_has_prev('IDLE'):
343 self.symbols_update_last(None, es, None)
344 self.carrier_flush()
345 self.carrier_is_idle = False
346 self.carrier_idle_ss = None
347
348 def carrier_set_busy(self, on, snum):
349 self.carrier_is_busy = on
350 if on:
351 self.carrier_is_idle = None
352 if not self.carrier_busy_ss:
353 self.carrier_busy_ss = snum
354 return
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
359
360 def carrier_check(self, level, snum):
361
362 # When HIGH is seen, immediately end IDLE and switch to BUSY.
363 if level:
364 self.carrier_set_idle(False, snum, snum)
365 self.carrier_set_busy(True, snum)
366 return
367
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)
371
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)
380
381 def span_prepare(self):
382 '''Prepare calculation of durations in terms of samples.'''
383
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).
388
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
394
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
398
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)
407
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)
414
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
419
420 self.lookahead_width = int(4 * self.data_width)
421
422 def span_snum_to_us(self, count):
423 return count / self.usec_width
424
425 def span_is_pad(self, span):
426 return span in range(*self.pad_bit_range)
427
428 def span_is_data(self, span):
429 if span in range(*self.data_bit_1_range):
430 return 1
431 if span in range(*self.data_bit_2_range):
432 return 2
433 if span in range(*self.data_bit_3_range):
434 return 3
435 if span in range(*self.data_bit_4_range):
436 return 4
437 return False
438
439 def span_is_short(self, span):
440 return span in range(*self.short_data_range)
441
442 def wait_until(self, want):
443 '''Wait until a given location, but keep sensing carrier.'''
444
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).
449
450 want = int(want)
451 while True:
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:
456 return pins
457 # UNREACH
458
459 def decode(self):
460 if not self.samplerate or self.samplerate < 1e6:
461 raise SamplerateError('Need a samplerate of at least 1MSa/s')
462
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.
466 #
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.
473 #
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.
478 while True:
479
480 # Help kick-start the IDLE condition detection after
481 # decoder state reset.
482 if not self.edges:
483 curr_level, = self.wait({PIN_DATA: 'l'})
484 self.carrier_check(curr_level, self.samplenum)
485 self.edges = [self.samplenum]
486 continue
487
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]
496 if edge_seen:
497 bit_level = 1 - bit_level
498 if not self.edges:
499 self.edges = [self.samplenum]
500 continue
501 self.edges.append(self.samplenum)
502 curr_snum = self.samplenum
503
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)
509
510 if is_pad:
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)
517 continue
518
519 if is_short:
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)
526 continue
527
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:
531 continue
532
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
537 # next frame).
538 if not bit_level:
539 if self.symbols_has_prev('DATA_BYTE'):
540 continue
541 if self.symbols_has_prev('SHORT_BIT'):
542 continue
543 if self.symbols_has_prev('WAIT_ACK'):
544 continue
545
546 # Get (consume!) the LOW DATA bit after a PAD.
547 took_low = False
548 if is_data and not bit_level and self.symbols_has_prev('PAD_BIT'):
549 took_low = True
550 is_data -= 1
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)
567 self.data_bits = []
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)
583 self.frame_flush()
584 self.symbols.clear()
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.)
607 continue
608
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.
617 if not is_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])
622 self.frame_flush()
623 self.carrier_flush()
624 self.reset_state()
625 if fast_cont:
626 self.edges = [self.samplenum]
627 continue
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
636 self.frame_flush()
637 self.carrier_flush()
638 self.reset_state()
639 if fast_cont:
640 self.edges = [self.samplenum]
641 continue
642
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.
649 bit_field = []
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)
665 bit_ss = bit_es
666 end_snum = bit_es
667 curr_level, = self.wait_until(end_snum)
668 curr_snum = self.samplenum
669
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.
675 if curr_level:
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
680
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)
690
691 # Turn a sequence of a SYNC-PAD and eight DATA bits into a
692 # DATA-BYTE symbol.
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)
700
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
706 # not seen, though.
707 sync_resp_seq = ['WAIT_ACK'] + ['DATA_BYTE']
708 if self.symbols_has_prev(sync_resp_seq):
709 self.frame_flush()