Example #1
0
    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