def rc5_synth(messages,
              idle_start=0.0,
              message_interval=89.0e-3,
              idle_end=1.0e-3):
    '''Generate synthesized RC5 infrared waveforms
    
    This function simulates Philips RC5 IR pulses.

    messages (sequence of RC5Message)
        Commands to be synthesized.
    
    idle_start (float)
        The amount of idle time before the transmission of messages begins.

    message_interval (float)
        The amount of time between messages.
    
    idle_end (float)
        The amount of idle time after the last message.

    Yields an edge stream of (float, int) pairs. The first element in the iterator
      is the initial state of the stream.
    '''

    t = 0.0

    pulse_width = 889.0e-6  # 889us pulse width

    yield (t, 0)  # set initial conditions; idle-low
    t += idle_start

    for msg in messages:
        msg_bits = [1, 0 if (msg.cmd & 0x40) else 1, msg.toggle]
        msg_bits.extend(split_bits(msg.addr, 5))
        msg_bits.extend(split_bits(msg.cmd, 6))

        #print('### msg_bits:', msg_bits)

        coded_bits = ((0, 1) if b else (1, 0) for b in msg_bits
                      )  # Expand each bit into a pair of half bits
        coded_bits = (b for sl in coded_bits for b in sl)  # Flatten the tuples

        prev_state = 0
        for b in coded_bits:
            if b != prev_state:
                yield (t, b)
            t += pulse_width
            prev_state = b

        if prev_state == 1:
            yield (t, 0)

        t += message_interval

    t += idle_end - message_interval

    yield (t, 0)  # Final state
Example #2
0
def rc5_synth(messages, idle_start=0.0, message_interval=89.0e-3, idle_end=1.0e-3):
    '''Generate synthesized RC5 infrared waveforms
    
    This function simulates Philips RC5 IR pulses.

    messages (sequence of RC5Message)
        Commands to be synthesized.
    
    idle_start (float)
        The amount of idle time before the transmission of messages begins.

    message_interval (float)
        The amount of time between messages.
    
    idle_end (float)
        The amount of idle time after the last message.

    Yields an edge stream of (float, int) pairs. The first element in the iterator
      is the initial state of the stream.
    '''

    t = 0.0

    pulse_width = 889.0e-6 # 889us pulse width
    
    yield (t, 0) # set initial conditions; idle-low
    t += idle_start

    for msg in messages:
        msg_bits = [1, 0 if (msg.cmd & 0x40) else 1, msg.toggle]
        msg_bits.extend(split_bits(msg.addr, 5))
        msg_bits.extend(split_bits(msg.cmd, 6))

        #print('### msg_bits:', msg_bits)

        coded_bits = ((0, 1) if b else (1, 0) for b in msg_bits) # Expand each bit into a pair of half bits
        coded_bits = (b for sl in coded_bits for b in sl) # Flatten the tuples

        prev_state = 0
        for b in coded_bits:
            if b != prev_state:
                yield (t, b)
            t += pulse_width
            prev_state = b

        if prev_state == 1:
            yield (t, 0)

        t += message_interval
            
    t += idle_end - message_interval
        
    yield (t, 0) # Final state
def lin_pid(id):
    '''Generate a LIN PID from an ID

    id (int)
        The ID to generate parity for

    Returns the PID as an int.
    '''

    p0 = reduce(operator.xor, split_bits(id & _p0_mask, 6))
    p1 = reduce(operator.xor, split_bits(id & _p1_mask, 6)) ^ 0x01

    return join_bits([p1, p0] + split_bits(id, 6))
Example #4
0
    def bit_stream(self):
        '''Get the sequence of raw bits for the frame.

        This includes the SOF and SFD at the start and the IDL phase at end of frame.
        '''
        for b in [0x55] * 7 + [0xD5]: # SOF + SFD
            for bit in reversed(split_bits(b, 8)):
                yield bit
        for b in self.bytes:
            for bit in reversed(split_bits(b, 8)):
                yield bit
        # IDL = high for 3 bit times -> 6 half-bit times
        for bit in [ManchesterStates.High] * 6:
            yield bit

        yield ManchesterStates.Idle
