def _make_lin_frame(raw_bytes, frame_start, enhanced_ids): '''Generate a LINStreamFrame from raw bytes''' if len(raw_bytes) >= 2: lf = LINFrame(raw_bytes[0].data & 0x3F, [b.data for b in raw_bytes[1:-1]],\ raw_bytes[-1].data, pid_parity=raw_bytes[0].data >> 6) else: lf = LINFrame(raw_bytes[0].data & 0x3F, pid_parity=raw_bytes[0].data >> 6) if lf.data is not None: if enhanced_ids is not None: if lf.id in enhanced_ids: lf.cs_type = LINChecksum.Enhanced else: # Determine checksum type automatically if not lf.checksum_is_valid(): lf.cs_type = LINChecksum.Enhanced # Try using enhanced checksum if not lf.checksum_is_valid(): lf.cs_type = LINChecksum.Classic # Revert to classic checksum sf = LINStreamFrame((frame_start, raw_bytes[-1].end_time), lf) # Add annotated PID field pid_start = raw_bytes[0].subrecords[1].start_time pid_end = raw_bytes[0].subrecords[1].end_time id_end = (pid_end - pid_start) / 8 * 6 + pid_start sf.subrecords.append(stream.StreamSegment((pid_start, id_end), lf.id, kind='ID')) sf.subrecords[-1].annotate('addr', {'_bits':6}) status = stream.StreamStatus.Ok if lf.pid_is_valid() else LINStreamStatus.PIDError sf.subrecords.append(stream.StreamSegment((id_end, pid_end), lf.pid >> 6, kind='PID parity', status=status)) sf.subrecords[-1].annotate('check', {'_bits':2}, stream.AnnotationFormat.Hidden) if len(raw_bytes) >= 2: if len(raw_bytes) > 2: # Add data annotation for d in raw_bytes[1:-1]: d_info = d.subrecords[1] sf.subrecords.append(stream.StreamSegment((d_info.start_time, d_info.end_time), d.data, kind='data')) sf.subrecords[-1].annotate('data', {'_bits':8}) # Add checksum annotation cs_info = raw_bytes[-1].subrecords[1] status = stream.StreamStatus.Ok if lf.checksum_is_valid() else LINStreamStatus.ChecksumError sf.subrecords.append(stream.StreamSegment((cs_info.start_time, cs_info.end_time),\ lf.checksum, kind='checksum', status=status)) sf.subrecords[-1].annotate('check', {'_bits':8}, stream.AnnotationFormat.Hex) return sf
def can_decode(can, polarity=CANConfig.IdleHigh, bit_rate=None, bit_timing=None, coerce_rates=None, logic_levels=None,\ stream_type=stream.StreamType.Samples, decode_info=None): '''Decode a CAN data stream This is a generator function that can be used in a pipeline of waveform procesing operations. Sample streams are a sequence of SampleChunk Objects. Edge streams are a sequence of 2-tuples of (time, int) pairs. The type of stream is identified by the stream_type parameter. Sample streams will be analyzed to find edge transitions representing 0 and 1 logic states of the waveforms. With sample streams, an initial block of data is consumed to determine the most likely logic levels in the signal. can (iterable of SampleChunk objects or (float, int) pairs) A sample stream or edge stream representing a CAN data signal. This can be one of CAN-High, CAN-Low, or the differential voltage between them. polarity (CANConfig) Set the polarity (idle state high or low). This will be low when the can parameter is from CAN-Low, high when CAN-High, and dependent on probe orientation when using a differential input. bit_rate (number or None) The bit rate of the stream. If None, the first 50 edges will be analyzed to automatically determine the most likely bit rate for the stream. On average 50 edges will occur after 11 bytes have been captured. bit_timing (CANTiming or None) An optional CANTiming object that specifies the time quanta for each bit phase. If None, a default timing object is used with prop. delay = 1q and p1 & p2 = 4q. coerce_rates (sequence of number or None) An optional list of standard bit rates to coerce the automatically detected bit rate to. logic_levels ((float, float) or None) Optional pair that indicates (low, high) logic levels of the sample stream. When present, auto level detection is disabled. This has no effect on edge streams. stream_type (streaming.StreamType) A StreamType value indicating that the can parameter represents either Samples or Edges decode_info (dict or None) An optional dictionary object that is used to monitor the results of automatic parameter analysis and retrieve bit timing. Yields a series of CANStreamFrame objects. Each frame contains subrecords marking the location of sub-elements within the frame. CRC and Ack errors are recorded as an error status in their respective subrecords. Raises AutoLevelError if stream_type = Samples and the logic levels cannot be determined. Raises AutoRateError if auto-rate detection is active and the bit rate cannot be determined. ''' if stream_type == stream.StreamType.Samples: if logic_levels is None: can_it, logic_levels = check_logic_levels(can) else: can_it = can edges = find_edges(can_it, logic_levels, hysteresis=0.4) else: # The stream is already a list of edges edges = can if bit_rate is None: # Find the bit rate # Tee off an independent iterator to determine bit rate edges_it, sre_it = itertools.tee(edges) min_edges = 50 symbol_rate_edges = list(itertools.islice(sre_it, min_edges)) # We need to ensure that we can pull out enough edges from the iterator slice if len(symbol_rate_edges) < min_edges: raise AutoRateError('Unable to compute automatic bit rate. Insufficient edges.') raw_symbol_rate = find_symbol_rate(iter(symbol_rate_edges), spectra=2) # Delete the tee'd iterators so that the internal buffer will not grow # as the edges_it is advanced later on del symbol_rate_edges del sre_it if coerce_rates: # find the standard rate closest to the raw rate bit_rate = _coerce_symbol_rate(raw_symbol_rate, coerce_rates) else: bit_rate = raw_symbol_rate if bit_rate == 0: raise AutoRateError('Unable to compute automatic bit rate. Got 0.') #print('### decoded bit rate:', bit_rate) else: edges_it = edges # Invert edge polarity if idle-low if polarity == CANConfig.IdleLow: edges_it = ((t, 1 - e) for t, e in edges_it) bit_period = 1.0 / float(bit_rate) es = EdgeSequence(edges_it, bit_period) if bit_timing is None: # Use default timing: prop delay = 1q, p1 & p2 = 4q bit_timing = CANTiming(1, 4) bit_timing.set_quantum_period(bit_period) #print('### resync jump:', bit_timing.resync_jump) sample_points = [] be = _BitExtractor(es, bit_timing, sample_points) if decode_info is not None: decode_info['bit_rate'] = bit_rate if stream_type == stream.StreamType.Samples: decode_info['logic_levels'] = logic_levels decode_info['sample_points'] = sample_points # initialize to point where state is high --> idle time before first SOF while es.cur_state() == 0 and not es.at_end(): es.advance_to_edge() while not es.at_end(): # look for SOF falling edge #es.advance_to_edge() be.advance_to_falling() # We are now at a potential SOF # We could have an anamolous edge at the end of the edge list # Check if edge sequence is complete after our advance if es.at_end(): break start_time = es.cur_time start_sample = len(sample_points) es.advance(bit_timing.sample_point_delay) # Move to sample point of SOF bit unstuffed_bits = be.get_bits(std_header_bits) stuffing_error = True if len(unstuffed_bits) < std_header_bits and not es.at_end() else False # If a data or remote frame ends with an error frame we will get a stuffing error. # If the error happens in the EOF field, the frame is still recoverable. # If a stuffing error occures in the last-but-one bit of the EOF it is regarded as an overload frame. found_data_rmt_frame = False field_info = [] if len(unstuffed_bits) >= std_header_bits: found_data_rmt_frame = True header_bits = std_header_bits # Extract fields from unstuffed bits id_bits = unstuffed_bits[1:12]; field_info.append(('id', (1, 11))) rtr = unstuffed_bits[12]; field_info.append(('rtr', (12, 12))) ide = unstuffed_bits[13]; field_info.append(('ide', (13, 13))) field_ix = 14 if ide == 1: # Extended format frame unstuffed_bits += be.get_bits(ext_header_bits - std_header_bits) if len(unstuffed_bits) >= ext_header_bits: header_bits = ext_header_bits srr = rtr id_ext_bits = unstuffed_bits[field_ix:field_ix + 18] field_info.append(('id_ext', (field_ix, field_ix+17))) field_ix += 18 rtr = unstuffed_bits[field_ix]; field_info.append(('rtr', (field_ix, field_ix))) field_info[1] = ('srr', (12, 12)) field_ix += 1 r1 = unstuffed_bits[field_ix]; field_info.append(('r1', (field_ix, field_ix))) field_ix += 1 else: # ERROR: short extended frame # Not enough bits to have partially decoded frame. Just abort #print('### short extended frame:', len(unstuffed_bits), ext_header_bits) continue r0 = unstuffed_bits[field_ix]; field_info.append(('r0', (field_ix, field_ix))) field_ix += 1 dlc_bits = unstuffed_bits[field_ix:field_ix + 4]; field_info.append(('dlc', (field_ix, field_ix+3))) dlc = min(join_bits(dlc_bits), 8) # Limit to max of 8 data bytes field_ix += 4 data = [] if rtr == 0: # Data frame remaining_stuffed_bits = 8 * dlc + 15 else: # Remote frame remaining_stuffed_bits = 15 min_frame_bits = header_bits + remaining_stuffed_bits unstuffed_bits += be.get_bits(remaining_stuffed_bits) short_frame = False if rtr == 0: # Data frame # Verify we have enough raw bits if len(unstuffed_bits) < min_frame_bits: # ERROR: short frame short_frame = True else: # Get data bytes for _ in xrange(dlc): data.append(join_bits(unstuffed_bits[field_ix:field_ix + 8])) field_info.append(('data', (field_ix, field_ix+7))) field_ix += 8 else: # Remote frame # Verify we have enough raw bits if len(unstuffed_bits) < min_frame_bits: # ERROR: short frame #print('### short remote frame', len(unstuffed_bits), min_frame_bits, unstuffed_bits, stuffed_bits) short_frame = True form_error = False check_bits = [] ack = 1 if not short_frame: # Get checksum check_bits = unstuffed_bits[field_ix:field_ix + 15]; field_info.append(('crc', (field_ix, field_ix+14))) field_ix += 15 # The remaining fields (crc delim, ack, ack delim) are not stuffed end_bits = be.get_bits(3, unstuff=False) if len(end_bits) == 3: ack = True if end_bits[1] == 0 else False if end_bits[0] != 1 or end_bits[2] != 1: form_error = True else: ack = False form_error = True # The last frame of the stream requires special treatment # To position ourselves after the ack delim. if es.cur_state() == 1: es.advance((2 - len(end_bits)) * bit_period) field_info.append(('ack', (field_ix+1, field_ix+1))) if ide == 0: cf = CANStandardFrame(join_bits(id_bits), data, join_bits(dlc_bits), join_bits(check_bits), ack) else: cf = CANExtendedFrame(join_bits(id_bits + id_ext_bits), data, \ join_bits(dlc_bits), join_bits(check_bits), ack) cf.srr = srr cf.r1 = r1 cf.rtr = rtr cf.ide = ide cf.r0 = r0 # Determine the end of the frame if es.cur_state() == 1: if es.cur_time > es.next_states[0]: # Special case for last frame in stream end_time = es.cur_time + 5 * bit_period + bit_timing.post_sample_delay elif es.next_states[0] > es.cur_time + 5 * bit_period: end_time = es.cur_time + 5 * bit_period + bit_timing.post_sample_delay else: end_time = es.next_states[0] stuffing_error = True es.advance_to_edge() be.dom_start_time = es.cur_time else: # Aready in dominant state end_time = be.dom_start_time stuffing_error = True status = CANStreamStatus.FormError if form_error else stream.StreamStatus.Ok sf = CANStreamFrame((start_time, end_time), cf, field_info, be.stuffed_bits) if short_frame: sf.annotate('frame_bad', {}, stream.AnnotationFormat.Hidden) sf.status = CANStreamStatus.ShortFrameError # Add subrecords for each field in the frame adj_info = _adjust_fields_for_stuffing(field_info, be.stuffed_bits) field_sizes = [e - s + 1 for _, (s, e) in field_info] data_ix = 0 for (field, bit_bounds), field_size in zip(adj_info, field_sizes): bounds = (sample_points[start_sample + bit_bounds[0]][0], sample_points[start_sample + bit_bounds[1] + 1][0]) if field in _can_field_formats: style, text_format = _can_field_formats[field] else: style = 'ctrl' text_format = stream.AnnotationFormat.Hex value = getattr(cf, field) if field == 'data': value = value[data_ix] data_ix += 1 status = stream.StreamStatus.Ok if field == 'crc' and not cf.crc_is_valid(): status = CANStreamStatus.CRCError if field == 'ack' and not cf.ack: status = CANStreamStatus.AckError sf.subrecords.append(stream.StreamSegment(bounds, value, kind=field, status=status)) sf.subrecords[-1].annotate(style, {'_bits':field_size}, text_format) yield sf # Check if the EOF was complete if stuffing_error: # This could be an error or overload frame # Keep fetching dominant bits until they become recessive. Then look for 8 recessive delimiter bits. while es.cur_state() == 0 and not es.at_end(): es.advance(bit_period) if es.cur_time < es.next_states[0]: end_time = es.next_states[0] else: # Special case for end of stream end_time = es.cur_time + 7 * bit_period if end_time - es.cur_time > 6.5 * bit_period: # Valid error or overload frame es.advance(7 * bit_period) if found_data_rmt_frame: # There was an error frame following a data or remote frame cf = CANErrorFrame() else: cf = CANOverloadFrame() end_time = es.cur_time + bit_timing.post_sample_delay sf = CANStreamFrame((be.dom_start_time, end_time), cf) sf.annotate('frame', {'name':''}, stream.AnnotationFormat.String) yield sf
def rc5_decode(ir_stream, carrier_freq=36.0e3, polarity=ir.IRConfig.IdleLow, logic_levels=None, \ stream_type=stream.StreamType.Samples): '''Decode RC5 infrared protocol This is a generator function that can be used in a pipeline of waveform procesing operations. ir_stream (iterable of SampleChunk objects or (float, int) pairs) A sample stream or edge stream of IR pulses. The type of stream is identified by the stream_type parameter. When this is a sample stream, an initial block of data is consumed to determine the most likely logic levels in the signal. This signal can be either modulated or demodulated. carrier_freq (float) The carrier frequency for modulation. polarity (infrared.IRConfig) Set the polarity (idle state high or low). logic_levels ((float, float) or None) Optional pair that indicates (low, high) logic levels of the sample stream. When present, auto level detection is disabled. This has no effect on edge streams. stream_type (streaming.StreamType) A StreamType value indicating that the ir_stream parameter represents either Samples or Edges. Yields a series of RC5StreamMessage objects. Raises AutoLevelError if stream_type = Samples and the logic levels cannot be determined. ''' if stream_type == stream.StreamType.Samples: if logic_levels is None: samp_it, logic_levels = decode.check_logic_levels(ir_stream) else: samp_it = ir_stream edges = decode.find_edges(samp_it, logic_levels, hysteresis=0.4) else: # the stream is already a list of edges edges = ir_stream # Demodulate signal (also passes an unmodulated signal without changes) edges = ir.demodulate(edges, carrier_freq, polarity) pulse_width = 889.0e-6 # 889us pulse width es = decode.EdgeSequence(edges, pulse_width) while not es.at_end(): # Look for the rising edge of a pulse if es.cur_state() == 0: es.advance_to_edge() # Now we're 1 msg_start_time = es.cur_time - pulse_width # Move ahead half a pulse and check that we are still 1 es.advance(pulse_width / 2.0) if es.cur_state() != 1: continue # Advance through edge sequence recording state until we see 3 0's or 3 1's in a row # This indicates a break in the Manchester encoding. coded_bits = [0, 1] bit_starts = [0.0, 0.0] same_count = 1 prev_state = 1 while True: es.advance() coded_bits.append(es.cur_state()) bit_starts.append(es.cur_time - pulse_width / 2.0) if es.cur_state() == prev_state: same_count += 1 else: same_count = 1 if same_count > 2: break prev_state = es.cur_state() msg_end_time = es.cur_time - pulse_width if len(coded_bits) >= 14 * 2: # Decode Manchester # The second bit of each pair is the same as the decoded bit msg_bits = coded_bits[1:28:2] mb_starts = bit_starts[::2] #print('$$$ coded_bits:', coded_bits) #print('$$$ msg_bits:', msg_bits) toggle = msg_bits[2] addr = join_bits(msg_bits[3:8]) cmd = join_bits([0 if msg_bits[1] else 1] + msg_bits[8:14]) msg = RC5Message(cmd, addr, toggle) sm = RC5StreamMessage((msg_start_time, msg_end_time), msg) sm.annotate('frame', {'name': 'frame'}, stream.AnnotationFormat.Hidden) sm.subrecords.append( stream.StreamSegment((mb_starts[2], mb_starts[3]), toggle, kind='toggle')) sm.subrecords[-1].annotate('data', {'_bits': 1}, stream.AnnotationFormat.Int) sm.subrecords.append( stream.StreamSegment((mb_starts[3], mb_starts[8]), addr, kind='addr')) sm.subrecords[-1].annotate('addr', {'_bits': 5}) sm.subrecords.append( stream.StreamSegment((mb_starts[8], mb_starts[14]), cmd, kind='cmd')) sm.subrecords[-1].annotate('data', {'_bits': 6}) if cmd > 63: # Extended format; 7-th bit is just after first start bit sm.subrecords[-1].fields['_bits'] = 7 sm.subrecords.append( stream.StreamSegment((mb_starts[1], mb_starts[2]), msg_bits[1], kind='cmd bit-7')) sm.subrecords[-1].annotate('data1', {'_bits': 1}, stream.AnnotationFormat.Int) yield sm
def rc6_decode(ir_stream, carrier_freq=36.0e3, polarity=ir.IRConfig.IdleLow, logic_levels=None, \ stream_type=stream.StreamType.Samples): '''Decode RC6 infrared protocol This is a generator function that can be used in a pipeline of waveform procesing operations. ir_stream (iterable of SampleChunk objects or (float, int) pairs) A sample stream or edge stream of IR pulses. The type of stream is identified by the stream_type parameter. When this is a sample stream, an initial block of data is consumed to determine the most likely logic levels in the signal. This signal can be either modulated or demodulated. carrier_freq (float) The carrier frequency for modulation. polarity (infrared.IRConfig) Set the polarity (idle state high or low). logic_levels ((float, float) or None) Optional pair that indicates (low, high) logic levels of the sample stream. When present, auto level detection is disabled. This has no effect on edge streams. stream_type (streaming.StreamType) A StreamType value indicating that the ir_stream parameter represents either Samples or Edges. Yields a series of RC6StreamMessage objects. Raises AutoLevelError if stream_type = Samples and the logic levels cannot be determined. ''' if stream_type == stream.StreamType.Samples: if logic_levels is None: samp_it, logic_levels = decode.check_logic_levels(ir_stream) else: samp_it = ir_stream edges = decode.find_edges(samp_it, logic_levels, hysteresis=0.4) else: # the stream is already a list of edges edges = ir_stream # Demodulate signal (also passes an unmodulated signal without changes) edges = ir.demodulate(edges, carrier_freq, polarity) pulse_width = 444.0e-6 # 444us pulse width es = decode.EdgeSequence(edges, pulse_width) epsilon = 30.0e-6 # allow +/-30us variation for pulses and bit times time_is_nearly = functools.partial(ir.time_is_nearly, epsilon=epsilon) while not es.at_end(): # Look for the rising edge of a pulse if es.cur_state() == 0: es.advance_to_edge() # Now we're 1 msg_start_time = es.cur_time ts = es.advance_to_edge() if not time_is_nearly(ts, 6 * pulse_width): continue # This is not the leading AGC pulse of a message ts = es.advance_to_edge() if not time_is_nearly(ts, 2 * pulse_width): continue # This is not the leading AGC pulse of a message # Move ahead half a pulse and check that we are still 1 es.advance(pulse_width / 2.0) if es.cur_state() != 1: continue # Not a valid start bit es.advance() # End of start bit # Advance through edge sequence recording state until we see 4 0's or 4 1's in a row # This indicates a break in the Manchester encoding. coded_bits = [1, 0] # Start bit bit_starts = [0.0, 0.0] same_count = 1 prev_state = 1 while True: es.advance() coded_bits.append(es.cur_state()) bit_starts.append(es.cur_time - pulse_width / 2.0) if es.cur_state() == prev_state: same_count += 1 else: same_count = 1 if same_count > 3: break prev_state = es.cur_state() msg_end_time = es.cur_time - 2.5 * pulse_width #print('$$$ found bits:', len(coded_bits)) if len(coded_bits) >= 22 * 2: # Decode Manchester # The first bit of each pair is the same as the decoded bit msg_bits = coded_bits[0:44:2] mb_starts = bit_starts[::2] mode = join_bits(msg_bits[1:4]) toggle = msg_bits[4] if mode == 6: # RC6A message if msg_bits[6]: # 15-bit customer field msg_bits = coded_bits[0:76:2] customer = join_bits(msg_bits[7:22]) asb = 22 # addr start bit index else: # 7-bit customer field msg_bits = coded_bits[0:60:2] customer = join_bits(msg_bits[7:14]) asb = 14 else: # RC6 message customer = None asb = 6 #print('$$$ coded_bits:', coded_bits) #print('$$$ msg_bits:', msg_bits) addr = join_bits(msg_bits[asb:asb+8]) cmd = join_bits(msg_bits[asb+8:asb+16]) msg = RC6Message(cmd, addr, toggle, mode, customer) sm = RC6StreamMessage((msg_start_time, msg_end_time), msg) sm.annotate('frame', {'name':'frame'}, stream.AnnotationFormat.Hidden) sm.subrecords.append(stream.StreamSegment((mb_starts[1], mb_starts[4]), mode, kind='mode')) sm.subrecords[-1].annotate('addr', {'_bits':3}) sm.subrecords.append(stream.StreamSegment((mb_starts[4], mb_starts[6]), toggle, kind='toggle')) sm.subrecords[-1].annotate('data', {'_bits':1}, stream.AnnotationFormat.Int) if mode == 6: sm.subrecords.append(stream.StreamSegment((mb_starts[6], mb_starts[asb]), customer, kind='customer')) sm.subrecords[-1].annotate('data', {'_bits':7 + msg_bits[6]*8}) sm.subrecords.append(stream.StreamSegment((mb_starts[asb], mb_starts[asb+8]), addr, kind='addr')) sm.subrecords[-1].annotate('addr', {'_bits':8}) sm.subrecords.append(stream.StreamSegment((mb_starts[asb+8], mb_starts[asb+16]), cmd, kind='cmd')) sm.subrecords[-1].annotate('data', {'_bits':8}) yield sm
def ps2_decode(clk, data, logic_levels=None, stream_type=stream.StreamType.Samples): '''Decode a PS/2 data stream This is a generator function that can be used in a pipeline of waveform processing operations. Sample streams are a sequence of SampleChunk Objects. Edge streams are a sequence of 2-tuples of (time, int) pairs. The type of stream is identified by the stream_type parameter. Sample streams will be analyzed to find edge transitions representing 0 and 1 logic states of the waveforms. With sample streams, an initial block of data on the clk stream is consumed to determine the most likely logic levels in the signal. clk (iterable of SampleChunk objects or (float, int) pairs) A sample stream or edge stream representing a PS/2 clk signal data (iterable of SampleChunk objects or (float, int) pairs) A sample stream or edge stream representing a PS/2 data signal. logic_levels ((float, float) or None) Optional pair that indicates (low, high) logic levels of the sample stream. When present, auto level detection is disabled. This has no effect on edge streams. stream_type (streaming.StreamType) A StreamType value indicating that the clk and data parameters represent either Samples or Edges Yields a series of PS2StreamFrame objects. Raises AutoLevelError if stream_type = Samples and the logic levels cannot be determined. ''' if stream_type == stream.StreamType.Samples: if logic_levels is None: s_clk_it, logic_levels = check_logic_levels(clk) else: s_clk_it = clk hyst = 0.4 clk_it = find_edges(s_clk_it, logic_levels, hysteresis=hyst) data_it = find_edges(data, logic_levels, hysteresis=hyst) else: # the streams are already lists of edges clk_it = clk data_it = data edge_sets = { 'clk': clk_it, 'data': data_it } es = MultiEdgeSequence(edge_sets, 0.0) # begin to decode direction = PS2Dir.DeviceToHost find_frame_start = True bits_remaining = 10 bits = [] byte_complete = False # Minimum clock is 10kHz min_period = 1.0 / 10.0e3 * 1.05 # add 5% while not es.at_end('clk'): ts, cname = es.advance_to_edge(channel_name='clk') if es.at_end('clk'): continue clk_val = es.cur_state('clk') d_val = es.cur_state('data') if ts > 100.0e-6 and not find_frame_start: # Some sort of framing error happened. Start over to resynchronize find_frame_start = True bits = [] ne = stream.StreamEvent(es.cur_time(), kind='PS/2 resynch', \ status=PS2StreamStatus.FramingError) yield ne if find_frame_start == True and (d_val == 0): bits_remaining = 10 start_time = es.cur_time() timing_error = False if clk_val == 0: # falling edge direction = PS2Dir.DeviceToHost find_frame_start = False elif clk_val == 1 and ts > 100.0e-6: direction = PS2Dir.HostToDevice find_frame_start = False get_ack = False continue if not find_frame_start: # Confirm that the clock rate is fast enough # We should always be advancing by no more than 1/2 the minimum clock period if ts > (min_period / 2): timing_error = True if direction == PS2Dir.DeviceToHost: if clk_val == 0: # this is a falling edge, capture data if bits_remaining: bits.append(d_val) bits_remaining -= 1 if not bits_remaining: # completed a byte, verify and build frame object end_time = es.cur_time() bit_period = (end_time - start_time) / 10 start_time -= bit_period / 2 end_time += bit_period / 2 data_time = start_time + bit_period parity_time = end_time - (bit_period * 2) stop_time = end_time - bit_period stop_end_time = end_time byte_complete = True else: # HostToDevice if clk_val == 1 and get_ack == False: # this is a rising edge, capture data if bits_remaining: bits.append(d_val) bits_remaining -= 1 if not bits_remaining: # ready to look for ack get_ack = True elif clk_val == 0 and get_ack == True: bits.append(d_val) end_time = es.cur_time() bit_period = (end_time - start_time) / 10.5 start_time -= bit_period / 2 end_time += bit_period / 2 data_time = start_time + bit_period parity_time = end_time - (bit_period * 2.5) stop_time = end_time - (bit_period * 1.5) ack_time = end_time - (bit_period * 0.75) stop_end_time = ack_time byte_complete = True if d_val == 0: es.advance_to_edge('data') # move ahead to rising edge of ack pulse if byte_complete: data = join_bits(reversed(bits[0:8])) p = 1 for b in bits[0:8]: p ^= b parity_error = True if p != bits[8] else False framing_error = True if bits[9] != 1 else False # missing stop bit status = PS2StreamStatus.FramingError if framing_error else \ PS2StreamStatus.TimingError if timing_error else stream.StreamStatus.Ok nf = PS2StreamFrame((start_time, end_time), PS2Frame(data, direction), status=status) nf.annotate('frame', {}, stream.AnnotationFormat.Hidden) nf.subrecords.append(stream.StreamSegment((start_time, data_time), kind='start bit')) nf.subrecords[-1].annotate('misc', {'_bits':1}, stream.AnnotationFormat.Invisible) nf.subrecords.append(stream.StreamSegment((data_time, parity_time), data, kind='data bits')) nf.subrecords[-1].annotate('data', {'_bits':bits}, stream.AnnotationFormat.General) status = PS2StreamStatus.ParityError if parity_error else stream.StreamStatus.Ok nf.subrecords.append(stream.StreamSegment((parity_time, stop_time), kind='parity', status=status)) nf.subrecords[-1].annotate('check', {'_bits':1}, stream.AnnotationFormat.Hidden) nf.subrecords.append(stream.StreamSegment((stop_time, stop_end_time), kind='stop bit')) nf.subrecords[-1].annotate('misc', {'_bits':1}, stream.AnnotationFormat.Invisible) if direction == PS2Dir.HostToDevice: ack_error = True if bits[10] != 0 else False status = PS2StreamStatus.AckError if ack_error else stream.StreamStatus.Ok nf.subrecords.append(stream.StreamSegment((ack_time, end_time), kind='ack bit', status=status)) nf.subrecords[-1].annotate('ack', {'_bits':1}, stream.AnnotationFormat.Hidden) bits = [] find_frame_start = True byte_complete = False yield nf
def lm73_decode(i2c_stream, addresses=LM73Addresses): '''Decode an LM73 data stream i2c_stream (sequence of StreamRecord or I2CTransfer) An iterable representing either a stream of I2C StreamRecord objects or I2CTransfer objects produced by i2c_decode() or reconstruct_i2c_transfers() respectively. addresses (set of ints) A collection identifying the valid LM73 addresses to decode. All others are ignored. Yields a series of LM73Transfer objects and any unrelated I2CTransfer objects. ''' cur_reg = LM73Register.Temperature # check type of stream stream_it, check_it = itertools.tee(i2c_stream) try: rec0 = next(check_it) except StopIteration: # Stream is empty rec0 = None if rec0 is not None: if not isinstance(rec0, i2c.I2CTransfer): # Convert the stream to a set of I2C transfers stream_it = i2c.reconstruct_i2c_transfers(stream_it) del check_it for tfer in stream_it: if tfer.address not in addresses: yield tfer continue if tfer.r_wn == i2c.I2C.Write: if len(tfer.data) == 0: # Error condition # This should only happen if the data portion of a write is missing tfer.status = LM73StreamStatus.MissingDataError yield tfer continue elif len(tfer.data) == 1: # Set pointer op cur_reg = tfer.data[0] lm_tfer = LM73Transfer(tfer.address, LM73Operation.SetPointer, cur_reg, None) lm_tfer.annotate('frame', {}, stream.AnnotationFormat.Hidden) lm_tfer.subrecords = tfer.subrecords lm_tfer.subrecords[ 0].data_format = stream.AnnotationFormat.Small address = lm_tfer.subrecords[0].data >> 1 lm_tfer.subrecords[0].fields['value'] = 'set ptr\n{}'.format( hex(address)) lm_tfer.subrecords[2].annotate('ctrl', None, stream.AnnotationFormat.Enum) lm_tfer.subrecords[2].fields['value'] = LM73Register( lm_tfer.subrecords[2].data) else: # Write data cur_reg = tfer.data[0] lm_tfer = LM73Transfer(tfer.address, LM73Operation.WriteData, \ cur_reg, tfer.data[1:]) lm_tfer.annotate('frame', {}, stream.AnnotationFormat.Hidden) lm_tfer.subrecords = tfer.subrecords else: # Read data lm_tfer = LM73Transfer(tfer.address, LM73Operation.ReadData, cur_reg, tfer.data) lm_tfer.annotate('frame', {}, stream.AnnotationFormat.Hidden) lm_tfer.subrecords = tfer.subrecords lm_tfer.subrecords[0].data_format = stream.AnnotationFormat.Small address = lm_tfer.subrecords[0].data >> 1 lm_tfer.subrecords[0].fields['value'] = 'read\n{}'.format( hex(address)) for sr in lm_tfer.subrecords[2::2]: sr.data_format = stream.AnnotationFormat.Hex if cur_reg == LM73Register.Temperature and len(tfer.data) == 2: temp_data = lm_tfer.subrecords[2:] temp_sr = stream.StreamSegment( (temp_data[0].start_time, temp_data[-1].end_time), kind='LM73 Temperature') value = 'Temp. = {} C\n0x{:04X}'.format( lm_tfer.temperature, (tfer.data[0] << 8) + tfer.data[1]) temp_sr.annotate('data', {'value': value}, stream.AnnotationFormat.Small) temp_sr.subrecords = temp_data for sr in temp_sr.subrecords: sr.data_format = stream.AnnotationFormat.Hidden lm_tfer.subrecords = lm_tfer.subrecords[0:2] + [temp_sr] lm_tfer.i2c_tfer = tfer yield lm_tfer
def _build_j1850_record(bytes, ifr_bytes, byte_starts, ifr_byte_starts, ifr_with_crc, bounds): '''Create a J1850 frame from raw bytes''' header_len = 1 if bytes[0] & 0x10 else 3 priority = bytes[0] >> 5 msg_type = bytes[0] & 0x0F data = bytes[header_len:-1] if len(data) == 0: data = None ifr_data = None ifr_crc = None if len(ifr_bytes) > 0: ifr_data = ifr_bytes[:-1] if ifr_with_crc else ifr_bytes ifr_crc = ifr_bytes[-1] if ifr_with_crc else None if header_len == 3: nf = J1850Frame(priority, msg_type, data, bytes[1], bytes[2], crc=bytes[-1], \ ifr_data=ifr_data, ifr_crc=ifr_crc) else: nf = J1850Frame(priority, msg_type, data, crc=bytes[-1], ifr_data=ifr_data, ifr_crc=ifr_crc) sf = J1850StreamFrame(bounds, nf) # Add annotations bounds = (byte_starts[0], byte_starts[1]) sf.subrecords.append(stream.StreamSegment(bounds, bytes[0], kind='header')) sf.subrecords[-1].annotate('ctrl', {'_bits': 8}) if header_len == 3: # Add target and source bounds = (byte_starts[1], byte_starts[2]) sf.subrecords.append( stream.StreamSegment(bounds, bytes[1], kind='target')) sf.subrecords[-1].annotate('addr', {'_bits': 8}) bounds = (byte_starts[2], byte_starts[3]) sf.subrecords.append( stream.StreamSegment(bounds, bytes[2], kind='source')) sf.subrecords[-1].annotate('addr', {'_bits': 8}) # Data for i, d in enumerate(bytes[header_len:-1]): bounds = (byte_starts[i + header_len], byte_starts[i + 1 + header_len]) sf.subrecords.append(stream.StreamSegment(bounds, d, kind='data')) sf.subrecords[-1].annotate('data', {'_bits': 8}) # CRC bounds = (byte_starts[-2], byte_starts[-1]) status = J1850StreamStatus.CRCError if not nf.crc_is_valid( ) else stream.StreamStatus.Ok sf.subrecords.append( stream.StreamSegment(bounds, bytes[-1], kind='CRC', status=status)) sf.subrecords[-1].annotate('check', {'_bits': 8}) if len(ifr_bytes) > 0: # IFR last_ifr = len(ifr_bytes) if not ifr_with_crc else len(ifr_bytes) - 1 for i, d in enumerate(ifr_bytes[:last_ifr]): bounds = (ifr_byte_starts[i], ifr_byte_starts[i + 1]) sf.subrecords.append( stream.StreamSegment(bounds, d, kind='IFR data')) sf.subrecords[-1].annotate('data', {'_bits': 8}) if ifr_with_crc: bounds = (ifr_byte_starts[-2], ifr_byte_starts[-1]) status = J1850StreamStatus.CRCError if not nf.ifr_crc_is_valid( ) else stream.StreamStatus.Ok sf.subrecords.append( stream.StreamSegment(bounds, ifr_bytes[-1], kind='CRC', status=status)) sf.subrecords[-1].annotate('check', {'_bits': 8}) return sf
def j1850_pwm_decode(pwm, logic_levels=None, stream_type=stream.StreamType.Samples): '''Decode a J1850 PWM data stream This decodes the Pulse Width Modulated version of J1850 (Ford). This is a generator function that can be used in a pipeline of waveform procesing operations. Sample streams are a sequence of SampleChunk Objects. Edge streams are a sequence of 2-tuples of (time, int) pairs. The type of stream is identified by the stream_type parameter. Sample streams will be analyzed to find edge transitions representing 0 and 1 logic states of the waveforms. With sample streams, an initial block of data is consumed to determine the most likely logic levels in the signal. pwm (iterable of SampleChunk objects or (float, int) pairs) A sample stream or edge stream representing a PWM Bus+ signal or the differential Bus+ - Bus-. logic_levels ((float, float) or None) Optional pair that indicates (low, high) logic levels of the sample stream. When present, auto level detection is disabled. This has no effect on edge streams. stream_type (streaming.StreamType) A StreamType value indicating that the can parameter represents either Samples or Edges Yields a series of J1850StreamFrame objects. Each frame contains subrecords marking the location of sub-elements within the frame. CRC errors are recorded as an error status in their respective subrecords. Raises AutoLevelError if stream_type = Samples and the logic levels cannot be determined. ''' if stream_type == stream.StreamType.Samples: if logic_levels is None: pwm_it, logic_levels = decode.check_logic_levels(pwm) else: pwm_it = pwm edges = decode.find_edges(pwm_it, logic_levels, hysteresis=0.4) else: # The stream is already a list of edges edges = pwm es = decode.EdgeSequence(edges, 0.0) while not es.at_end(): # Look for start of frame es.advance_to_edge() if es.cur_state() != 1: continue frame_start = es.cur_time if es.next_states[0] > es.cur_time: pulse_width = es.next_states[0] - es.cur_time else: pulse_width = 500.0e-6 if 27.0e-6 <= pulse_width <= 34.0e-6: # This is a valid SOF pulse (Tp7) sof_start = es.cur_time es.advance_to_edge() # Move to end of SOF pulse if es.next_states[0] > es.cur_time: pulse_width = es.next_states[0] - es.cur_time else: pulse_width = 500.0e-6 if 42.0e-6 <= es.cur_time - sof_start + pulse_width <= 54.0e-6: # Valid SOF (Tp4) es.advance_to_edge() # Move to first bit else: # Look for break condition if 34.0e-6 < pulse_width <= 43.0e-6 and es.cur_state() == 1: brk = J1850Break() sf = stream.StreamSegment((es.cur_time, es.next_states[0]), brk, kind='break') sf.annotate('frame', {'value': 'Break'}, stream.AnnotationFormat.String) yield sf continue def collect_bits(es): '''Find frame and IFR bits''' frame_bits = [] bit_starts = [] if es.next_states[0] > es.cur_time: pulse_width = es.next_states[0] - es.cur_time else: pulse_width = 500.0e-6 while pulse_width <= 18.0e-6: if 4.0e-6 <= pulse_width <= 18.0e-6: if pulse_width <= 10.0e-6: b = 1 else: b = 0 frame_bits.append(b) bit_starts.append(es.cur_time) bit_pulse = pulse_width # Move to end of pulse es.advance_to_edge() if es.next_states[0] > es.cur_time: pulse_width = es.next_states[0] - es.cur_time else: pulse_width = 500.0e-6 tp3 = bit_pulse + pulse_width if 21.0e-6 <= tp3 <= 27e-6: # Valid bit es.advance_to_edge() # Move to start of next bit if es.next_states[0] > es.cur_time: pulse_width = es.next_states[0] - es.cur_time else: pulse_width = 500.0e-6 else: # No more bits break else: # Invalid pulse < 4us break bit_starts.append(es.cur_time) return (frame_bits, bit_starts, pulse_width) # Get the frame bits frame_bits, bit_starts, pulse_width = collect_bits(es) if 34.0e-6 < pulse_width <= 43.0e-6 and es.cur_state() == 1: brk = J1850Break() sf = stream.StreamSegment((es.cur_time, es.next_states[0]), brk, kind='break') sf.annotate('frame', {'value': 'Break'}, stream.AnnotationFormat.String) yield sf continue # Validate collected bits if len(frame_bits) % 8 != 0 or len(frame_bits) < 2 * 8: continue # Convert frame bits to bytes bytes = [] for i in xrange(len(frame_bits) // 8): bytes.append(join_bits(frame_bits[i * 8:i * 8 + 8])) byte_starts = bit_starts[::8] header_len = 1 if bytes[0] & 0x10 else 3 if header_len == 3 and len(bytes) < 4: continue priority = bytes[0] >> 5 msg_type = bytes[0] & 0x0F data = bytes[header_len:-1] if len(data) == 0: data = None # Look for IFR if es.next_states[0] > es.cur_time: pulse_width = es.next_states[0] - es.cur_time else: pulse_width = 500.0e-6 ifr_bytes = [] ifr_byte_starts = [] ifr_with_crc = False if 42.0e-6 <= pulse_width + (es.cur_time - bit_starts[-2] ) <= 63.0e-6 and (msg_type & 0x08) == 0: # IFR is present es.advance_to_edge() # Start of first IFR bit ifr_with_crc = True if msg_type == J1850MT.FunctionRead else False # Get the IFR bits ifr_bits, ifr_bit_starts, pulse_width = collect_bits(es) if 34.0e-6 < pulse_width <= 43.0e-6 and es.cur_state() == 1: brk = J1850Break() sf = stream.StreamSegment((es.cur_time, es.next_states[0]), brk, kind='break') sf.annotate('frame', {'value': 'Break'}, stream.AnnotationFormat.String) yield sf continue # Validate IFR bits if len(ifr_bits) % 8 == 0 and len(ifr_bits) >= 8: # Convert IFR bits to bytes for i in xrange(len(ifr_bits) // 8): ifr_bytes.append(join_bits(ifr_bits[i * 8:i * 8 + 8])) ifr_byte_starts = ifr_bit_starts[::8] sf = _build_j1850_record(bytes, ifr_bytes, byte_starts, ifr_byte_starts, ifr_with_crc, \ (frame_start, es.cur_time + 4.0e-6)) yield sf
def j1850_vpw_decode(vpw, norm_bit=VPWNormBitStyle.SAE, logic_levels=None, stream_type=stream.StreamType.Samples): '''Decode a J1850 VPW data stream This decodes the Variable Pulse Width version of J1850 (GM & Chrysler). This is a generator function that can be used in a pipeline of waveform procesing operations. Sample streams are a sequence of SampleChunk Objects. Edge streams are a sequence of 2-tuples of (time, int) pairs. The type of stream is identified by the stream_type parameter. Sample streams will be analyzed to find edge transitions representing 0 and 1 logic states of the waveforms. With sample streams, an initial block of data is consumed to determine the most likely logic levels in the signal. vpw (iterable of SampleChunk objects or (float, int) pairs) A sample stream or edge stream representing a VPW data signal. norm_bit (VPWNormBitStyle) How to interpret the normalization bit for In-Frame Response. Either standard SAE style or the GM specific variant. This determines whether the IFR is expected to have a CRC independently from the message type. logic_levels ((float, float) or None) Optional pair that indicates (low, high) logic levels of the sample stream. When present, auto level detection is disabled. This has no effect on edge streams. stream_type (streaming.StreamType) A StreamType value indicating that the can parameter represents either Samples or Edges Yields a series of J1850StreamFrame objects. Each frame contains subrecords marking the location of sub-elements within the frame. CRC errors are recorded as an error status in their respective subrecords. Raises AutoLevelError if stream_type = Samples and the logic levels cannot be determined. ''' if stream_type == stream.StreamType.Samples: if logic_levels is None: vpw_it, logic_levels = decode.check_logic_levels(vpw) else: vpw_it = vpw edges = decode.find_edges(vpw_it, logic_levels, hysteresis=0.4) else: # The stream is already a list of edges edges = vpw es = decode.EdgeSequence(edges, 0.0) while not es.at_end(): # Look for start of frame es.advance_to_edge() if es.cur_state() != 1: continue frame_start = es.cur_time if es.next_states[0] > es.cur_time: pulse_width = es.next_states[0] - es.cur_time else: pulse_width = 500.0e-6 if 163.0e-6 < pulse_width <= 239.0e-6: # This is a valid SOF es.advance_to_edge() # Move to first bit else: if pulse_width > 280.0e-6 and es.cur_state() == 1: brk = J1850Break() sf = stream.StreamSegment((es.cur_time, es.next_states[0]), brk, kind='break') sf.annotate('frame', {'value': 'Break'}, stream.AnnotationFormat.String) yield sf continue def collect_bits(es): '''Find frame and IFR bits''' frame_bits = [] bit_starts = [] is_passive = 1 if es.next_states[0] > es.cur_time: pulse_width = es.next_states[0] - es.cur_time else: pulse_width = 500.0e-6 while pulse_width <= 239.0e-6: if 34.0e-6 < pulse_width <= 163.0e-6: if pulse_width > 96.0e-6: # 128us pulse b = 1 if is_passive else 0 else: # 64us pulse b = 0 if is_passive else 1 frame_bits.append(b) bit_starts.append(es.cur_time) is_passive = 1 - is_passive elif pulse_width > 163.0e-6: # EOD break else: # Invalid pulse < 34us break es.advance_to_edge() if es.next_states[0] > es.cur_time: pulse_width = es.next_states[0] - es.cur_time else: pulse_width = 500.0e-6 bit_starts.append(es.cur_time) return (frame_bits, bit_starts, pulse_width) # Get the frame bits frame_bits, bit_starts, pulse_width = collect_bits(es) if pulse_width > 280.0e-6 and es.cur_state() == 1: brk = J1850Break() sf = stream.StreamSegment((es.cur_time, es.next_states[0]), brk, kind='break') sf.annotate('frame', {'value': 'Break'}, stream.AnnotationFormat.String) yield sf continue # Validate collected bits if len(frame_bits) % 8 != 0 or len(frame_bits) < 2 * 8: continue # Convert frame bits to bytes bytes = [] for i in xrange(len(frame_bits) // 8): bytes.append(join_bits(frame_bits[i * 8:i * 8 + 8])) byte_starts = bit_starts[::8] header_len = 1 if bytes[0] & 0x10 else 3 if header_len == 3 and len(bytes) < 4: continue priority = bytes[0] >> 5 msg_type = bytes[0] & 0x0F data = bytes[header_len:-1] if len(data) == 0: data = None # Look for IFR if es.next_states[0] > es.cur_time: pulse_width = es.next_states[0] - es.cur_time else: pulse_width = 500.0e-6 ifr_bytes = [] ifr_byte_starts = [] ifr_with_crc = False if 200.0e-6 < pulse_width <= 280.0e-6 and (msg_type & 0x08) == 0: # IFR is present # Check normalization bit width to determine if IFR CRC is present es.advance_to_edge() # Start of norm bit if es.next_states[0] > es.cur_time: pulse_width = es.next_states[0] - es.cur_time else: pulse_width = 500.0e-6 if norm_bit == VPWNormBitStyle.SAE: ifr_with_crc = True if pulse_width > 96.0e-6 else False else: # GM ifr_with_crc = True if pulse_width <= 96.0e-6 else False es.advance_to_edge() # Move to first bit # Get the IFR bits ifr_bits, ifr_bit_starts, pulse_width = collect_bits(es) if pulse_width > 280.0e-6 and es.cur_state() == 1: brk = J1850Break() sf = stream.StreamSegment((es.cur_time, es.next_states[0]), brk, kind='break') sf.annotate('frame', {'value': 'Break'}, stream.AnnotationFormat.String) yield sf continue # Validate IFR bits if len(ifr_bits) % 8 == 0 and len(ifr_bits) >= 8: # Convert IFR bits to bytes for i in xrange(len(ifr_bits) // 8): ifr_bytes.append(join_bits(ifr_bits[i * 8:i * 8 + 8])) ifr_byte_starts = ifr_bit_starts[::8] sf = _build_j1850_record(bytes, ifr_bytes, byte_starts, ifr_byte_starts, ifr_with_crc, (frame_start, es.cur_time + 64.0e-6)) yield sf
def i2c_decode(scl, sda, logic_levels=None, stream_type=stream.StreamType.Samples): '''Decode an I2C data stream This is a generator function that can be used in a pipeline of waveform processing operations. The scl, and sda parameters are edge or sample streams. Sample streams are a sequence of SampleChunk Objects. Edge streams are a sequence of 2-tuples of (time, int) pairs. The type of stream is identified by the stream_type parameter. Sample streams will be analyzed to find edge transitions representing 0 and 1 logic states of the waveforms. With sample streams, an initial block of data on the scl stream is consumed to determine the most likely logic levels in the signal. scl (iterable of SampleChunk objects or (float, int) pairs) A sample stream or edge stream representing the I2C serial clock sda (iterable of SampleChunk objects or (float, int) pairs) A sample stream or edge stream representing the I2C serial data logic_levels ((float, float) or None) Optional pair that indicates (low, high) logic levels of the sample stream. When present, auto level detection is disabled. This has no effect on edge streams. stream_type (streaming.StreamType) Indicates the type of stream used for scl and sda. When StreamType.Samples, the iterators represent a sequence of samples. Each sample is a 2-tuple representing the time of the sample and the sample's value. When this type is used, the scl stream is analyzed to determine the logic levels of the two streams. When StreamType.Edges, the iterators represent a series of edges. scl and sda are iterables of 2-tuples representing each edge transition. The 2-tuples *must* be in the absolute time form (time, logic level). Yields a series of StreamRecord-based objects. These will be one of three event types or two data types. The three events are represented by StreamEvent object with these obj.kind attribute values: * 'I2C start' The start of an I2C transfer * 'I2C restart' A start condition during a transfer * 'I2C stop' The end of a transfer The two data types are represented by the objects I2CAddress and I2CByte. The former is a 7-bit or 10-bit address from the start of a transfer or restart. The latter contains the data read or written during the transfer. I2CByte has an attribute ack_bit that records the value of the ACK for that byte. I2CAddress has a r_wn attribute that indicates if the transfer is a read or write. The subrecords attribute contains the I2CByte object or objects that composed the address. Raises AutoLevelError when the stream_type is Samples and the logic levels cannot be determined automatically. ''' if stream_type == stream.StreamType.Samples: if logic_levels is None: s_scl_it, logic_levels = check_logic_levels(scl) else: s_scl_it = scl hyst = 0.4 scl_it = find_edges(s_scl_it, logic_levels, hysteresis=hyst) sda_it = find_edges(sda, logic_levels, hysteresis=hyst) else: # the streams are already lists of edges scl_it = scl sda_it = sda edge_sets = {'scl': scl_it, 'sda': sda_it} es = MultiEdgeSequence(edge_sets, 0.0) S_IDLE = 0 S_ADDR = 1 S_ADDR_10B = 2 S_DATA = 3 state = S_IDLE start_time = None end_time = None bits = [] prev_10b_addr = None while not es.at_end(): ts, cname = es.advance_to_edge() if state == S_IDLE: bits = [] if cname == 'sda' and not es.at_end('sda') \ and es.cur_state('sda') == 0 and es.cur_state('scl') == 1: # start condition met se = stream.StreamEvent(es.cur_time(), data=None, kind='I2C start') yield se state = S_ADDR else: # check for stop and restart if cname == 'sda' and not es.at_end('sda'): if es.cur_state('sda') == 1 and es.cur_state('scl') == 1: # stop condition met se = stream.StreamEvent(es.cur_time(), data=None, kind='I2C stop') yield se state = S_IDLE continue if es.cur_state('sda') == 0 and es.cur_state('scl') == 1: # restart condition met se = stream.StreamEvent(es.cur_time(), data=None, kind='I2C restart') yield se bits = [] state = S_ADDR continue if cname == 'scl' and not es.at_end('scl') and es.cur_state( 'scl') == 1: # rising edge of SCL # accumulate the bit if len(bits) == 0: start_time = es.cur_time() bits.append(es.cur_state('sda')) end_time = es.cur_time() if len(bits) == 9: word = join_bits(bits[0:8]) ack_bit = bits[8] clock_period = (end_time - start_time) / 9.0 f_bound = (start_time - 0.4 * clock_period, end_time + 0.4 * clock_period) d_start = start_time - 0.25 * clock_period d_bound = (d_start, d_start + 8.5 * clock_period) a_bound = (end_time - 0.25 * clock_period, end_time + 0.25 * clock_period) a_status = stream.StreamStatus.Ok if ack_bit == 0 else stream.StreamStatus.Error if state == S_ADDR: addr = word >> 1 r_wn = word & 0x01 if addr > 0x77: # first 2 bits of 10-bit address if r_wn: # 10-bit addressed read # We will not receive the second byte of the address # The 10-bit address being read should be the last one # written to. if prev_10b_addr is not None: addr_10b = prev_10b_addr # Check that the upper bits match ub = addr & 0x03 prev_ub = (prev_10b_addr >> 8) & 0x03 if ub != prev_ub: # This shouldn't happen addr_10b = 0xFFF # invalid address else: # This shouldn't happen addr_10b = 0xFFF # invalid address na = I2CAddress(f_bound, addr_10b, r_wn) if addr_10b < 0xFFF: addr_text = '{:02X} {}'.format( addr_10b, 'r' if word & 0x01 else 'w') else: # Missing second address byte addr_text = '{:1X}?? {}'.format( addr & 0x03, 'r' if word & 0x01 else 'w') na.annotate('frame', {'value': addr_text}, stream.AnnotationFormat.String) na.subrecords.append( I2CByte(d_bound, word, ack_bit)) na.subrecords[-1].annotate( 'addr', {'_bits': 8}, stream.AnnotationFormat.Hidden) na.subrecords.append( stream.StreamSegment(a_bound, ack_bit, kind='ack', status=a_status)) na.subrecords[-1].annotate( 'ack', {'_bits': 1}, stream.AnnotationFormat.Hidden) yield na bits = [] state = S_DATA else: # 10-bit addressed write: first byte first_addr = I2CByte(d_bound, word, ack_bit) first_ack = stream.StreamSegment( a_bound, ack_bit, kind='ack', status=a_status) bits = [] state = S_ADDR_10B else: # 7-bit address r_wn = word & 0x01 na = I2CAddress(f_bound, addr, r_wn) na.annotate('frame', {}, stream.AnnotationFormat.Hidden) na.subrecords.append( I2CByte(d_bound, word, ack_bit)) addr_text = '{:02X} {}'.format( word >> 1, 'r' if word & 0x01 else 'w') na.subrecords[-1].annotate( 'addr', { 'value': addr_text, '_bits': 8 }, stream.AnnotationFormat.Hex) na.subrecords.append( stream.StreamSegment(a_bound, ack_bit, kind='ack', status=a_status)) na.subrecords[-1].annotate( 'ack', {'_bits': 1}, stream.AnnotationFormat.Hidden) yield na bits = [] state = S_DATA elif state == S_ADDR_10B: # 10-bit address addr = (((first_addr.data >> 1) & 0x03) << 8) | word r_wn = first_addr.data & 0x01 na = I2CAddress( (first_addr.start_time - 0.4 * clock_period, f_bound[1]), addr, r_wn) addr_10b = (( (first_addr.data * 256) >> 1) + word) & 0x3FF addr_text = '{:02X} {}'.format( addr_10b, 'r' if first_addr.data & 0x01 else 'w') na.annotate('frame', {'value': addr_text}, stream.AnnotationFormat.String) na.subrecords.append(first_addr) na.subrecords[-1].annotate( 'addr', {'_bits': 8}, stream.AnnotationFormat.Hidden) na.subrecords.append(first_ack) na.subrecords[-1].annotate( 'ack', {'_bits': 1}, stream.AnnotationFormat.Hidden) na.subrecords.append(I2CByte(d_bound, word, ack_bit)) na.subrecords[-1].annotate( 'addr', {'_bits': 8}, stream.AnnotationFormat.Hidden) na.subrecords.append( stream.StreamSegment(a_bound, ack_bit, kind='ack', status=a_status)) na.subrecords[-1].annotate( 'ack', {'_bits': 1}, stream.AnnotationFormat.Hidden) prev_10b_addr = addr_10b yield na bits = [] state = S_DATA else: # S_DATA nb = I2CByte(f_bound, word, ack_bit) nb.annotate('frame', {}, stream.AnnotationFormat.Hidden) nb.subrecords.append( stream.StreamSegment(d_bound, word, kind='data')) nb.subrecords[-1].annotate('data', {'_bits': 8}) nb.subrecords.append( stream.StreamSegment(a_bound, ack_bit, kind='ack', status=a_status)) nb.subrecords[-1].annotate( 'ack', {'_bits': 1}, stream.AnnotationFormat.Hidden) yield nb bits = []
def sirc_decode(ir_stream, carrier_freq=40.0e3, polarity=ir.IRConfig.IdleLow, logic_levels=None, \ stream_type=stream.StreamType.Samples): '''Decode Sony SIRC infrared protocol This is a generator function that can be used in a pipeline of waveform procesing operations. ir_stream (iterable of SampleChunk objects or (float, int) pairs) A sample stream or edge stream of IR pulses. The type of stream is identified by the stream_type parameter. When this is a sample stream, an initial block of data is consumed to determine the most likely logic levels in the signal. This signal can be either modulated or demodulated. carrier_freq (float) The carrier frequency for modulation. polarity (infrared.IRConfig) Set the polarity (idle state high or low). logic_levels ((float, float) or None) Optional pair that indicates (low, high) logic levels of the sample stream. When present, auto level detection is disabled. This has no effect on edge streams. stream_type (streaming.StreamType) A StreamType value indicating that the ir_stream parameter represents either Samples or Edges. Yields a series of SIRCStreamMessage objects. Raises AutoLevelError if stream_type = Samples and the logic levels cannot be determined. ''' if stream_type == stream.StreamType.Samples: if logic_levels is None: samp_it, logic_levels = decode.check_logic_levels(ir_stream) else: samp_it = ir_stream edges = decode.find_edges(samp_it, logic_levels, hysteresis=0.4) else: # the stream is already a list of edges edges = ir_stream # Demodulate signal (also passes an unmodulated signal without changes) edges = ir.demodulate(edges, carrier_freq, polarity) mod_period = (1.0 / carrier_freq) # Not really important. Just need a default value to pass to EdgeSequence es = decode.EdgeSequence(edges, mod_period) epsilon = 30.0e-6 # allow +/-30us variation for pulses and bit times time_is_nearly = functools.partial(ir.time_is_nearly, epsilon=epsilon) one_t = 600.0e-6 # 600us 1T time while not es.at_end(): # Look for the falling edge of a start pulse if es.cur_state() == 0: es.advance_to_edge() # Now we're 1 ts = es.advance_to_edge() # Now we're 0. Could be end of start pulse # Measure the time we skipped forward by if not time_is_nearly(ts, 4 * one_t): continue # Not a start pulse msg_start_time = es.cur_time - ts msg_bits = [] bit_starts = [] prev_edge = 0.0 # Accumulate bits until idle for too long while True: prev_edge = es.cur_time ts = es.advance_to_edge() if not time_is_nearly(ts, one_t): break # Not the beginning of a bit bit_starts.append(es.cur_time - ts) ts = es.advance_to_edge() if time_is_nearly(ts, one_t): # 0-bit msg_bits.append(0) elif time_is_nearly(ts, 2 * one_t): #1-bit msg_bits.append(1) else: break bit_starts.append(prev_edge) # End of last bit #print('### last bit:', es.cur_time) if len(msg_bits) in (12, 15, 20): cmd = join_bits(reversed(msg_bits[0:7])) cmd_range = (bit_starts[0], bit_starts[7]) if len(msg_bits) == 12 or len(msg_bits) == 20: device = join_bits(reversed(msg_bits[7:12])) device_range = (bit_starts[7], bit_starts[12]) else: # 15-bit command device = join_bits(reversed(msg_bits[7:15])) device_range = (bit_starts[7], bit_starts[15]) extended = None if len(msg_bits) == 20: # 20-bit extended format extended = join_bits(reversed(msg_bits[12:20])) extended_range = (bit_starts[12], bit_starts[20]) msg = SIRCMessage(cmd, device, extended) sm = SIRCStreamMessage((msg_start_time, prev_edge + 0.5*one_t), msg) sm.annotate('frame', {}, stream.AnnotationFormat.Hidden) cmd_ss = stream.StreamSegment((cmd_range[0], cmd_range[1]), cmd, kind='command') sm.subrecords.append(cmd_ss) sm.subrecords[-1].annotate('data', {'_bits':7}) dev_ss = stream.StreamSegment((device_range[0], device_range[1]), device, kind='device') sm.subrecords.append(dev_ss) sm.subrecords[-1].annotate('addr') if len(msg_bits) == 15: sm.subrecords[-1].fields['_bits'] = 8 else: sm.subrecords[-1].fields['_bits'] = 5 if extended is not None: ext_ss = stream.StreamSegment((extended_range[0], extended_range[1]), extended, kind='extended') sm.subrecords.append(ext_ss) sm.subrecords[-1].annotate('data', {'_bits':8}) yield sm
def _ethernet_generic_decode(mstates, tag_ethertypes): '''Decode Manchester states into ethernet frames''' while True: try: cur_edge = next(mstates) except StopIteration: break if cur_edge[1] == ManchesterStates.High: # Possible link test pulse ltp_start = cur_edge[0] while True: try: cur_edge = next(mstates) except StopIteration: break if cur_edge[1] != ManchesterStates.High: if 90.0e-9 < cur_edge[ 0] - ltp_start < 110.0e-9: # Pulse should be nominally 100ns wide # Found a LTP ltp = stream.StreamSegment((ltp_start, cur_edge[0]), kind='LTP') ltp.annotate('misc', {}) yield ltp break continue elif cur_edge[1] not in (0, 1): continue frame_start = cur_edge[0] #print('## frame start:', frame_start) # Get preamble bits get_preamble = True prev_bit = cur_edge[1] preamble_count = 7 * 8 + 6 + 1 # Get alternating 1's and 0's until we see a break in the pattern # that indicates we've reached the SFD. while preamble_count > 0: try: cur_edge = next(mstates) except StopIteration: break if cur_edge[1] != 1 - prev_bit: break prev_bit = cur_edge[1] preamble_count -= 1 # Verify we have the SFD if not (prev_bit == 1 and cur_edge[1] == 1): # Restart search for a frame continue # Move to first bit of frame header try: cur_edge = next(mstates) except StopIteration: break header_start = cur_edge[0] frame_bits = [] bit_start_times = [] # Get all frame bits while cur_edge[1] in (0, 1): frame_bits.append(cur_edge[1]) bit_start_times.append(cur_edge[0]) try: cur_edge = next(mstates) except StopIteration: break crc_end_time = cur_edge[0] # Find end of frame while True: try: cur_edge = next(mstates) except StopIteration: break if cur_edge[1] == ManchesterStates.Idle: break end_time = cur_edge[0] #print('## got frame bits:', len(frame_bits)) # Verify we have a multiple of 8 bits if len(frame_bits) % 8 != 0: continue # Verify we have the minimum of 64 bytes for a frame if len(frame_bits) < 64 * 8: continue # Convert bits to bytes frame_bytes = [] for i in xrange(0, len(frame_bits), 8): frame_bytes.append(join_bits(reversed(frame_bits[i:i + 8]))) byte_start_times = [t for t in bit_start_times[::8]] #print('## got bytes:', ['{:02x}'.format(b) for b in frame_bytes]) # Create frame object if tag_ethertypes is None: tag_ethertypes = [0x8100, 0x88a8, 0x9100] tags = [] lt_start = 12 length_type = frame_bytes[lt_start] * 256 + frame_bytes[lt_start + 1] while length_type in tag_ethertypes: # This is a tag tpid = length_type tci = frame_bytes[lt_start + 2] * 256 + frame_bytes[lt_start + 3] tags.append(EthernetTag(tpid, tci)) lt_start += 4 length_type = frame_bytes[lt_start] * 256 + frame_bytes[lt_start + 1] if len(tags) == 0: # No tags tags = None data_bytes = frame_bytes[lt_start + 2:-4] crc = 0 for b in frame_bytes[-4:]: crc <<= 8 crc += b ef = EthernetFrame(frame_bytes[0:6], frame_bytes[6:12], tags=tags, length_type=length_type, data=data_bytes, crc=crc) status = EthernetStreamStatus.CRCError if not ef.crc_is_valid( ) else stream.StreamStatus.Ok sf = EthernetStreamFrame((frame_start, end_time), ef) # Annotate fields bounds = (byte_start_times[0], byte_start_times[6]) sf.subrecords.append( stream.StreamSegment(bounds, str(ef.dest), kind='dest')) sf.subrecords[-1].annotate('addr', {'_bits': 48}, stream.AnnotationFormat.Small) bounds = (byte_start_times[6], byte_start_times[12]) sf.subrecords.append( stream.StreamSegment(bounds, str(ef.source), kind='source')) sf.subrecords[-1].annotate('addr', {'_bits': 48}, stream.AnnotationFormat.Small) # Tags if tags is not None: for i, t in enumerate(tags): bounds = (byte_start_times[12 + 4 * i], byte_start_times[12 + 4 * i + 4]) sf.subrecords.append( stream.StreamSegment(bounds, 'tag', kind='tag')) sf.subrecords[-1].annotate('ctrl', {}, stream.AnnotationFormat.String) # Ethertype / length bounds = (byte_start_times[lt_start], byte_start_times[lt_start + 2]) length_type = ef.length_type if length_type >= 0x600: kind = 'ethertype' if length_type in ethertypes: value = ethertypes[length_type] else: value = 'Unknown: {:04X}'.format(length_type) text_format = stream.AnnotationFormat.Small else: kind = 'length' value = length_type text_format = stream.AnnotationFormat.Int sf.subrecords.append(stream.StreamSegment(bounds, value, kind=kind)) sf.subrecords[-1].annotate('ctrl', {'_bits': 16}, text_format) # Data bounds = (byte_start_times[lt_start + 2], byte_start_times[-4]) sf.subrecords.append( stream.StreamSegment(bounds, 'Payload, {} bytes'.format(len(data_bytes)), kind='data')) sf.subrecords[-1].annotate('data', {}, stream.AnnotationFormat.String) # CRC bounds = (byte_start_times[-4], crc_end_time) status = EthernetStreamStatus.CRCError if not ef.crc_is_valid( ) else stream.StreamStatus.Ok #print('## CRC bytes:', [hex(b) for b in frame_bytes[-4:]]) sf.subrecords.append( stream.StreamSegment(bounds, frame_bytes[-4:], kind='CRC', status=status)) sf.subrecords[-1].annotate('check', {}, stream.AnnotationFormat.Hex) yield sf
def uart_decode(stream_data, bits=8, parity=None, stop_bits=1.0, lsb_first=True, polarity=UARTConfig.IdleHigh, \ baud_rate=None, use_std_baud=True, logic_levels=None, stream_type=stream.StreamType.Samples, param_info=None): '''Decode a UART data stream This is a generator function that can be used in a pipeline of waveform procesing operations. Sample streams are a sequence of SampleChunk Objects. Edge streams are a sequence of 2-tuples of (time, int) pairs. The type of stream is identified by the stream_type parameter. Sample streams will be analyzed to find edge transitions representing 0 and 1 logic states of the waveforms. With sample streams, an initial block of data is consumed to determine the most likely logic levels in the signal. stream_data (iterable of SampleChunk objects or (float, int) pairs) A sample stream or edge stream representing a serial data signal. bits (int) The number of bits in each word. Typically 5, 7, 8, or 9. parity (string or None) The type of parity to use. One of None, 'even', or 'odd' stop_bits (number) The number of stop bits. Typically 1, 1.5, or 2 lsb_first (bool) Flag indicating whether the Least Significant Bit is transmitted first. inverted (bool) Flag indicating if the signal levels have been inverted from their logical meaning. Use this when the input stream derives from an inverting driver such as those used for RS-232. polarity (UARTConfig) Set the polarity (idle state high or low). baud_rate (int) The baud rate of the stream. If None, the first 50 edges will be analyzed to automatically determine the most likely baud rate for the stream. On average 50 edges will occur after 11 frames have been captured. use_std_baud (bool) Flag that forces coercion of automatically detected baud rate to the set of standard rates logic_levels ((float, float) or None) Optional pair that indicates (low, high) logic levels of the sample stream. When present, auto level detection is disabled. This has no effect on edge streams. stream_type (streaming.StreamType) A StreamType value indicating that the stream parameter represents either Samples or Edges param_info (dict or None) An optional dictionary object that is used to monitor the results of automatic baud detection. Yields a series of UARTFrame objects. Each frame contains subrecords marking the location of sub-elements within the frame (start, data, parity, stop). Parity errors are recorded as an error status in the parity subrecord. BRK conditions are reported as a data value 0x00 with a framing error in the status code. Raises AutoLevelError if stream_type = Samples and the logic levels cannot be determined. Raises AutoBaudError if auto-baud is active and the baud rate cannot be determined. Raises ValueError if the parity argument is invalid. ''' bits = int(bits) if stream_type == stream.StreamType.Samples: if logic_levels is None: samp_it, logic_levels = check_logic_levels(stream_data) else: samp_it = stream_data edges = find_edges(samp_it, logic_levels, hysteresis=0.4) else: # the stream is already a list of edges edges = stream_data raw_symbol_rate = 0 if baud_rate is None: # Find the baud rate # tee off an independent iterator to determine baud rate edges_it, sre_it = itertools.tee(edges) # Experiments on random data indicate that find_symbol_rate() will almost # always converge to a close estimate of baud rate within the first 35 edges. # It seems to be a guarantee after 50 edges (pathological cases not withstanding). min_edges = 50 symbol_rate_edges = itertools.islice(sre_it, min_edges) # We need to ensure that we can pull out enough edges from the iterator slice # Just consume them all for a count sre_list = list(symbol_rate_edges) if len(sre_list) < min_edges: raise AutoBaudError( 'Unable to compute automatic baud rate. Insufficient edges.') raw_symbol_rate = find_symbol_rate(iter(sre_list), spectra=2) if raw_symbol_rate == 0: # Some special data sequences may lack a second harmonic which # ruins the HPS used in find_symbol_rate(). # In this case we bypass the HPS and just take the symbol rate using the dominant span raw_symbol_rate = find_symbol_rate(iter(sre_list), spectra=1) # delete the tee'd iterators so that the internal buffer will not grow # as the edges_it is advanced later on del symbol_rate_edges del sre_it std_bauds = (110, 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, \ 56000, 57600, 115200, 128000, 153600, 230400, 256000, 460800, 921600) if use_std_baud: # find the standard baud closest to the raw rate baud_rate = min(std_bauds, key=lambda x: abs(x - raw_symbol_rate)) else: baud_rate = raw_symbol_rate #print('@@@@@@@@@@ baud rate:', baud_rate, raw_symbol_rate) if baud_rate == 0: raise AutoBaudError( 'Unable to compute automatic baud rate. Got 0.') else: edges_it = edges # Invert edge polarity if idle-low if polarity == UARTConfig.IdleLow: edges_it = ((t, 1 - e) for t, e in edges_it) if param_info is not None: param_info['baud_rate'] = baud_rate param_info['raw_symbol_rate'] = raw_symbol_rate if stream_type == stream.StreamType.Samples: param_info['logic_levels'] = logic_levels bit_period = 1.0 / float(baud_rate) es = EdgeSequence(edges_it, bit_period) # Now we start the actual decode process mark = 1 space = 0 # initialize to point where state is 'mark' --> idle time before first start bit while es.cur_state() == space and not es.at_end(): es.advance_to_edge() #print('start bit', es.cur_time) while not es.at_end(): # look for start bit falling edge es.advance_to_edge() #print('### advance:', es.cur_time) # We could have an anamolous edge at the end of the edge list # Check if edge sequence is complete after our advance if es.at_end(): break # We should be at the start of the start bit (space) # If not then the previous character was likely a BRK condition and # we just now returned to idle (mark). if es.cur_state() != space: continue start_time = es.cur_time data_time = es.cur_time + bit_period es.advance(bit_period * 1.5) # move to middle of first data bit byte = 0 cur_bit = 0 p = 0 if parity is not None: if parity.lower() == 'even': p = 0 elif parity.lower() == 'odd': p = 1 else: raise ValueError('Invalid parity argument') while cur_bit < bits: bit_val = es.cur_state() p ^= bit_val if lsb_first: byte = byte >> 1 | (bit_val << (bits - 1)) else: byte = byte << 1 | bit_val cur_bit += 1 es.advance() #print(es.cur_time) data_end_time = es.cur_time - bit_period * 0.5 parity_error = False if parity is not None: parity_time = data_end_time parity_val = es.cur_state() #print('PB:', p, parity_val) # check the parity if parity_val != p: parity_error = True es.advance() # We are currently 1/2 bit past the last data or parity bit stop_time = es.cur_time - bit_period * 0.5 # Verify the stop bit(s) if stop_bits > 1.0: # Move to within 1/2 bit of the end of the stop bits es.advance(bit_period * (stop_bits - 1.0)) framing_error = False if es.cur_state() != mark: # Not at idle -> break condition framing_error = True end_time = es.cur_time + bit_period * 0.5 # construct frame objects status = UARTStreamStatus.FramingError if framing_error else stream.StreamStatus.Ok nf = UARTFrame((start_time, end_time), byte, status=status) nf.annotate('frame', {}, stream.AnnotationFormat.Hidden) nf.subrecords.append( stream.StreamSegment((start_time, data_time), kind='start bit')) nf.subrecords[-1].annotate('misc', {'_bits': 1}, stream.AnnotationFormat.Invisible) nf.subrecords.append( stream.StreamSegment((data_time, data_end_time), byte, kind='data bits')) nf.subrecords[-1].annotate('data', {'_bits': bits}, stream.AnnotationFormat.General) if parity is not None: status = UARTStreamStatus.ParityError if parity_error else stream.StreamStatus.Ok nf.subrecords.append( stream.StreamSegment((parity_time, stop_time), kind='parity', status=status)) nf.subrecords[-1].annotate('check', {'_bits': 1}, stream.AnnotationFormat.General) nf.subrecords.append( stream.StreamSegment((stop_time, end_time), kind='stop bit')) nf.subrecords[-1].annotate('misc', {'_bits': stop_bits}, stream.AnnotationFormat.Invisible) yield nf
def nec_decode(ir_stream, carrier_freq=38.0e3, polarity=ir.IRConfig.IdleLow, logic_levels=None, \ stream_type=stream.StreamType.Samples): '''Decode NEC infrared protocol This is a generator function that can be used in a pipeline of waveform procesing operations. ir_stream (iterable of SampleChunk objects or (float, int) pairs) A sample stream or edge stream of IR pulses. The type of stream is identified by the stream_type parameter. When this is a sample stream, an initial block of data is consumed to determine the most likely logic levels in the signal. This signal can be either modulated or demodulated. carrier_freq (float) The carrier frequency for modulation. polarity (infrared.IRConfig) Set the polarity (idle state high or low). logic_levels ((float, float) or None) Optional pair that indicates (low, high) logic levels of the sample stream. When present, auto level detection is disabled. This has no effect on edge streams. stream_type (streaming.StreamType) A StreamType value indicating that the ir_stream parameter represents either Samples or Edges. Yields a series of NECStreamMessage objects. Raises AutoLevelError if stream_type = Samples and the logic levels cannot be determined. ''' if stream_type == stream.StreamType.Samples: if logic_levels is None: samp_it, logic_levels = decode.check_logic_levels(ir_stream) else: samp_it = ir_stream edges = decode.find_edges(samp_it, logic_levels, hysteresis=0.4) else: # the stream is already a list of edges edges = ir_stream # Demodulate signal (also passes an unmodulated signal without changes) edges = ir.demodulate(edges, carrier_freq, polarity) mod_period = ( 1.0 / carrier_freq ) # Not really important. Just need a default value to pass to EdgeSequence es = decode.EdgeSequence(edges, mod_period) epsilon = 30.0e-6 # Allow +/-30us variation for pulses and bit times time_is_nearly = functools.partial(ir.time_is_nearly, epsilon=epsilon) time_is_at_least = functools.partial(ir.time_is_at_least, epsilon=epsilon) while not es.at_end(): # Look for the falling edge of an AGC burst if es.cur_state() == 0: es.advance_to_edge() # Now we're 1 es.advance_to_edge() # Now we're 0. Could be end of AGC ts = es.advance_to_edge() # Start of next pulse # Measure the time we skipped forward by if time_is_nearly(ts, 2.25e-3) or time_is_nearly(ts, 4.5e-3): # Previous pulse was AGC (gap between 2.25ms and 4.5ms) msg_start_time = es.cur_time - ts - 9.0e-3 if time_is_at_least(ts, 4.5e-3): # command message msg_bits = [] bit_starts = [] while len(msg_bits) < 32: bit_start_time = es.cur_time ts = es.advance_to_edge() if time_is_nearly(ts, 560.0e-6): # 560us bit pulse time # Measure next time gap to determine if bit is 1 or 0 ts = es.advance_to_edge() bit_period = es.cur_time - bit_start_time if time_is_nearly(bit_period, 2.25e-3): # 1-bit msg_bits.append(1) bit_starts.append(bit_start_time) if time_is_nearly(bit_period, 1.12e-3): # 0-bit msg_bits.append(0) bit_starts.append(bit_start_time) else: break if len(msg_bits) == 32: bit_starts.append(es.cur_time) # End of last byte # Check for the stop bit ts = es.advance_to_edge() if time_is_nearly(ts, 560.0e-6): # 560us stop pulse time # Valid command message msg_bytes = [ join_bits(reversed(msg_bits[i:i + 8])) for i in xrange(0, 32, 8) ] m_bounds = [(bit_starts[i], bit_starts[i + 8]) for i in xrange(0, 32, 8)] addr_low = msg_bytes[0] addr_high = msg_bytes[1] cmd = msg_bytes[2] cmd_inv = msg_bytes[3] nec_msg = NECMessage(cmd, addr_low, addr_high, cmd_inv) sm = NECStreamMessage((msg_start_time, es.cur_time), nec_msg) sm.annotate('frame', {}, stream.AnnotationFormat.Hidden) sm.subrecords.append( stream.StreamSegment( (m_bounds[0][0], m_bounds[0][1]), addr_low, kind='addr-low')) sm.subrecords[-1].annotate('addr', {'_bits': 8}) sm.subrecords.append( stream.StreamSegment( (m_bounds[1][0], m_bounds[1][1]), addr_high, kind='addr-high')) sm.subrecords[-1].annotate('addr', {'_bits': 8}) sm.subrecords.append( stream.StreamSegment( (m_bounds[2][0], m_bounds[2][1]), cmd, kind='cmd')) sm.subrecords[-1].annotate('data', {'_bits': 8}) status = stream.StreamStatus.Ok if cmd == ( ~cmd_inv) & 0xFF else stream.StreamStatus.Error sm.subrecords.append( stream.StreamSegment( (m_bounds[3][0], m_bounds[3][1]), cmd_inv, kind='cmd-inv', status=status)) sm.subrecords[-1].annotate('check', {'_bits': 8}) yield sm else: # repeat message # Check for the stop bit ts = es.advance_to_edge() if time_is_nearly(ts, 560.0e-6): # 560us stop pulse time # Valid repeat message sm = NECStreamMessage((msg_start_time, es.cur_time), NECRepeat()) sm.annotate('frame', {'name': ''}, stream.AnnotationFormat.String) yield sm