def test_es(self): edges = [(0.0, 0), (0.5, 1), (2.0, 0), (4.0, 1)] es = decode.EdgeSequence(iter(edges), 1.0) self.assertEqual(es.cur_state(), 0) states = [0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1] for i, s in zip(xrange(20), states): es.advance(0.25) #print('@@@ cs', es.cur_state(), states[i]) self.assertEqual(es.cur_state(), s, 'advance() mismatch') es = decode.EdgeSequence(iter(edges), 1.0) states = [1, 0, 1, 1] i = 0 while not es.at_end(): es.advance_to_edge() #print('cs', es.cur_state(), es.cur_time) self.assertEqual(es.cur_state(), states[i], 'advance_to_edge() mismatch') i += 1
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 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 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 manchester_decode(edges, bit_period, falling=0, combine_bits=False): '''Convert differential coded edges to manchester states edges (iterable of (float, int)) An edge stream of differential states (-1, 0, 1) representing Manchester coded bits. bit_period (float) The period of a single bit. falling (int) The bit encoding for a falling edge. Either 0 or 1. combine_bits (bool) When True, the output states will only be yielded when a different state occurs. When False, a state is yielded for each bit or half-bit. Yields a stream of Manchester states in (float, int) form. ''' def to_manchester(diff_state): '''Utility function to convert differentially coded states to ManchesterStates''' if diff_state == 1: mstate = ManchesterStates.High elif diff_state == -1: mstate = ManchesterStates.Low else: mstate = ManchesterStates.Idle return mstate es = decode.EdgeSequence(edges, bit_period) # Initial state yield (es.cur_time, to_manchester(es.cur_state())) processing_bits = False prev_bit = None while not es.at_end(): if not processing_bits: #state == S_IDLE: es.advance_to_edge() if es.cur_state() in (1, -1): es.advance(bit_period * 0.25) processing_bits = True prev_bit = None else: yield (es.cur_time, ManchesterStates.Idle) else: # processing bits if es.cur_state() not in (1, -1): # Idle yield (es.cur_time - bit_period*0.25, ManchesterStates.Idle) processing_bits = False elif es.next_states[0] - es.cur_time < bit_period * 0.5: # This is a bit if es.next_states[1] == 1: # rising edge bit = 1 - falling elif es.next_states[1] == -1: # falling edge bit = falling else: # Unexpected transition to idle # Create a half bit for this segment yield (es.cur_time - bit_period*0.25, to_manchester(es.cur_state())) es.advance(bit_period * 0.5) continue #print('### got bit 2:', bit, prev_bit, es.cur_time - bit_period*0.25) if not combine_bits or bit != prev_bit: yield (es.cur_time - bit_period*0.25, bit) prev_bit = bit es.advance(es.next_states[0] - es.cur_time + bit_period*0.75) # Resync to next edge else: # This is a half bit #print('## half bit 2', es.cur_time - bit_period*0.25) half_bit = to_manchester(es.cur_state()) if not combine_bits or half_bit != prev_bit: yield (es.cur_time - bit_period*0.25, half_bit) prev_bit = half_bit es.advance(bit_period * 0.5)
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