def vpw_encode(bytes, start_time):
    '''Convert bytes to a VPW edge sequence

    bytes (sequence of int)
        The bytes to encode

    start_time (float)
        The Start time for the first edge

    Returns a list of (float, int) edge pairs.
    '''

    is_passive = 1
    edges = []
    t = start_time
    for byte in bytes:
        for b in split_bits(byte, 8):
            if is_passive:
                edges.append((t, 0))
                pw = 64.0e-6 if b == 0 else 128.0e-6
            else:  # Active
                edges.append((t, 1))
                pw = 64.0e-6 if b == 1 else 128.0e-6

            t += pw
            is_passive = 1 - is_passive

    # Each frame has an even number of bits which guarantees the last bit
    # was in the active state. Thus there should be a falling edge at the
    # current time to end the last bit.
    edges.append((t, 0))

    return edges
def pwm_encode(bytes, start_time, bit_slice):
    '''Convert bytes to a PWM edge sequence

    bytes (sequence of int)
        The bytes to encode

    start_time (float)
        The Start time for the first edge

    bit_slice (float)
        The time for 1/3 of a bit period.

    Returns a list of (float, int) edge pairs.
    '''
    edges = []

    t = start_time
    for byte in bytes:
        for b in split_bits(byte, 8):
            edges.append((t, 1))
            pw = bit_slice if b == 1 else bit_slice * 2
            t += pw
            edges.append((t, 0))
            t += 3 * bit_slice - pw

    edges.append((t, 0))
    return edges
Example #7
0
def vpw_encode(bytes, start_time):
    '''Convert bytes to a VPW edge sequence

    bytes (sequence of int)
        The bytes to encode

    start_time (float)
        The Start time for the first edge

    Returns a list of (float, int) edge pairs.
    '''

    is_passive = 1
    edges = []
    t = start_time
    for byte in bytes:
        for b in split_bits(byte, 8):
            if is_passive:
                edges.append((t, 0))
                pw = 64.0e-6 if b == 0 else 128.0e-6
            else: # Active
                edges.append((t, 1))
                pw = 64.0e-6 if b == 1 else 128.0e-6

            t += pw
            is_passive = 1 - is_passive

    # Each frame has an even number of bits which guarantees the last bit
    # was in the active state. Thus there should be a falling edge at the
    # current time to end the last bit.
    edges.append((t, 0))

    return edges
Example #8
0
def pwm_encode(bytes, start_time, bit_slice):
    '''Convert bytes to a PWM edge sequence

    bytes (sequence of int)
        The bytes to encode

    start_time (float)
        The Start time for the first edge

    bit_slice (float)
        The time for 1/3 of a bit period.

    Returns a list of (float, int) edge pairs.
    '''
    edges = []

    t = start_time
    for byte in bytes:
        for b in split_bits(byte, 8):
            edges.append((t, 1))
            pw = bit_slice if b == 1 else bit_slice * 2
            t += pw
            edges.append((t, 0))
            t += 3*bit_slice - pw

    edges.append((t, 0))
    return edges
Example #9
0
    def edges(self, bit_period):
        '''Get the edges for this object

        bit_period (float)
            The period of a single bit.

        Returns a list of (float, int) edges representing the pulse(s) for this object
        '''
        if self.link_code is None:
            return [(0.0, 1), (bit_period, ManchesterStates.Idle),
                    (2 * bit_period, ManchesterStates.Idle)]

        else:
            #print('## code word:', '{:016b}'.format(self.link_code.word))
            code_bits = reversed(split_bits(self.link_code.word, 16))

            edges = []
            t = 0.0
            for b in code_bits:
                edges.extend([(t, 1), (t + bit_period, ManchesterStates.Idle)])
                if b == 1:
                    t += 62.5e-6
                    edges.extend([(t, 1),
                                  (t + bit_period, ManchesterStates.Idle)])
                    t += 62.5e-6
                else:  # 0
                    t += 125.0e-6

            # Last framing pulse
            edges.extend([(t, 1), (t + bit_period, ManchesterStates.Idle),
                          (t + 2 * bit_period, ManchesterStates.Idle)])

            return edges
Example #10
0
    def bit_stream(self):
        '''Get the sequence of raw bits for the frame.

        This includes the SOF and SFD at the start and the IDL phase at end of frame.
        '''
        for b in [0x55] * 7 + [0xD5]:  # SOF + SFD
            for bit in reversed(split_bits(b, 8)):
                yield bit
        for b in self.bytes:
            for bit in reversed(split_bits(b, 8)):
                yield bit
        # IDL = high for 3 bit times -> 6 half-bit times
        for bit in [ManchesterStates.High] * 6:
            yield bit

        yield ManchesterStates.Idle
