]> sigrok.org Git - libsigrokdecode.git/blob - decoders/usb_power_delivery/pd.py
42425acd9955db8ea78f022e400108d959176bfd
[libsigrokdecode.git] / decoders / usb_power_delivery / pd.py
1 ##
2 ## This file is part of the libsigrokdecode project.
3 ##
4 ## Copyright (C) 2015 Google, Inc
5 ## Copyright (C) 2018 davidanger <davidanger@163.com>
6 ## Copyright (C) 2018 Peter Hazenberg <sigrok@haas-en-berg.nl>
7 ##
8 ## This program is free software; you can redistribute it and/or modify
9 ## it under the terms of the GNU General Public License as published by
10 ## the Free Software Foundation; either version 2 of the License, or
11 ## (at your option) any later version.
12 ##
13 ## This program is distributed in the hope that it will be useful,
14 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 ## GNU General Public License for more details.
17 ##
18 ## You should have received a copy of the GNU General Public License
19 ## along with this program; if not, see <http://www.gnu.org/licenses/>.
20 ##
21
22 import sigrokdecode as srd
23 import struct
24 import zlib   # for crc32
25
26 # BMC encoding with a 600kHz datarate
27 UI_US = 1000000/600000.0
28
29 # Threshold to discriminate half-1 from 0 in Binary Mark Conding
30 THRESHOLD_US = (UI_US + 2 * UI_US) / 2
31
32 # Control Message type
33 CTRL_TYPES = {
34     0: 'reserved',
35     1: 'GOOD CRC',
36     2: 'GOTO MIN',
37     3: 'ACCEPT',
38     4: 'REJECT',
39     5: 'PING',
40     6: 'PS RDY',
41     7: 'GET SOURCE CAP',
42     8: 'GET SINK CAP',
43     9: 'DR SWAP',
44     10: 'PR SWAP',
45     11: 'VCONN SWAP',
46     12: 'WAIT',
47     13: 'SOFT RESET',
48     14: 'reserved',
49     15: 'reserved',
50     16: 'Not Supported',
51     17: 'Get_Source_Cap_Extended',
52     18: 'Get_Status',
53     19: 'FR_Swap',
54     20: 'Get_PPS_Status',
55     21: 'Get_Country_Codes',
56 }
57
58 # Data message type
59 DATA_TYPES = {
60     1: 'SOURCE CAP',
61     2: 'REQUEST',
62     3: 'BIST',
63     4: 'SINK CAP',
64     5: 'Battery_Status',
65     6: 'Alert',
66     7: 'Get_Country_Info',
67     15: 'VDM'
68 }
69
70 # 4b5b encoding of the symbols
71 DEC4B5B = [
72     0x10,   # Error      00000
73     0x10,   # Error      00001
74     0x10,   # Error      00010
75     0x10,   # Error      00011
76     0x10,   # Error      00100
77     0x10,   # Error      00101
78     0x13,   # Sync-3     00110
79     0x14,   # RST-1      00111
80     0x10,   # Error      01000
81     0x01,   # 1 = 0001   01001
82     0x04,   # 4 = 0100   01010
83     0x05,   # 5 = 0101   01011
84     0x10,   # Error      01100
85     0x16,   # EOP        01101
86     0x06,   # 6 = 0110   01110
87     0x07,   # 7 = 0111   01111
88     0x10,   # Error      10000
89     0x12,   # Sync-2     10001
90     0x08,   # 8 = 1000   10010
91     0x09,   # 9 = 1001   10011
92     0x02,   # 2 = 0010   10100
93     0x03,   # 3 = 0011   10101
94     0x0A,   # A = 1010   10110
95     0x0B,   # B = 1011   10111
96     0x11,   # Sync-1     11000
97     0x15,   # RST-2      11001
98     0x0C,   # C = 1100   11010
99     0x0D,   # D = 1101   11011
100     0x0E,   # E = 1110   11100
101     0x0F,   # F = 1111   11101
102     0x00,   # 0 = 0000   11110
103     0x10,   # Error      11111
104 ]
105 SYM_ERR = 0x10
106 SYNC1 = 0x11
107 SYNC2 = 0x12
108 SYNC3 = 0x13
109 RST1 = 0x14
110 RST2 = 0x15
111 EOP = 0x16
112 SYNC_CODES = [SYNC1, SYNC2, SYNC3]
113 HRST_CODES = [RST1, RST1, RST1, RST2]
114
115 SOP_SEQUENCES = [
116     (SYNC1, SYNC1, SYNC1, SYNC2),
117     (SYNC1, SYNC1, SYNC3, SYNC3),
118     (SYNC1, SYNC3, SYNC1, SYNC3),
119     (SYNC1, RST2,  RST2,  SYNC3),
120     (SYNC1, RST2,  SYNC3, SYNC2),
121     (RST1,  SYNC1, RST1,  SYNC3),
122     (RST1,  RST1,  RST1,   RST2),
123 ]
124 START_OF_PACKETS = {
125     SOP_SEQUENCES[0]: 'SOP',
126     SOP_SEQUENCES[1]: "SOP'",
127     SOP_SEQUENCES[2]: 'SOP"',
128     SOP_SEQUENCES[3]: "SOP' Debug",
129     SOP_SEQUENCES[4]: 'SOP" Debug',
130     SOP_SEQUENCES[5]: 'Cable Reset',
131     SOP_SEQUENCES[6]: 'Hard Reset',
132 }
133
134 SYM_NAME = [
135     ['0x0', '0'],
136     ['0x1', '1'],
137     ['0x2', '2'],
138     ['0x3', '3'],
139     ['0x4', '4'],
140     ['0x5', '5'],
141     ['0x6', '6'],
142     ['0x7', '7'],
143     ['0x8', '8'],
144     ['0x9', '9'],
145     ['0xA', 'A'],
146     ['0xB', 'B'],
147     ['0xC', 'C'],
148     ['0xD', 'D'],
149     ['0xE', 'E'],
150     ['0xF', 'F'],
151     ['ERROR', 'X'],
152     ['SYNC-1', 'S1'],
153     ['SYNC-2', 'S2'],
154     ['SYNC-3', 'S3'],
155     ['RST-1', 'R1'],
156     ['RST-2', 'R2'],
157     ['EOP', '#'],
158 ]
159
160 RDO_FLAGS = {
161     (1 << 23): 'unchunked',
162     (1 << 24): 'no_suspend',
163     (1 << 25): 'comm_cap',
164     (1 << 26): 'cap_mismatch',
165     (1 << 27): 'give_back'
166 }
167
168 BIST_MODES = {
169         0: 'Receiver',
170         1: 'Transmit',
171         2: 'Counters',
172         3: 'Carrier 0',
173         4: 'Carrier 1',
174         5: 'Carrier 2',
175         6: 'Carrier 3',
176         7: 'Eye',
177 }
178
179 VDM_CMDS = {
180         1: 'Disc Ident',
181         2: 'Disc SVID',
182         3: 'Disc Mode',
183         4: 'Enter Mode',
184         5: 'Exit Mode',
185         6: 'Attention',
186         # 16..31: SVID Specific Commands
187         # DisplayPort Commands
188         16: 'DP Status',
189         17: 'DP Configure',
190 }
191 VDM_ACK = ['REQ', 'ACK', 'NAK', 'BSY']
192
193
194 class SamplerateError(Exception):
195     pass
196
197 class Decoder(srd.Decoder):
198     api_version = 3
199     id = 'usb_power_delivery'
200     name = 'USB PD'
201     longname = 'USB Power Delivery'
202     desc = 'USB Power Delivery protocol.'
203     license = 'gplv2+'
204     inputs = ['logic']
205     outputs = ['usb_pd']
206     channels = (
207         {'id': 'cc1', 'name': 'CC1', 'desc': 'Configuration Channel 1'},
208     )
209     optional_channels = (
210         {'id': 'cc2', 'name': 'CC2', 'desc': 'Configuration Channel 2'},
211     )
212     options = (
213         {'id': 'fulltext', 'desc': 'full text decoding of the packet',
214          'default': 'no', 'values': ('yes', 'no')},
215     )
216     annotations = (
217         ('type', 'Packet Type'),
218         ('Preamble', 'Preamble'),
219         ('SOP', 'Start of Packet'),
220         ('Head', 'Header'),
221         ('Data', 'Data'),
222         ('CRC', 'Checksum'),
223         ('EOP', 'End Of Packet'),
224         ('Sym', '4b5b symbols'),
225         ('warnings', 'Warnings'),
226         ('src', 'Source Message'),
227         ('snk', 'Sink Message'),
228         ('payload', 'Payload'),
229         ('text', 'Plain text'),
230     )
231     annotation_rows = (
232        ('4B5B', 'symbols', (7, )),
233        ('Phase', 'parts', (1, 2, 3, 4, 5, 6, )),
234        ('payload', 'Payload', (11, )),
235        ('type', 'Type', (0, 9, 10, )),
236        ('warnings', 'Warnings', (8, )),
237        ('text', 'Full text', (12, )),
238     )
239     binary = (
240         ('raw-data', 'RAW binary data'),
241     )
242
243     stored_pdos = {}
244
245
246     def get_request(self, rdo):
247         pos = (rdo >> 28) & 7
248
249         op_ma = ((rdo >> 10) & 0x3ff) * 0.01
250         max_ma = (rdo & 0x3ff) * 0.01
251
252         mark = self.cap_mark[pos]
253         if mark == 3:
254             op_v = ((rdo >> 9) & 0x7ff) * 0.02
255             op_a = (rdo & 0x3f) * 0.05
256             t_settings = '%gV %gA' % (op_v, op_a)
257         elif mark == 2:
258             op_w = ((rdo >> 10) & 0x3ff) * 0.25
259             mp_w = (rdo & 0x3ff) * 0.25
260             t_settings = '%gW (operating) / %gW (max)' % (op_w, max_w)
261         else:
262             op_a = ((rdo >> 10) & 0x3ff) * 0.01
263             max_a = (rdo & 0x3ff) * 0.01
264             t_settings = '%gA (operating) / %gA (max)' % (op_a, max_a)
265
266         t_flags = ''
267         for f in sorted(RDO_FLAGS.keys(), reverse = True):
268             if rdo & f:
269                 t_flags += ' [' + RDO_FLAGS[f] + ']'
270
271         if pos in self.stored_pdos.keys():
272             t_pdo = '#%d: %s' % (pos, self.stored_pdos[pos])
273         else:
274             t_pdo = '#d' % (pos)
275
276         return '(PDO %s) %s%s' % (t_pdo, t_settings, t_flags)
277
278     def get_source_sink_cap(self, pdo, idx, source):
279         t1 = (pdo >> 30) & 3
280         self.cap_mark[idx] = t1
281
282         flags = {}
283         if t1 == 0:
284             t_name = 'Fixed'
285             if source:
286                 flags = {
287                     (1 << 29): 'dual_role_power',
288                     (1 << 28): 'suspend',
289                     (1 << 27): 'unconstrained',
290                     (1 << 26): 'comm_cap',
291                     (1 << 25): 'dual_role_data',
292                     (1 << 24): 'unchunked',
293                 }
294             else: #sink
295                 flags = {
296                     (1 << 29): 'dual_role_power',
297                     (1 << 28): 'high_capability',
298                     (1 << 27): 'unconstrained',
299                     (1 << 26): 'comm_cap',
300                     (1 << 25): 'dual_role_data',
301                     (0b01 << 23): 'fr_swap default power',
302                     (0b10 << 23): 'fr_swap 1.5 A',
303                     (0b11 << 23): 'fr_swap 3.0 A',
304                 }
305             mv = ((pdo >> 10) & 0x3ff) * 0.05
306             ma = ((pdo >>  0) & 0x3ff) * 0.01
307             p = '%gV %gA (%gW)' % (mv, ma, mv*ma)
308             self.stored_pdos[idx] = '%s %gV' % (t_name, mv)
309         elif t1 == 1:
310             t_name = 'Battery'
311             flags = {} # No flags defined for Battery PDO in PD 3.0 spec
312             minv = ((pdo >> 10) & 0x3ff) * 0.05
313             maxv = ((pdo >> 20) & 0x3ff) * 0.05
314             mw   = ((pdo >>  0) & 0x3ff) * 0.25
315             p = '%g/%gV %gW' % (minv, maxv, mw)
316             self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv)
317         elif t1 == 2:
318             t_name = 'Variable'
319             flags = {} # No flags defined for Variable PDO in PD 3.0 spec
320             minv = ((pdo >> 10) & 0x3ff) * 0.05
321             maxv = ((pdo >> 20) & 0x3ff) * 0.05
322             ma   = ((pdo >>  0) & 0x3ff) * 0.01
323             p = '%g/%gV %gA' % (minv, maxv, ma)
324             self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv)
325         elif t1 == 3:
326             t2 = (pdo >> 28) & 3
327             if t2 == 0:
328                 t_name = 'Programmable|PPS'
329                 flags = {
330                     (1 << 29): 'power_limited',
331                 }
332                 minv = ((pdo >> 8) & 0xff) * 0.1
333                 maxv = ((pdo >> 17) & 0xff) * 0.1
334                 ma = ((pdo >> 0) & 0xff) * 0.05
335                 p = '%g/%gV %gA' % (minv, maxv, ma)
336                 if (pdo >> 27) & 0x1:
337                     p += ' [limited]'
338                 self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv)
339             else:
340                 t_name = 'Reserved APDO: '+bin(t2)
341                 p = '[raw: %s]' % (bin(pdo))
342                 self.stored_pdos[idx] = '%s %s' % (t_name, p)
343         t_flags = ''
344         for f in sorted(flags.keys(), reverse = True):
345             if pdo & f:
346                 t_flags += ' [' + flags[f] + ']'
347         return '[%s] %s%s' % (t_name, p, t_flags)
348
349     def get_vdm(self, idx, data):
350         if idx == 0:    # VDM header
351             vid = data >> 16
352             struct = data & (1 << 15)
353             txt = 'VDM'
354             if struct:  # Structured VDM
355                 cmd = data & 0x1f
356                 src = data & (1 << 5)
357                 ack = (data >> 6) & 3
358                 pos = (data >> 8) & 7
359                 ver = (data >> 13) & 3
360                 txt = VDM_ACK[ack] + ' '
361                 txt += VDM_CMDS[cmd] if cmd in VDM_CMDS else 'cmd?'
362                 txt += ' pos %d' % (pos) if pos else ' '
363             else:   # Unstructured VDM
364                 txt = 'unstruct [%04x]' % (data & 0x7fff)
365             txt += ' SVID:%04x' % (vid)
366         else:   # VDM payload
367             txt = 'VDO:%08x' % (data)
368         return txt
369
370     def get_bist(self, idx, data):
371         mode = data >> 28
372         counter = data & 0xffff
373         mode_name = BIST_MODES[mode] if mode in BIST_MODES else 'INVALID'
374         if mode == 2:
375             mode_name = 'Counter[= %d]' % (counter)
376         # TODO check all 0 bits are 0 / emit warnings
377         return 'mode %s' % (mode_name) if idx == 0 else 'invalid BRO'
378
379     def putpayload(self, s0, s1, idx):
380         t = self.head_type()
381         txt = '['+str(idx+1)+'] '
382         if t == 2:
383             txt += self.get_request(self.data[idx])
384         elif t == 1 or t == 4:
385             txt += self.get_source_sink_cap(self.data[idx], idx+1, t==1)
386         elif t == 15:
387             txt += self.get_vdm(idx, self.data[idx])
388         elif t == 3:
389             txt += self.get_bist(idx, self.data[idx])
390         self.putx(s0, s1, [11, [txt, txt]])
391         self.text += ' - ' + txt
392
393     def puthead(self):
394         ann_type = 9 if self.head_power_role() else 10
395         role = 'SRC' if self.head_power_role() else 'SNK'
396         if self.head_data_role() != self.head_power_role():
397             role += '/DFP' if self.head_data_role() else '/UFP'
398         t = self.head_type()
399         if self.head_count() == 0:
400             shortm = CTRL_TYPES[t]
401         else:
402             shortm = DATA_TYPES[t] if t in DATA_TYPES else 'DAT???'
403
404         longm = '(r{:d}) {:s}[{:d}]: {:s}'.format(self.head_rev(), role, self.head_id(), shortm)
405         self.putx(0, -1, [ann_type, [longm, shortm]])
406         self.text += longm
407
408     def head_id(self):
409         return (self.head >> 9) & 7
410
411     def head_power_role(self):
412         return (self.head >> 8) & 1
413
414     def head_data_role(self):
415         return (self.head >> 5) & 1
416
417     def head_rev(self):
418         return ((self.head >> 6) & 3) + 1
419
420     def head_type(self):
421         return self.head & 0xF
422
423     def head_count(self):
424         return (self.head >> 12) & 7
425
426     def putx(self, s0, s1, data):
427         self.put(self.edges[s0], self.edges[s1], self.out_ann, data)
428
429     def putwarn(self, longm, shortm):
430         self.putx(0, -1, [8, [longm, shortm]])
431
432     def compute_crc32(self):
433         bdata = struct.pack('<H'+'I'*len(self.data), self.head & 0xffff,
434                             *tuple([d & 0xffffffff for d in self.data]))
435         return zlib.crc32(bdata)
436
437     def rec_sym(self, i, sym):
438         self.putx(i, i+5, [7, SYM_NAME[sym]])
439
440     def get_sym(self, i, rec=True):
441         v = (self.bits[i] | (self.bits[i+1] << 1) | (self.bits[i+2] << 2) |
442              (self.bits[i+3] << 3) | (self.bits[i+4] << 4))
443         sym = DEC4B5B[v]
444         if rec:
445             self.rec_sym(i, sym)
446         return sym
447
448     def get_short(self):
449         i = self.idx
450         # Check it's not a truncated packet
451         if len(self.bits) - i <= 20:
452             self.putwarn('Truncated', '!')
453             return 0x0BAD
454         k = [self.get_sym(i), self.get_sym(i+5),
455              self.get_sym(i+10), self.get_sym(i+15)]
456         # TODO check bad symbols
457         val = k[0] | (k[1] << 4) | (k[2] << 8) | (k[3] << 12)
458         self.idx += 20
459         return val
460
461     def get_word(self):
462         lo = self.get_short()
463         hi = self.get_short()
464         return lo | (hi << 16)
465
466     def find_corrupted_sop(self, k):
467         # Start of packet are valid even if they have only 3 correct symbols
468         # out of 4.
469         for seq in SOP_SEQUENCES:
470             if [k[i] == seq[i] for i in range(len(k))].count(True) >= 3:
471                 return START_OF_PACKETS[seq]
472         return None
473
474     def scan_eop(self):
475         for i in range(len(self.bits) - 19):
476             k = (self.get_sym(i, rec=False), self.get_sym(i+5, rec=False),
477                  self.get_sym(i+10, rec=False), self.get_sym(i+15, rec=False))
478             sym = START_OF_PACKETS.get(k, None)
479             if not sym:
480                 sym = self.find_corrupted_sop(k)
481             # We have an interesting symbol sequence
482             if sym:
483                 # annotate the preamble
484                 self.putx(0, i, [1, ['Preamble', '...']])
485                 # annotate each symbol
486                 self.rec_sym(i, k[0])
487                 self.rec_sym(i+5, k[1])
488                 self.rec_sym(i+10, k[2])
489                 self.rec_sym(i+15, k[3])
490                 if sym == 'Hard Reset':
491                     self.text += 'HRST'
492                     return -1   # Hard reset
493                 elif sym == 'Cable Reset':
494                     self.text += 'CRST'
495                     return -1   # Cable reset
496                 else:
497                     self.putx(i, i+20, [2, [sym, 'S']])
498                 return i+20
499         self.putx(0, len(self.bits), [1, ['Junk???', 'XXX']])
500         self.text += 'Junk???'
501         self.putwarn('No start of packet found', 'XXX')
502         return -1   # No Start Of Packet
503
504     def __init__(self):
505         self.reset()
506
507     def reset(self):
508         self.samplerate = None
509         self.idx = 0
510         self.packet_seq = 0
511         self.previous = 0
512         self.startsample = None
513         self.bits = []
514         self.edges = []
515         self.bad = []
516         self.half_one = False
517         self.start_one = 0
518         self.stored_pdos = {}
519         self.cap_mark = [0, 0, 0, 0, 0, 0, 0, 0]
520
521     def metadata(self, key, value):
522         if key == srd.SRD_CONF_SAMPLERATE:
523             self.samplerate = value
524             # 0 is 2 UI, space larger than 1.5x 0 is definitely wrong
525             self.maxbit = self.us2samples(3 * UI_US)
526             # duration threshold between half 1 and 0
527             self.threshold = self.us2samples(THRESHOLD_US)
528
529     def start(self):
530         self.out_python = self.register(srd.OUTPUT_PYTHON)
531         self.out_ann = self.register(srd.OUTPUT_ANN)
532         self.out_binary = self.register(srd.OUTPUT_BINARY)
533         self.out_bitrate = self.register(
534             srd.OUTPUT_META,
535             meta=(int, 'Bitrate', 'Bitrate during the packet')
536         )
537
538     def us2samples(self, us):
539         return int(us * self.samplerate / 1000000)
540
541     def decode_packet(self):
542         self.data = []
543         self.idx = 0
544         self.text = ''
545
546         if len(self.edges) < 50:
547             return  # Not a real PD packet
548
549         self.packet_seq += 1
550         tstamp = float(self.startsample) / self.samplerate
551         self.text += '#%-4d (%8.6fms): ' % (self.packet_seq, tstamp*1000)
552
553         self.idx = self.scan_eop()
554         if self.idx < 0:
555             # Full text trace of the issue
556             self.putx(0, self.idx, [12, [self.text, '...']])
557             return  # No real packet: ABORT
558
559         # Packet header
560         self.head = self.get_short()
561         self.putx(self.idx-20, self.idx, [3, ['H:%04x' % (self.head), 'HD']])
562         self.puthead()
563
564         # Decode data payload
565         for i in range(self.head_count()):
566             self.data.append(self.get_word())
567             self.putx(self.idx-40, self.idx,
568                       [4, ['[%d]%08x' % (i, self.data[i]), 'D%d' % (i)]])
569             self.putpayload(self.idx-40, self.idx, i)
570
571         # CRC check
572         self.crc = self.get_word()
573         ccrc = self.compute_crc32()
574         if self.crc != ccrc:
575             self.putwarn('Bad CRC %08x != %08x' % (self.crc, ccrc), 'CRC!')
576         self.putx(self.idx-40, self.idx, [5, ['CRC:%08x' % (self.crc), 'CRC']])
577
578         # End of Packet
579         if len(self.bits) >= self.idx + 5 and self.get_sym(self.idx) == EOP:
580             self.putx(self.idx, self.idx + 5, [6, ['EOP', 'E']])
581             self.idx += 5
582         else:
583             self.putwarn('No EOP', 'EOP!')
584         # Full text trace
585         if self.options['fulltext'] == 'yes':
586             self.putx(0, self.idx, [12, [self.text, '...']])
587
588         # Meta data for bitrate
589         ss, es = self.edges[0], self.edges[-1]
590         bitrate = self.samplerate*len(self.bits) / float(es - ss)
591         self.put(es, ss, self.out_bitrate, int(bitrate))
592         # Raw binary data (BMC decoded)
593         self.put(es, ss, self.out_binary, [0, bytes(self.bits)])
594
595     def decode(self):
596         if not self.samplerate:
597             raise SamplerateError('Cannot decode without samplerate.')
598         while True:
599             pins = self.wait([{0: 'e'}, {1: 'e'}, {'skip': int(self.samplerate/1e3)}])
600
601             # First sample of the packet, just record the start date
602             if not self.startsample:
603                 self.startsample = self.samplenum
604                 self.previous = self.samplenum
605                 continue
606
607             diff = self.samplenum - self.previous
608
609             # Large idle: use it as the end of packet
610             if diff > self.maxbit:
611                 # the last edge of the packet
612                 self.edges.append(self.previous)
613                 # Export the packet
614                 self.decode_packet()
615                 # Reset for next packet
616                 self.startsample = self.samplenum
617                 self.bits = []
618                 self.edges = []
619                 self.bad = []
620                 self.half_one = False
621                 self.start_one = 0
622             else:   # add the bit to the packet
623                 is_zero = diff > self.threshold
624                 if is_zero and not self.half_one:
625                     self.bits.append(0)
626                     self.edges.append(self.previous)
627                 elif not is_zero and self.half_one:
628                     self.bits.append(1)
629                     self.edges.append(self.start_one)
630                     self.half_one = False
631                 elif not is_zero and not self.half_one:
632                     self.half_one = True
633                     self.start_one = self.previous
634                 else:   # Invalid BMC sequence
635                     self.bad.append((self.start_one, self.previous))
636                     # TODO try to recover
637                     self.bits.append(0)
638                     self.edges.append(self.previous)
639                     self.half_one = False
640             self.previous = self.samplenum