Yet another lamp bypass script

I'm tried using the Arduino IDE to program an RP2040 but failed. I am using currently thony with micropython. I purchased some from Amazon that looked sort of cute just to try out microPython. I want to use this post to share what I have so far projector. So far I figured out the connector that sends UART to the lamp. The UART is 2500, 9 bit and I've used a logic analyzer to decode it. It seems to come in a preamble set of back and forth communication followed by a constant 3 byte repeating pattern. I can now read the pattern and send the pattern but I have not been able to do it automatically. I was hoping someone more familiar could help me out with code ideas. Between some help from here and chatgpertee I might be able to bypass the lamp. Details to follow soon.

So I tried to put together a communication system but this doesn't seem to be working. When I remove the lamp the rp2040 seems to clamp the TX RX lines and the motherboard never sends UART requests:

from machine import Pin, UART, Timer
from rp2 import PIO, StateMachine, asm_pio
import utime

UART_BAUD = 2500
PIN_TX = 0
PIN_RX = 1

# Define the input byte sequences and their corresponding responses
input_sequences = {
    (0x0009, 0x0000, 0x00F6): (0x0009, 0x0116, 0x01E0),
    (0x0000, 0x0048, 0x00B7): (0x0000, 0x0000, 0x00FF),
    (0x0101, 0x0000, 0x01FE): (0x0101, 0x0048, 0x01B6),
    (0x0170, 0x0107, 0x0088): (0x0170, 0x0000, 0x018F),
    (0x0011, 0x0101, 0x00ED): (0x0011, 0x0000, 0x00EE),
    (0x01FF,): (0x0102, 0x0104, 0x00F9),
    (0x0102, 0x0000, 0x01FD): (0x0102, 0x0104, 0x00F9),
    (0x0104, 0x0000, 0x01FB): (0x0104, 0x0176, 0x0185),
    (0x0003, 0x0000, 0x00FC): (0x0003, 0x001D, 0x01DF),
    (0x0180, 0x0011, 0x016E): (0x0180, 0x0000, 0x017F),
    (0x0011, 0x0000, 0x00EE): (0x0011, 0x0000, 0x00EE),
    (0x0011, 0x0000): (0x0123, 0x0000, 0x01DC),
}

# Assembly code for UART transmission
@asm_pio(sideset_init=PIO.OUT_HIGH, out_init=PIO.OUT_HIGH, out_shiftdir=PIO.SHIFT_RIGHT)
def uart_tx():
    # Block with TX deasserted until data available
    pull()
    # Initialise bit counter, assert start bit for 8 cycles
    set(x, 8)  .side(0)       [7]
    # Shift out 9 data bits, 9 execution cycles per bit
    label("bitloop")
    out(pins, 1)              [7]
    jmp(x_dec, "bitloop")
    # Assert stop bit for 8 cycles total (incl 1 for pull())
    nop()      .side(1)       [6]

# Initialize StateMachine for UART
sm = StateMachine(
    0, uart_tx, freq=9 * UART_BAUD, sideset_base=Pin(PIN_RX), out_base=Pin(PIN_TX), in_base=Pin(PIN_RX)
)
sm.active(1)

# Function to bit-bang read a 9-bit data frame
def bit_bang_read_9bit(rx_pin):
    # Wait for start bit
    while rx_pin.value() == 1:
        pass
    
    # Wait half of bit time to sample in the middle of the bit
    utime.sleep_us(int(500000 / UART_BAUD))  # Adjust based on baud rate
    
    # Read 9 bits (assuming LSB-first)
    value = 0
    for i in range(9):
        value |= (rx_pin.value() << i)
        utime.sleep_us(int(1000000 / UART_BAUD))  # Adjust based on baud rate
    
    return value

# Main loop for UART reception and response
while True:
    received_value = bit_bang_read_9bit(Pin(PIN_RX))
    print("Received:", hex(received_value))
    utime.sleep_us(400)  # Adjust delay as needed to match transmission rate

    # Check if received value matches any input sequence
    for seq, response in input_sequences.items():
        if received_value == seq[0]:  # Check only the first value of the sequence
            sm.put(response[0])  # Send the first byte of the response
            sm.put(response[1])  # Send the second byte of the response
            sm.put(response[2])  # Send the third byte of the response
            break  # Exit the loop after matching and sending response