Example #11
0
    def edges(self, bit_period):
        '''Get the edges for this object

        bit_period (float)
            The period of a single bit.

        Returns a list of (float, int) edges representing the pulse(s) for this object
        '''
        if self.link_code is None:
            return [(0.0, 1), (bit_period, ManchesterStates.Idle), (2*bit_period, ManchesterStates.Idle)]

        else:
            #print('## code word:', '{:016b}'.format(self.link_code.word))
            code_bits = reversed(split_bits(self.link_code.word, 16))

            edges = []
            t = 0.0
            for b in code_bits:
                edges.extend([(t, 1), (t + bit_period, ManchesterStates.Idle)])
                if b == 1:
                    t += 62.5e-6
                    edges.extend([(t, 1), (t + bit_period, ManchesterStates.Idle)])
                    t += 62.5e-6
                else: # 0
                    t += 125.0e-6

            # Last framing pulse
            edges.extend([(t, 1), (t + bit_period, ManchesterStates.Idle), (t + 2*bit_period, ManchesterStates.Idle)])


            return edges
def _get_drive_cycle_status(a, b, c, d):
    '''Decode response for sid 0x01, pid 0x41'''

    test_available = [bool(v) for v in split_bits(b & 0x0F, 4)]
    test_incomplete = [bool(v) for v in split_bits((b & 0xF0) >> 4, 4)]
    b_tests = ['misfire', 'fuel system', 'components', 'reserved_in_b']

    tests = {}
    for tname, ta, tc in zip(b_tests, test_available, test_incomplete):
        tests[tname] = (ta, tc)

    test_available = [bool(v) for v in split_bits(c, 8)]
    test_incomplete = [bool(v) for v in split_bits(d, 8)]

    spark_tests = ['catalyst', 'heated catalyst', 'evap. system', \
        'secondary air system', 'A/C refrigerant', 'oxygen sensor', \
        'oxygen sensor heater', 'EGR system']

    for tname, ta, tc in zip(spark_tests, test_available, test_incomplete):
        tests[tname] = (ta, tc)

    return tests
Example #13
0
def _get_drive_cycle_status(a, b, c, d):
    '''Decode response for sid 0x01, pid 0x41'''

    test_available = [bool(v) for v in split_bits(b & 0x0F, 4)]
    test_incomplete = [bool(v) for v in split_bits((b & 0xF0) >> 4, 4)]
    b_tests = ['misfire', 'fuel system', 'components', 'reserved_in_b']

    tests = {}
    for tname, ta, tc in zip(b_tests, test_available, test_incomplete):
        tests[tname] = (ta, tc)


    test_available = [bool(v) for v in split_bits(c, 8)]
    test_incomplete = [bool(v) for v in split_bits(d, 8)]
    
    spark_tests = ['catalyst', 'heated catalyst', 'evap. system', \
        'secondary air system', 'A/C refrigerant', 'oxygen sensor', \
        'oxygen sensor heater', 'EGR system']

    for tname, ta, tc in zip(spark_tests, test_available, test_incomplete):
        tests[tname] = (ta, tc)
    
    return tests
def _get_supported_pids(offset, a, b, c, d):
    '''Decode the response to SID 0x01 PID 0x00, 0x20, 0x40, 0x60 requests

    offset (int)
        The offset for the PID (0x00, 0x20, etc.)

    a,b,c,d (sequence of ints)
        The four bytes of the response

    Returns a sequence of integers for each suported PID.
    '''
    merged_code = ((a * 256 + b) * 256 + c) * 256 + d
    pid_bits = split_bits(merged_code, 32)

    pids = []
    for i, b in enumerate(pid_bits):
        if b == 1:
            pids.append(i + 1 + offset)

    return pids
Example #15
0
def _get_supported_pids(offset, a, b, c, d):
    '''Decode the response to SID 0x01 PID 0x00, 0x20, 0x40, 0x60 requests

    offset (int)
        The offset for the PID (0x00, 0x20, etc.)

    a,b,c,d (sequence of ints)
        The four bytes of the response

    Returns a sequence of integers for each suported PID.
    '''
    merged_code = ((a*256 + b)*256 +c)*256 + d
    pid_bits = split_bits(merged_code, 32)

    pids = []
    for i, b in enumerate(pid_bits):
        if b == 1:
            pids.append(i+1+offset)

    return pids
