]> sigrok.org Git - libsigrokdecode.git/blame - decoders/pjdl/pd.py
pjdl: prepare for "stretched" timings
[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.
0fb7ce22
GS
390 mode_times = self.mode_times[self.options['mode']]
391 mode_times = [t * 1.0 for t in mode_times]
392 self.data_width, self.pad_width = mode_times
db18ba49
GS
393 self.byte_width = self.pad_width + 9 * self.data_width
394 self.add_idle_width = self.options['idle_add_us']
395 self.idle_width = self.byte_width + self.add_idle_width
396
397 # Derive ranges (add tolerance) and scale to sample counts.
398 self.usec_width = self.samplerate / 1e6
399 self.hold_high_width = 9 * self.time_tol_abs * self.usec_width
400
401 def _get_range(width):
402 reladd = self.time_tol_perc / 100
403 absadd = self.time_tol_abs
404 lower = min(width * (1 - reladd), width - absadd)
405 upper = max(width * (1 + reladd), width + absadd)
406 lower = floor(lower * self.usec_width)
407 upper = ceil(upper * self.usec_width)
408 return (lower, upper + 1)
409
410 self.data_bit_1_range = _get_range(self.data_width * 1)
411 self.data_bit_2_range = _get_range(self.data_width * 2)
412 self.data_bit_3_range = _get_range(self.data_width * 3)
413 self.data_bit_4_range = _get_range(self.data_width * 4)
414 self.short_data_range = _get_range(self.data_width / 4)
415 self.pad_bit_range = _get_range(self.pad_width)
416
417 self.data_width *= self.usec_width
418 self.pad_width *= self.usec_width
419 self.byte_width *= self.usec_width
420 self.idle_width *= self.usec_width
421
422 self.lookahead_width = int(4 * self.data_width)
423
424 def span_snum_to_us(self, count):
425 return count / self.usec_width
426
427 def span_is_pad(self, span):
428 return span in range(*self.pad_bit_range)
429
430 def span_is_data(self, span):
431 if span in range(*self.data_bit_1_range):
432 return 1
433 if span in range(*self.data_bit_2_range):
434 return 2
435 if span in range(*self.data_bit_3_range):
436 return 3
437 if span in range(*self.data_bit_4_range):
438 return 4
439 return False
440
441 def span_is_short(self, span):
442 return span in range(*self.short_data_range)
443
444 def wait_until(self, want):
445 '''Wait until a given location, but keep sensing carrier.'''
446
447 # Implementor's note: Avoids skip values below 1. This version
448 # "may overshoot" by one sample. Which should be acceptable for
449 # this specific use case (can put the sample point of a bit time
450 # out of the center by some 4% under worst case conditions).
451
452 want = int(want)
453 while True:
454 diff = max(want - self.samplenum, 1)
455 pins = self.wait([{PIN_DATA: 'e'}, {'skip': diff}])
456 self.carrier_check(pins[PIN_DATA], self.samplenum)
457 if self.samplenum >= want:
458 return pins
459 # UNREACH
460
461 def decode(self):
462 if not self.samplerate or self.samplerate < 1e6:
463 raise SamplerateError('Need a samplerate of at least 1MSa/s')
464
465 # As a special case the first low period in the input capture is
466 # saught regardless of whether we can see its falling edge. This
467 # approach is also used to recover after synchronization was lost.
468 #
469 # The important condition here in the main loop is: Get the next
470 # edge's position, but time out after a maximum period of four
471 # data bits. This allows for the detection of SYNC pulses, also
472 # responds "soon enough" to DATA bits where edges can be few
473 # within a data byte. Also avoids excessive waits for unexpected
474 # communication errors.
475 #
476 # DATA bits within a byte are taken at fixed intervals relative
477 # to the SYNC-PAD's falling edge. It's essential to check the
478 # carrier at every edge, also during DATA bit sampling. Simple
479 # skips to the desired sample point could break that feature.
480 while True:
481
482 # Help kick-start the IDLE condition detection after
483 # decoder state reset.
484 if not self.edges:
485 curr_level, = self.wait({PIN_DATA: 'l'})
486 self.carrier_check(curr_level, self.samplenum)
487 self.edges = [self.samplenum]
488 continue
489
490 # Advance to the next edge, or over a medium span without an
491 # edge. Prepare to classify the distance to derive bit types
492 # from these details.
493 last_snum = self.samplenum
494 curr_level, = self.wait([{PIN_DATA: 'e'}, {'skip': self.lookahead_width}])
495 self.carrier_check(curr_level, self.samplenum)
496 bit_level = curr_level
497 edge_seen = self.matched[0]
498 if edge_seen:
499 bit_level = 1 - bit_level
500 if not self.edges:
501 self.edges = [self.samplenum]
502 continue
503 self.edges.append(self.samplenum)
504 curr_snum = self.samplenum
505
506 # Check bit width (can also be multiple data bits).
507 span = self.edges[-1] - self.edges[-2]
508 is_pad = bit_level and self.span_is_pad(span)
509 is_data = self.span_is_data(span)
510 is_short = bit_level and self.span_is_short(span)
511
512 if is_pad:
513 ss, es = last_snum, curr_snum
514 texts = ['PAD', '{:d}'.format(bit_level)]
515 self.putg(ss, es, [ANN_PAD_BIT, texts])
516 self.symbols_append(ss, es, 'PAD_BIT', bit_level)
517 ss, es = self.symbols_get_last()[:2]
518 self.putpy(ss, es, 'PAD_BIT', bit_level)
519 continue
520
521 if is_short:
522 ss, es = last_snum, curr_snum
523 texts = ['SHORT', '{:d}'.format(bit_level)]
524 self.putg(ss, es, [ANN_SHORT_DATA, texts])
525 self.symbols_append(ss, es, 'SHORT_BIT', bit_level)
526 ss, es = self.symbols_get_last()[:2]
527 self.putpy(ss, es, 'SHORT_BIT', bit_level)
528 continue
529
530 # Force IDLE period check when the decoder seeks to sync
531 # to the input data stream.
532 if not bit_level and not self.symbols and self.carrier_want_idle:
533 continue
534
535 # Accept arbitrary length LOW phases after DATA bytes(!) or
536 # SHORT pulses, but not within a DATA byte or SYNC-PAD etc.
537 # This covers the late start of the next SYNC-PAD (byte of
538 # a frame, or ACK byte after a frame, or the start of the
539 # next frame).
540 if not bit_level:
541 if self.symbols_has_prev('DATA_BYTE'):
542 continue
543 if self.symbols_has_prev('SHORT_BIT'):
544 continue
545 if self.symbols_has_prev('WAIT_ACK'):
546 continue
547
548 # Get (consume!) the LOW DATA bit after a PAD.
549 took_low = False
550 if is_data and not bit_level and self.symbols_has_prev('PAD_BIT'):
551 took_low = True
552 is_data -= 1
553 next_snum = int(last_snum + self.data_width)
554 ss, es = last_snum, next_snum
555 texts = ['ZERO', '{:d}'.format(bit_level)]
556 self.putg(ss, es, [ANN_LOW_BIT, texts])
557 self.symbols_append(ss, es, 'ZERO_BIT', bit_level)
558 ss, es = self.symbols_get_last()[:2]
559 self.putpy(ss, es, 'DATA_BIT', bit_level)
560 self.data_fall_time = last_snum
561 last_snum = next_snum
562 # Turn the combination of PAD and LOW DATA into SYNC-PAD.
563 # Start data bit accumulation after a SYNC-PAD was seen.
564 sync_pad_seq = ['PAD_BIT', 'ZERO_BIT']
565 if self.symbols_has_prev(sync_pad_seq):
566 self.symbols_collapse(len(sync_pad_seq), 'SYNC_PAD')
567 ss, es = self.symbols_get_last()[:2]
568 self.putpy(ss, es, 'SYNC_PAD', True)
569 self.data_bits = []
570 # Turn three subsequent SYNC-PAD into FRAME-INIT. Start the
571 # accumulation of frame bytes when FRAME-INIT was seen.
572 frame_init_seq = 3 * ['SYNC_PAD']
573 if self.symbols_has_prev(frame_init_seq):
574 self.symbols_collapse(len(frame_init_seq), 'FRAME_INIT')
575 # Force a flush of the previous frame after we have
576 # reliably detected the start of another one. This is a
577 # workaround for this decoder's inability to detect the
578 # end of a frame after an ACK was seen or byte counts
579 # have been reached. We cannot assume perfect input,
580 # thus we leave all interpretation of frame content to
581 # upper layers. Do keep the recently queued FRAME_INIT
582 # symbol across the flush operation.
583 if len(self.symbols) > 1:
584 keep = self.symbols.pop(-1)
585 self.frame_flush()
586 self.symbols.clear()
587 self.symbols.append(keep)
588 ss, es = self.symbols_get_last()[:2]
589 texts = ['FRAME INIT', 'INIT', 'I']
590 self.putg(ss, es, [ANN_FRAME_INIT, texts])
591 self.putpy(ss, es, 'FRAME_INIT', True)
592 self.frame_bytes = []
593 # Collapse SYNC-PAD after SHORT+ into a WAIT-ACK. Include
594 # all leading SHORT bits in the WAIT as well.
595 wait_ack_seq = ['SHORT_BIT', 'SYNC_PAD']
596 if self.symbols_has_prev(wait_ack_seq):
597 self.symbols_collapse(len(wait_ack_seq), 'WAIT_ACK',
598 squeeze = 'SHORT_BIT')
599 ss, es = self.symbols_get_last()[:2]
600 texts = ['WAIT for sync response', 'WAIT response', 'WAIT', 'W']
601 self.putg(ss, es, [ANN_FRAME_WAIT, texts])
602 self.putpy(ss, es, 'SYNC_RESP_WAIT', True)
603 if took_low and not is_data:
604 # Start at the very next edge if we just consumed a LOW
605 # after a PAD bit, and the DATA bit count is exhausted.
606 # This improves robustness, deals with inaccurate edge
607 # positions. (Motivated by real world captures, the spec
608 # would not discuss bit time tolerances.)
609 continue
610
611 # When we get here, the only remaining (the only supported)
612 # activity is the collection of a data byte's DATA bits.
613 # These are not taken by the main loop's "edge search, with
614 # a timeout" approach, which is "too tolerant". Instead all
615 # DATA bits get sampled at a fixed interval and relative to
616 # the SYNC-PAD's falling edge. We expect to have seen the
617 # data byte' SYNC-PAD before. If we haven't, the decoder is
618 # not yet synchronized to the input data.
619 if not is_data:
620 fast_cont = edge_seen and curr_level
621 ss, es = last_snum, curr_snum
622 texts = ['failed pulse length check', 'pulse length', 'length']
623 self.putg(ss, es, [ANN_SYNC_LOSS, texts])
624 self.frame_flush()
625 self.carrier_flush()
626 self.reset_state()
627 if fast_cont:
628 self.edges = [self.samplenum]
629 continue
630 if not self.symbols_has_prev('SYNC_PAD'):
631 # Fast reponse to the specific combination of: no-sync,
632 # edge seen, and current high level. In this case we
633 # can reset internal state, but also can continue the
634 # interpretation right after the most recently seen
635 # rising edge, which could start the next PAD time.
636 # Otherwise continue slow interpretation after reset.
637 fast_cont = edge_seen and curr_level
638 self.frame_flush()
639 self.carrier_flush()
640 self.reset_state()
641 if fast_cont:
642 self.edges = [self.samplenum]
643 continue
644
645 # The main loop's "edge search with period timeout" approach
646 # can have provided up to three more DATA bits after the LOW
647 # bit of the SYNC-PAD. Consume them immediately in that case,
648 # otherwise .wait() for their sample point. Stick with float
649 # values for bit sample points and bit time boundaries for
650 # improved accuracy, only round late to integers when needed.
651 bit_field = []
652 bit_ss = self.data_fall_time + self.data_width
653 for bit_idx in range(8):
654 bit_es = bit_ss + self.data_width
655 bit_snum = (bit_es + bit_ss) / 2
656 if bit_snum > self.samplenum:
657 bit_level, = self.wait_until(bit_snum)
658 ss, es = ceil(bit_ss), floor(bit_es)
659 texts = ['{:d}'.format(bit_level)]
660 self.putg(ss, es, [ANN_DATA_BIT, texts])
661 self.symbols_append(ss, es, 'DATA_BIT', bit_level)
662 ss, es = self.symbols_get_last()[:2]
663 self.putpy(ss, es, 'DATA_BIT', bit_level)
664 bit_field.append(bit_level)
665 if self.data_bits is not None:
666 self.data_bits.append(bit_level)
667 bit_ss = bit_es
668 end_snum = bit_es
669 curr_level, = self.wait_until(end_snum)
670 curr_snum = self.samplenum
671
672 # We are at the exact _calculated_ boundary of the last DATA
673 # bit time. Improve robustness for those situations where
674 # the transmitter's and the sender's timings differ within a
675 # margin, and the transmitter may hold the last DATA bit's
676 # HIGH level for a little longer.
677 if curr_level:
678 hold = self.hold_high_width
679 curr_level, = self.wait([{PIN_DATA: 'l'}, {'skip': int(hold)}])
680 self.carrier_check(curr_level, self.samplenum)
681 curr_snum = self.samplenum
682
683 # Get the byte value from the bits (when available).
684 # TODO Has the local 'bit_field' become obsolete, or should
685 # self.data_bits go away?
686 data_byte = bitpack(bit_field)
687 if self.data_bits is not None:
688 data_byte = bitpack(self.data_bits)
689 self.data_bits.clear()
690 if self.frame_bytes is not None:
691 self.frame_bytes.append(data_byte)
692
693 # Turn a sequence of a SYNC-PAD and eight DATA bits into a
694 # DATA-BYTE symbol.
695 byte_seq = ['SYNC_PAD'] + 8 * ['DATA_BIT']
696 if self.symbols_has_prev(byte_seq):
697 self.symbols_collapse(len(byte_seq), 'DATA_BYTE')
698 ss, es = self.symbols_get_last()[:2]
699 texts = ['{:02x}'.format(data_byte)]
700 self.putg(ss, es, [ANN_DATA_BYTE, texts])
701 self.putpy(ss, es, 'DATA_BYTE', data_byte)
702
703 # Optionally terminate the accumulation of a frame when a
704 # WAIT-ACK period was followed by a DATA-BYTE? This could
705 # flush the current packet before the next FRAME-INIT or
706 # IDLE are seen, and increases usability for short input
707 # data (aggressive trimming). It won't help when WAIT is
708 # not seen, though.
709 sync_resp_seq = ['WAIT_ACK'] + ['DATA_BYTE']
710 if self.symbols_has_prev(sync_resp_seq):
711 self.frame_flush()