]>
Commit | Line | Data |
---|---|---|
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 | ||
56 | import sigrokdecode as srd | |
57 | from common.srdhelper import bitpack | |
58 | from math import ceil, floor | |
59 | ||
60 | ''' | |
61 | OUTPUT_PYTHON format for stacked decoders: | |
62 | ||
63 | General packet format: | |
64 | [<ptype>, <pdata>] | |
65 | ||
66 | This is the list of <ptype>s and their respective <pdata> values: | |
67 | ||
68 | Carrier sense: | |
69 | - 'IDLE': <pdata> is the pin level (always 0). | |
70 | - 'BUSY': <pdata> is always True. | |
71 | ||
72 | Raw 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 | ||
78 | Date 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 | ||
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. | |
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 | ||
104 | PIN_DATA, = range(1) | |
105 | ANN_CARRIER_BUSY, ANN_CARRIER_IDLE, \ | |
106 | ANN_PAD_BIT, ANN_LOW_BIT, ANN_DATA_BIT, ANN_SHORT_DATA, ANN_SYNC_LOSS, \ | |
107 | ANN_DATA_BYTE, \ | |
108 | ANN_FRAME_INIT, ANN_FRAME_BYTES, ANN_FRAME_WAIT, \ | |
109 | = range(11) | |
110 | ||
111 | class SamplerateError(Exception): | |
112 | pass | |
113 | ||
114 | class 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'] | |
122 | outputs = ['pjon_link'] | |
123 | tags = ['Embedded/industrial'] | |
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 | 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 | |
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 | # BEWARE! Use ss value of last edge (genuinely seen, or | |
514 | # inserted after a DATA byte) for PAD bit annotations. | |
515 | ss, es = self.edges[-2], curr_snum | |
516 | texts = ['PAD', '{:d}'.format(bit_level)] | |
517 | self.putg(ss, es, [ANN_PAD_BIT, texts]) | |
518 | self.symbols_append(ss, es, 'PAD_BIT', bit_level) | |
519 | ss, es = self.symbols_get_last()[:2] | |
520 | self.putpy(ss, es, 'PAD_BIT', bit_level) | |
521 | continue | |
522 | ||
523 | if is_short: | |
524 | ss, es = last_snum, curr_snum | |
525 | texts = ['SHORT', '{:d}'.format(bit_level)] | |
526 | self.putg(ss, es, [ANN_SHORT_DATA, texts]) | |
527 | self.symbols_append(ss, es, 'SHORT_BIT', bit_level) | |
528 | ss, es = self.symbols_get_last()[:2] | |
529 | self.putpy(ss, es, 'SHORT_BIT', bit_level) | |
530 | continue | |
531 | ||
532 | # Force IDLE period check when the decoder seeks to sync | |
533 | # to the input data stream. | |
534 | if not bit_level and not self.symbols and self.carrier_want_idle: | |
535 | continue | |
536 | ||
537 | # Accept arbitrary length LOW phases after DATA bytes(!) or | |
538 | # SHORT pulses, but not within a DATA byte or SYNC-PAD etc. | |
539 | # This covers the late start of the next SYNC-PAD (byte of | |
540 | # a frame, or ACK byte after a frame, or the start of the | |
541 | # next frame). | |
542 | if not bit_level: | |
543 | if self.symbols_has_prev('DATA_BYTE'): | |
544 | continue | |
545 | if self.symbols_has_prev('SHORT_BIT'): | |
546 | continue | |
547 | if self.symbols_has_prev('WAIT_ACK'): | |
548 | continue | |
549 | ||
550 | # Get (consume!) the LOW DATA bit after a PAD. | |
551 | took_low = False | |
552 | if is_data and not bit_level and self.symbols_has_prev('PAD_BIT'): | |
553 | took_low = True | |
554 | is_data -= 1 | |
555 | next_snum = int(last_snum + self.data_width) | |
556 | ss, es = last_snum, next_snum | |
557 | texts = ['ZERO', '{:d}'.format(bit_level)] | |
558 | self.putg(ss, es, [ANN_LOW_BIT, texts]) | |
559 | self.symbols_append(ss, es, 'ZERO_BIT', bit_level) | |
560 | ss, es = self.symbols_get_last()[:2] | |
561 | self.putpy(ss, es, 'DATA_BIT', bit_level) | |
562 | self.data_fall_time = last_snum | |
563 | last_snum = next_snum | |
564 | # Turn the combination of PAD and LOW DATA into SYNC-PAD. | |
565 | # Start data bit accumulation after a SYNC-PAD was seen. | |
566 | sync_pad_seq = ['PAD_BIT', 'ZERO_BIT'] | |
567 | if self.symbols_has_prev(sync_pad_seq): | |
568 | self.symbols_collapse(len(sync_pad_seq), 'SYNC_PAD') | |
569 | ss, es = self.symbols_get_last()[:2] | |
570 | self.putpy(ss, es, 'SYNC_PAD', True) | |
571 | self.data_bits = [] | |
572 | # Turn three subsequent SYNC-PAD into FRAME-INIT. Start the | |
573 | # accumulation of frame bytes when FRAME-INIT was seen. | |
574 | frame_init_seq = 3 * ['SYNC_PAD'] | |
575 | if self.symbols_has_prev(frame_init_seq): | |
576 | self.symbols_collapse(len(frame_init_seq), 'FRAME_INIT') | |
577 | # Force a flush of the previous frame after we have | |
578 | # reliably detected the start of another one. This is a | |
579 | # workaround for this decoder's inability to detect the | |
580 | # end of a frame after an ACK was seen or byte counts | |
581 | # have been reached. We cannot assume perfect input, | |
582 | # thus we leave all interpretation of frame content to | |
583 | # upper layers. Do keep the recently queued FRAME_INIT | |
584 | # symbol across the flush operation. | |
585 | if len(self.symbols) > 1: | |
586 | keep = self.symbols.pop(-1) | |
587 | self.frame_flush() | |
588 | self.symbols.clear() | |
589 | self.symbols.append(keep) | |
590 | ss, es = self.symbols_get_last()[:2] | |
591 | texts = ['FRAME INIT', 'INIT', 'I'] | |
592 | self.putg(ss, es, [ANN_FRAME_INIT, texts]) | |
593 | self.putpy(ss, es, 'FRAME_INIT', True) | |
594 | self.frame_bytes = [] | |
595 | # Collapse SYNC-PAD after SHORT+ into a WAIT-ACK. Include | |
596 | # all leading SHORT bits in the WAIT as well. | |
597 | wait_ack_seq = ['SHORT_BIT', 'SYNC_PAD'] | |
598 | if self.symbols_has_prev(wait_ack_seq): | |
599 | self.symbols_collapse(len(wait_ack_seq), 'WAIT_ACK', | |
600 | squeeze = 'SHORT_BIT') | |
601 | ss, es = self.symbols_get_last()[:2] | |
602 | texts = ['WAIT for sync response', 'WAIT response', 'WAIT', 'W'] | |
603 | self.putg(ss, es, [ANN_FRAME_WAIT, texts]) | |
604 | self.putpy(ss, es, 'SYNC_RESP_WAIT', True) | |
605 | if took_low and not is_data: | |
606 | # Start at the very next edge if we just consumed a LOW | |
607 | # after a PAD bit, and the DATA bit count is exhausted. | |
608 | # This improves robustness, deals with inaccurate edge | |
609 | # positions. (Motivated by real world captures, the spec | |
610 | # would not discuss bit time tolerances.) | |
611 | continue | |
612 | ||
613 | # When we get here, the only remaining (the only supported) | |
614 | # activity is the collection of a data byte's DATA bits. | |
615 | # These are not taken by the main loop's "edge search, with | |
616 | # a timeout" approach, which is "too tolerant". Instead all | |
617 | # DATA bits get sampled at a fixed interval and relative to | |
618 | # the SYNC-PAD's falling edge. We expect to have seen the | |
619 | # data byte' SYNC-PAD before. If we haven't, the decoder is | |
620 | # not yet synchronized to the input data. | |
621 | if not is_data: | |
622 | fast_cont = edge_seen and curr_level | |
623 | ss, es = last_snum, curr_snum | |
624 | texts = ['failed pulse length check', 'pulse length', 'length'] | |
625 | self.putg(ss, es, [ANN_SYNC_LOSS, texts]) | |
626 | self.frame_flush() | |
627 | self.carrier_flush() | |
628 | self.reset_state() | |
629 | if fast_cont: | |
630 | self.edges = [self.samplenum] | |
631 | continue | |
632 | if not self.symbols_has_prev('SYNC_PAD'): | |
633 | # Fast reponse to the specific combination of: no-sync, | |
634 | # edge seen, and current high level. In this case we | |
635 | # can reset internal state, but also can continue the | |
636 | # interpretation right after the most recently seen | |
637 | # rising edge, which could start the next PAD time. | |
638 | # Otherwise continue slow interpretation after reset. | |
639 | fast_cont = edge_seen and curr_level | |
640 | self.frame_flush() | |
641 | self.carrier_flush() | |
642 | self.reset_state() | |
643 | if fast_cont: | |
644 | self.edges = [self.samplenum] | |
645 | continue | |
646 | ||
647 | # The main loop's "edge search with period timeout" approach | |
648 | # can have provided up to three more DATA bits after the LOW | |
649 | # bit of the SYNC-PAD. Consume them immediately in that case, | |
650 | # otherwise .wait() for their sample point. Stick with float | |
651 | # values for bit sample points and bit time boundaries for | |
652 | # improved accuracy, only round late to integers when needed. | |
653 | bit_field = [] | |
654 | bit_ss = self.data_fall_time + self.data_width | |
655 | for bit_idx in range(8): | |
656 | bit_es = bit_ss + self.data_width | |
657 | bit_snum = (bit_es + bit_ss) / 2 | |
658 | if bit_snum > self.samplenum: | |
659 | bit_level, = self.wait_until(bit_snum) | |
660 | ss, es = ceil(bit_ss), floor(bit_es) | |
661 | texts = ['{:d}'.format(bit_level)] | |
662 | self.putg(ss, es, [ANN_DATA_BIT, texts]) | |
663 | self.symbols_append(ss, es, 'DATA_BIT', bit_level) | |
664 | ss, es = self.symbols_get_last()[:2] | |
665 | self.putpy(ss, es, 'DATA_BIT', bit_level) | |
666 | bit_field.append(bit_level) | |
667 | if self.data_bits is not None: | |
668 | self.data_bits.append(bit_level) | |
669 | bit_ss = bit_es | |
670 | end_snum = bit_es | |
671 | curr_level, = self.wait_until(end_snum) | |
672 | curr_snum = self.samplenum | |
673 | ||
674 | # We are at the exact _calculated_ boundary of the last DATA | |
675 | # bit time. Improve robustness for those situations where | |
676 | # the transmitter's and the sender's timings differ within a | |
677 | # margin, and the transmitter may hold the last DATA bit's | |
678 | # HIGH level for a little longer. | |
679 | # | |
680 | # When no falling edge is seen within the maximum tolerance | |
681 | # for the last DATA bit, then this could be the combination | |
682 | # of a HIGH DATA bit and a PAD bit without a LOW in between. | |
683 | # Fake an edge in that case, to re-use existing code paths. | |
684 | # Make sure to keep referencing times to the last SYNC pad's | |
685 | # falling edge. This is the last reliable condition we have. | |
686 | if curr_level: | |
687 | hold = self.hold_high_width | |
688 | curr_level, = self.wait([{PIN_DATA: 'l'}, {'skip': int(hold)}]) | |
689 | self.carrier_check(curr_level, self.samplenum) | |
690 | if self.matched[1]: | |
691 | self.edges.append(curr_snum) | |
692 | curr_level = 1 - curr_level | |
693 | curr_snum = self.samplenum | |
694 | ||
695 | # Get the byte value from the bits (when available). | |
696 | # TODO Has the local 'bit_field' become obsolete, or should | |
697 | # self.data_bits go away? | |
698 | data_byte = bitpack(bit_field) | |
699 | if self.data_bits is not None: | |
700 | data_byte = bitpack(self.data_bits) | |
701 | self.data_bits.clear() | |
702 | if self.frame_bytes is not None: | |
703 | self.frame_bytes.append(data_byte) | |
704 | ||
705 | # Turn a sequence of a SYNC-PAD and eight DATA bits into a | |
706 | # DATA-BYTE symbol. | |
707 | byte_seq = ['SYNC_PAD'] + 8 * ['DATA_BIT'] | |
708 | if self.symbols_has_prev(byte_seq): | |
709 | self.symbols_collapse(len(byte_seq), 'DATA_BYTE') | |
710 | ss, es = self.symbols_get_last()[:2] | |
711 | texts = ['{:02x}'.format(data_byte)] | |
712 | self.putg(ss, es, [ANN_DATA_BYTE, texts]) | |
713 | self.putpy(ss, es, 'DATA_BYTE', data_byte) | |
714 | ||
715 | # Optionally terminate the accumulation of a frame when a | |
716 | # WAIT-ACK period was followed by a DATA-BYTE? This could | |
717 | # flush the current packet before the next FRAME-INIT or | |
718 | # IDLE are seen, and increases usability for short input | |
719 | # data (aggressive trimming). It won't help when WAIT is | |
720 | # not seen, though. | |
721 | sync_resp_seq = ['WAIT_ACK'] + ['DATA_BYTE'] | |
722 | if self.symbols_has_prev(sync_resp_seq): | |
723 | self.frame_flush() |