Example #16
0
    def test_usb_crc16(self):
        import ripyl.util.bitops as bitops
        self.test_name = 'USB CRC-16'
        self.trial_count = 1000
        for i in xrange(self.trial_count):
            self.update_progress(i+1)

            data_size = random.randint(1,30)
            data = [random.randint(0,255) for _ in xrange(data_size)]

            b_data = []
            for d in data:
                b_data.extend(reversed(bitops.split_bits(d, 8)))

            crc16 = bitops.join_bits(reversed(usb.usb_crc16(b_data)))
            tcrc16 = bitops.join_bits(reversed(usb.table_usb_crc16(data)))

            if crc16 != tcrc16:
                print('\nMismatch: {}, {}, {},  {}'.format(hex(crc16), hex(tcrc16), hex(ncrc16), data))

            self.assertEqual(crc16, tcrc16, 'CRC-16 mismatch')
    def test_usb_crc16(self):
        import ripyl.util.bitops as bitops
        self.test_name = 'USB CRC-16'
        self.trial_count = 1000
        for i in xrange(self.trial_count):
            self.update_progress(i + 1)

            data_size = random.randint(1, 30)
            data = [random.randint(0, 255) for _ in xrange(data_size)]

            b_data = []
            for d in data:
                b_data.extend(reversed(bitops.split_bits(d, 8)))

            crc16 = bitops.join_bits(reversed(usb.usb_crc16(b_data)))
            tcrc16 = bitops.join_bits(reversed(usb.table_usb_crc16(data)))

            if crc16 != tcrc16:
                print('\nMismatch: {}, {}, {},  {}'.format(
                    hex(crc16), hex(tcrc16), hex(ncrc16), data))

            self.assertEqual(crc16, tcrc16, 'CRC-16 mismatch')
Example #18
0
def _get_status(a, b, c, d):
    '''Decode response for sid 0x01, pid 0x01'''
    r = {}
    r['DTC count'] = a & 0x7F
    r['MIL status'] = True if a & 0x80 else False

    r['spark ignition'] = False if b & 0x08 else True
    test_available = [bool(v) for v in split_bits(b & 0x07, 3)]
    test_incomplete = [bool(v) for v in split_bits((b & 0x70) >> 4, 3)]
    common_tests = ['misfire', 'fuel system', 'components']
    tests = {}
    for tname, ta, tc in zip(common_tests, test_available, test_incomplete):
        #print('## test', tname, ta, tc)
        tests[tname] = (ta, tc)

    if r['spark ignition']:
        spark_tests = ['catalyst', 'heated catalyst', 'evap. system', \
            'secondary air system', 'A/C refrigerant', 'oxygen sensor', \
            'oxygen sensor heater', 'EGR system']
        test_available = [bool(v) for v in split_bits(c, 8)]
        test_incomplete = [bool(v) for v in split_bits(d, 8)]
        #print('## ta', test_available, test_complete)

        for tname, ta, tc in zip(spark_tests, test_available, test_incomplete):
            #print('## test', tname, ta, tc)
            tests[tname] = (ta, tc)
        
    else: #compression
        compression_tests = [('NMHC cat', 0), ('NOx/SCR meter', 1), ('boost pressure', 3), \
            ('exhaust gas sensor', 5), ('PM filter monitoring', 6), ('EGR and/or VVT system', 7)]


        test_available = [bool(v) for v in reversed(split_bits(c, 8))]
        test_incomplete = [bool(v) for v in reversed(split_bits(d, 8))]

        for tname, i in compression_tests:
            #print('## test', tname, test_available[i], test_incomplete[i])
            tests[tname] = (test_available[i], test_incomplete[i])
    
    r['tests'] = tests

    return r