Here are some of my observations:

query (motherboard RX) response (lamp TX)
0x0123 0x0000 0x01DC When powered on initially
0x0009 0x0000 0x00F6 0x0009 0x0116 0x01E0 query repeated 3 times if unanswered
0x0000 0x0048 0x00B7 0x0000 0x0000 0x00FF query repeated 3 times if unanswered
0x0101 0x0000 0x01FE 0x0101 0x0048 0x01B6 query not used if first two go unanswered
0x0170 0x0107 0x0088 0x0170 0x0000 0x018F query repeated 3 times if unanswered
0x0011 0x0101 0x00ED 0x0011 0x0000 0x00EE query repeats continuously
0x01FF 0x0102 0x0104 0x00F9 start normal
0x0102 0x0000 0x01FD 0x0102 0x0104 0x00F9 normal
0x0104 0x0000 0x01FB 0x0104 0x0176 0x0185
0x0003 0x0000 0x00FC 0x0003 0x001D 0x01DF
0x0180 0x0011 0x016E 0x0180 0x0000 0x017F
0x0011 0x0000 0x00EE 0x0011 0x0000 0x00EE Lamp Cool down
0x0011 0x0000 0x0123 0x0000 0x01DC Idle
0x01F0 0x0009 0x0000 lamp has failed before
0x00F6 0x0009 0x0000 repeats twice
0x00F6 appears once
0x0000 0x0048 0x00B7 repeats three times
0x0170 0x0107 0x0088 repeats three times
0x0011 0x0101 0x00ED Repeats 6 times

So I changed strategies for a sec. I want to test a unilateral communication where I just send the time sequences. Sending for example just the continuous "0x0102 0x0104 0x00F9" seemed to keep the green status light on for a few more seconds than when there is no light-bulb connected.

from machine import Pin, UART, Timer
from rp2 import PIO, StateMachine, asm_pio
import utime

UART_BAUD = 2500
PIN_TX = 0
PIN_RX = 1

# Define the input byte sequences and their corresponding responses
input_sequences = {
    (0x0009, 0x0000, 0x00F6): (0x0009, 0x0116, 0x01E0),
    (0x0000, 0x0048, 0x00B7): (0x0000, 0x0000, 0x00FF),
    (0x0101, 0x0000, 0x01FE): (0x0101, 0x0048, 0x01B6),
    (0x0170, 0x0107, 0x0088): (0x0170, 0x0000, 0x018F),
    (0x0011, 0x0101, 0x00ED): (0x0011, 0x0000, 0x00EE),
    (0x01FF,): (0x0102, 0x0104, 0x00F9),
    (0x0102, 0x0000, 0x01FD): (0x0102, 0x0104, 0x00F9),
    (0x0104, 0x0000, 0x01FB): (0x0104, 0x0176, 0x0185),
    (0x0003, 0x0000, 0x00FC): (0x0003, 0x001D, 0x01DF),
    (0x0180, 0x0011, 0x016E): (0x0180, 0x0000, 0x017F),
    (0x0011, 0x0000, 0x00EE): (0x0011, 0x0000, 0x00EE),
    (0x0011, 0x0000): (0x0123, 0x0000, 0x01DC),
}

# Assembly code for UART transmission
@asm_pio(sideset_init=PIO.OUT_HIGH, out_init=PIO.OUT_HIGH, out_shiftdir=PIO.SHIFT_RIGHT)
def uart_tx():
    # Block with TX deasserted until data available
    pull()
    # Initialise bit counter, assert start bit for 8 cycles
    set(x, 8)  .side(0)       [7]
    # Shift out 9 data bits, 9 execution cycles per bit
    label("bitloop")
    out(pins, 1)              [7]
    jmp(x_dec, "bitloop")
    # Assert stop bit for 8 cycles total (incl 1 for pull())
    nop()      .side(1)       [6]