def _get_status(a, b, c, d):
    '''Decode response for sid 0x01, pid 0x01'''
    r = {}
    r['DTC count'] = a & 0x7F
    r['MIL status'] = True if a & 0x80 else False

    r['spark ignition'] = False if b & 0x08 else True
    test_available = [bool(v) for v in split_bits(b & 0x07, 3)]
    test_incomplete = [bool(v) for v in split_bits((b & 0x70) >> 4, 3)]
    common_tests = ['misfire', 'fuel system', 'components']
    tests = {}
    for tname, ta, tc in zip(common_tests, test_available, test_incomplete):
        #print('## test', tname, ta, tc)
        tests[tname] = (ta, tc)

    if r['spark ignition']:
        spark_tests = ['catalyst', 'heated catalyst', 'evap. system', \
            'secondary air system', 'A/C refrigerant', 'oxygen sensor', \
            'oxygen sensor heater', 'EGR system']
        test_available = [bool(v) for v in split_bits(c, 8)]
        test_incomplete = [bool(v) for v in split_bits(d, 8)]
        #print('## ta', test_available, test_complete)

        for tname, ta, tc in zip(spark_tests, test_available, test_incomplete):
            #print('## test', tname, ta, tc)
            tests[tname] = (ta, tc)

    else:  #compression
        compression_tests = [('NMHC cat', 0), ('NOx/SCR meter', 1), ('boost pressure', 3), \
            ('exhaust gas sensor', 5), ('PM filter monitoring', 6), ('EGR and/or VVT system', 7)]

        test_available = [bool(v) for v in reversed(split_bits(c, 8))]
        test_incomplete = [bool(v) for v in reversed(split_bits(d, 8))]

        for tname, i in compression_tests:
            #print('## test', tname, test_available[i], test_incomplete[i])
            tests[tname] = (test_available[i], test_incomplete[i])

    r['tests'] = tests

    return r
def nec_synth(messages,
              idle_start=0.0,
              message_interval=42.5e-3,
              idle_end=1.0e-3):
    '''Generate synthesized NEC Infrared waveforms
    
    This function simulates NEC IR pulses.

    messages (sequence of NECMessage)
        Commands to be synthesized.
    
    idle_start (float)
        The amount of idle time before the transmission of messages begins.

    message_interval (float)
        The amount of time between messages.
    
    idle_end (float)
        The amount of idle time after the last message.

    Yields an edge stream of (float, int) pairs. The first element in the iterator
      is the initial state of the stream.
    '''

    t = 0.0
    irtx = 0  # idle-low

    yield (t, irtx)  # set initial conditions; idle-low
    t += idle_start

    for msg in messages:
        irtx = 1  # start AGC burst
        yield (t, irtx)

        t += 9.0e-3  # 9ms
        irtx = 0
        yield (t, irtx)

        if msg.cmd == -1 and msg.addr_low == -1:  # this is a repeat message
            t += 2.25e-3  # 2.25ms

        else:  # command message
            t += 4.5e-3  # 4.5ms

            msg_bytes = (split_bits(msg.addr_low, 8), split_bits(msg.addr_high, 8), \
                split_bits(msg.cmd, 8), split_bits(~msg.cmd, 8))

            msg_bits = [bit for b in msg_bytes for bit in reversed(b)]

            for bit in msg_bits:
                # output initial 560us pulse
                irtx = 1
                yield (t, irtx)

                t += 560.0e-6
                irtx = 0
                yield (t, irtx)

                if bit == 1:
                    t += 2.25e-3 - 560.0e-6
                else:
                    t += 1.12e-3 - 560.0e-6

        # output stop pulse
        irtx = 1
        yield (t, irtx)

        t += 560.0e-6
        irtx = 0
        yield (t, irtx)

        t += message_interval

    t += idle_end - message_interval

    yield (t, irtx)
def rc6_synth(messages, idle_start=0.0, message_interval=89.0e-3, idle_end=1.0e-3):
    '''Generate synthesized RC6 infrared waveforms
    
    This function simulates Philips RC6 IR pulses.

    messages (sequence of RC6Message)
        Commands to be synthesized.
    
    idle_start (float)
        The amount of idle time before the transmission of messages begins.

    message_interval (float)
        The amount of time between messages.
    
    idle_end (float)
        The amount of idle time after the last message.

    Yields an edge stream of (float, int) pairs. The first element in the iterator
      is the initial state of the stream.
    '''

    t = 0.0

    pulse_width = 444.0e-6 # 444us pulse width
    
    yield (t, 0) # set initial conditions; idle-low
    t += idle_start

    for msg in messages:
        msg_bits = [1] # Start bit
        msg_bits.extend(split_bits(msg.mode, 3))
        msg_bits.extend([2, 2]) # Place-holders for the toggle bit
        if msg.customer is not None and msg.mode == 6:
            if msg.customer > 127:
                msg_bits.append(1) # 15-bit customer
                msg_bits.extend(split_bits(msg.customer, 15))
            else:
                msg_bits.append(0) # 7-bit customer
                msg_bits.extend(split_bits(msg.customer, 7))
            
        msg_bits.extend(split_bits(msg.addr, 8))
        msg_bits.extend(split_bits(msg.cmd, 8))

        #print('\n### synth msg_bits:', msg_bits)

        coded_bits = ((1, 0) if b else (0, 1) for b in msg_bits) # Expand each bit into a pair of half bits
        coded_bits = [b for sl in coded_bits for b in sl] # Flatten the tuples
        coded_bits[8:12] = (1, 1, 0, 0) if msg.toggle else (0, 0, 1, 1) # Add toggle bit

        #print('### synth coded_bits:', coded_bits)
        coded_bits = [1, 1, 1, 1, 1, 1, 0, 0] + coded_bits # Add AGC leader

        prev_state = 0
        for b in coded_bits:
            if b != prev_state:
                yield (t, b)
            t += pulse_width
            prev_state = b

        if prev_state == 1:
            yield (t, 0)

        t += message_interval
            
    t += idle_end - message_interval
        
    yield (t, 0) # Final state
Example #22
0
def nec_synth(messages, idle_start=0.0, message_interval=42.5e-3, idle_end=1.0e-3):
    '''Generate synthesized NEC Infrared waveforms
    
    This function simulates NEC IR pulses.

    messages (sequence of NECMessage)
        Commands to be synthesized.
    
    idle_start (float)
        The amount of idle time before the transmission of messages begins.

    message_interval (float)
        The amount of time between messages.
    
    idle_end (float)
        The amount of idle time after the last message.

    Yields an edge stream of (float, int) pairs. The first element in the iterator
      is the initial state of the stream.
    '''

    t = 0.0
    irtx = 0 # idle-low
    
    yield (t, irtx) # set initial conditions; idle-low
    t += idle_start

    for msg in messages:
        irtx = 1 # start AGC burst
        yield (t, irtx)

        t += 9.0e-3 # 9ms
        irtx = 0
        yield (t, irtx)

        if msg.cmd == -1 and msg.addr_low == -1: # this is a repeat message
            t += 2.25e-3 # 2.25ms

        else: # command message
            t += 4.5e-3 # 4.5ms

            msg_bytes = (split_bits(msg.addr_low, 8), split_bits(msg.addr_high, 8), \
                split_bits(msg.cmd, 8), split_bits(~msg.cmd, 8))

            msg_bits = [bit for b in msg_bytes for bit in reversed(b)]
            
            for bit in msg_bits:
                # output initial 560us pulse
                irtx = 1
                yield (t, irtx)

                t += 560.0e-6
                irtx = 0
                yield (t, irtx)

                if bit == 1:
                    t += 2.25e-3 - 560.0e-6
                else:
                    t += 1.12e-3 - 560.0e-6

        # output stop pulse
        irtx = 1
        yield (t, irtx)

        t += 560.0e-6
        irtx = 0
        yield (t, irtx)

        t += message_interval

    t += idle_end - message_interval
        
    yield (t, irtx)
def sirc_synth(messages, idle_start=0.0, message_interval=42.5e-3, idle_end=1.0e-3):
    '''Generate synthesized Sony SIRC Infrared waveforms
    
    This function simulates SIRC IR pulses.

    messages (sequence of SIRCMessage)
        Commands to be synthesized.
    
    idle_start (float)
        The amount of idle time before the transmission of messages begins.

    message_interval (float)
        The amount of time between messages.
    
    idle_end (float)
        The amount of idle time after the last message.

    Yields an edge stream of (float, int) pairs. The first element in the iterator
      is the initial state of the stream.
    '''

    t = 0.0
    irtx = 0 # idle-low

    one_t = 600.0e-6 # 600us 1T time
    
    yield (t, irtx) # set initial conditions
    t += idle_start

    for msg in messages:

        msg_bits = list(reversed(split_bits(msg.cmd, 7)))
        if msg.device < 2**5 or msg.extended is not None:
            msg_bits.extend(reversed(split_bits(msg.device, 5)))
        else: # 15-bit command with 8-bit device field
            msg_bits.extend(reversed(split_bits(msg.device, 8)))

        if msg.extended is not None:
            msg_bits.extend(reversed(split_bits(msg.extended, 8)))
        

        irtx = 1 # start pulse burst
        yield (t, irtx)

        t += 4 * one_t # 4T pulse
        irtx = 0
        yield (t, irtx)

        for bit in msg_bits:
            t += one_t # 1T space
            irtx = 1
            yield (t, irtx)

            t += (bit+1) * one_t # 1T or 2T pulse
            irtx = 0
            yield (t, irtx)

        t += one_t # 1T space
        t += message_interval
            
    t += idle_end - message_interval
        
    yield (t, irtx) # Final state