# Initialize StateMachine for UART
sm = StateMachine(
    0, uart_tx, freq=9 * UART_BAUD, sideset_base=Pin(PIN_RX), out_base=Pin(PIN_TX), in_base=Pin(PIN_RX)
)
sm.active(1)

        
def send_sequence(seq, repeat=1, delay_ms=90):
    for _ in range(repeat):
        for byte in seq:
            sm.put(byte)
            utime.sleep_us(100)  # Adjust this delay if necessary
        utime.sleep_ms(delay_ms)  # Delay between repetitions

# Main loop for UART reception and response
utime.sleep_ms(2200)  # wait before first sequence
send_sequence(input_sequences[0x0009, 0x0000, 0x00F6], repeat=1, delay_ms=1)
utime.sleep_ms(122)  # wait before first sequence
send_sequence(input_sequences[0x0000, 0x0048, 0x00B7], repeat=1, delay_ms=1)
utime.sleep_ms(120)  # wait before first sequence
send_sequence(input_sequences[0x0101, 0x0000, 0x01FE], repeat=1, delay_ms=1)
utime.sleep_ms(131)  # wait before first sequence
send_sequence(input_sequences[0x0170, 0x0107, 0x0088], repeat=1, delay_ms=1)
utime.sleep_ms(341)  # wait before first sequence
send_sequence(input_sequences[0x0011, 0x0101, 0x00ED], repeat=1, delay_ms=1)
utime.sleep_ms(2100)  # wait before first sequence
send_sequence(input_sequences[0x0102, 0x0000, 0x01FD], repeat=80, delay_ms=90)  # Send the sequence 80 times with 90ms delay
utime.sleep_ms(121)  # wait before first sequence
send_sequence(input_sequences[0x0011, 0x0000], repeat=1, delay_ms=1) # there is actually no request here, it just appears
utime.sleep_ms(44)  # wait before first sequence
send_sequence(input_sequences[0x0102, 0x0000, 0x01FD], repeat=8, delay_ms=378)  # Send the sequence 80 times with 90ms delay
utime.sleep_ms(2114)  # wait before first sequence
send_sequence(input_sequences[0x0180, 0x0011, 0x016E], repeat=1, delay_ms=1)
utime.sleep_ms(218)  # wait before first sequence
send_sequence(input_sequences[0x0102, 0x0000, 0x01FD], repeat=1, delay_ms=1)
utime.sleep_ms(706)  # wait before first sequence
#lightbulb started, now we just repeat the same sequence

while True:
    # Iterate through the input sequences
    #print("Sequence:", ", ".join(f"0x{byte:04X}" for byte in input_sequences[0x0009, 0x0000, 0x00F6]))
    send_sequence(input_sequences[0x0102, 0x0000, 0x01FD], repeat=1, delay_ms=1)
    utime.sleep_ms(373)

Finally, here is an image from the logic analyzer just to give you an idea of the communication. The bottom channel seems to be a clock signal. the top seems to be High only when the lamp is on.... I haven't tried just simply making that pin go high on the rp2040 to see if that makes the thing magically work... it couldn't be that simple right??

The brown signal in CH1 in this case is the request from the motherboard... I think that's the case because it repeats the same request if I disconnect the lamp. The CH2 signal is the lamp. you can see that the lamp responds with 3 bytes next to each other while the motherboard sends 3 bytes with a larger spacing. I don't know why that might be. Maybe I can use some of this signal to trigger the rpi2040 to start sending its signal? I am not 100% sure where to start or well continue with this. I am sort of chatgpt stumbling at this point, thus my post on the forum.

I am puzzled by the high signal on ch0 and the clock on ch3. the clock stabilizes but starts with a weird almost PWM ramping like this:

But the most puzzling part is that the motherboard and lamp query responses often just overrun each other like they were not really correlating... more like just query query..answer, query, answer answer.. not really at random since the two signals are relatively equally spaced from message to message, but maybe not really a query/answer but more like Hey! Hey too! the difference between ping-pong and just ping-ping (do not ask any filipina what a ping ping signal is). Anyway, for that reason I think that just playing the whole communication is probably going to get the job done? but still running into problems. Right now I am trying to get the signal out but the print statements are not resulting the in the correct hex codes. That is why I changed over to bit banging the code out. I will try this soon tonight maybe and then report back. If you got clever ideas to try, I'm all ears.