I’m trying to reverse engineer the screen on the Epomaker RT100 keyboard, specifically the image uploading component. I used Wireshark to track the packets sent over USB. I found that RGB 565 is being used to send data and there are two 8-bit values that seem to count pixels. The issue is I can’t figure out how the data is being iterated through. It doesn’t seem to be simple rows or columns.
I have gotten to a point where things show up, however they are garbled. When comparing the packets from the code I wrote to the packets from the included software they are different.
The image shows the packets when uploading this image:
The one on the left is from the software the one on the right is mine. How could I find out how this is sending the data to the image.
And just for clarity, this is the code I wrote:
<code>from PIL import Image
def convert_rgb888_to_rgb565(rgb):
return (r << 11) | (g << 5) | b
def encode_image_to_rgb565(image_path):
img = Image.open(image_path)
pixels = list(img.getdata())
encoded = convert_rgb888_to_rgb565(pixel)
encoded_data.append(struct.pack('!H', encoded))
return b''.join(encoded_data)
encoded_image = encode_image_to_rgb565('rainbow.png')
screen = usb.core.find(idVendor=0x3151, idProduct=0x4015) #This is the actual screen, not the hub
raise ValueError('Device not found')
# Send initial control transfer
screen.set_configuration(1)
initial_payload = bytes.fromhex("a5000100f4da008b0000a2ad00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
screen.ctrl_transfer(0x21, 0x09, 0x0300, 2, initial_payload)
screen.ctrl_transfer(0xA1, 0x01, 0x0300, 2, 64) #GET_REPORT
# Header data = 25 00 01 00 __ 00 38 __
# Split and send data in chunks
chunk_size = 56 # Use 56 bytes for chunk to leave room for the 8-byte header
for i in range(0, len(encoded_image), chunk_size):
header = [0x25, 0x00, 0x01, 0x00, y, 0x00, 0x38, x]
chunk = encoded_image[i:(i + chunk_size)]
# Combine header and chunk
combined = bytes(header) + chunk
# Ensure combined is exactly 64 bytes
combined += b'x00' * (64 - len(combined)) # Pad with zeros
#Print the combined packet
print(f'Header: {header}')
print(f'Chunk: {chunk.hex()}')
print(f'Combined: {combined.hex()}')
screen.ctrl_transfer(0x21, 0x09, 0x0300, 2, combined)
usb.util.dispose_resources(screen)
<code>from PIL import Image
import struct
import usb.util
import usb.core
import time
def convert_rgb888_to_rgb565(rgb):
r, g, b, a = rgb
r = (r >> 3) & 0x1F
g = (g >> 2) & 0x3F
b = (b >> 3) & 0x1F
return (r << 11) | (g << 5) | b
def encode_image_to_rgb565(image_path):
img = Image.open(image_path)
img = img.rotate(0)
pixels = list(img.getdata())
encoded_data = []
for pixel in pixels:
encoded = convert_rgb888_to_rgb565(pixel)
encoded_data.append(struct.pack('!H', encoded))
return b''.join(encoded_data)
encoded_image = encode_image_to_rgb565('rainbow.png')
screen = usb.core.find(idVendor=0x3151, idProduct=0x4015) #This is the actual screen, not the hub
if screen is None:
raise ValueError('Device not found')
# Send initial control transfer
screen.set_configuration(1)
initial_payload = bytes.fromhex("a5000100f4da008b0000a2ad00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
screen.ctrl_transfer(0x21, 0x09, 0x0300, 2, initial_payload)
screen.ctrl_transfer(0xA1, 0x01, 0x0300, 2, 64) #GET_REPORT
# Header data = 25 00 01 00 __ 00 38 __
# 0 161 wwww3w
y = 0x00
x = 0xA1
# Split and send data in chunks
chunk_size = 56 # Use 56 bytes for chunk to leave room for the 8-byte header
for i in range(0, len(encoded_image), chunk_size):
header = [0x25, 0x00, 0x01, 0x00, y, 0x00, 0x38, x]
chunk = encoded_image[i:(i + chunk_size)]
# Combine header and chunk
combined = bytes(header) + chunk
# Ensure combined is exactly 64 bytes
if len(combined) < 64:
combined += b'x00' * (64 - len(combined)) # Pad with zeros
#Print the combined packet
print(f'Header: {header}')
print(f'Chunk: {chunk.hex()}')
print(f'Combined: {combined.hex()}')
if x - 1 > -1:
x = int(x) - 1
else:
x = 255
if y + 1 < 256:
y = int(y) + 1
else:
y = 0
print(combined)
screen.ctrl_transfer(0x21, 0x09, 0x0300, 2, combined)
print("Image uploaded.")
usb.util.dispose_resources(screen)
</code>
from PIL import Image
import struct
import usb.util
import usb.core
import time
def convert_rgb888_to_rgb565(rgb):
r, g, b, a = rgb
r = (r >> 3) & 0x1F
g = (g >> 2) & 0x3F
b = (b >> 3) & 0x1F
return (r << 11) | (g << 5) | b
def encode_image_to_rgb565(image_path):
img = Image.open(image_path)
img = img.rotate(0)
pixels = list(img.getdata())
encoded_data = []
for pixel in pixels:
encoded = convert_rgb888_to_rgb565(pixel)
encoded_data.append(struct.pack('!H', encoded))
return b''.join(encoded_data)
encoded_image = encode_image_to_rgb565('rainbow.png')
screen = usb.core.find(idVendor=0x3151, idProduct=0x4015) #This is the actual screen, not the hub
if screen is None:
raise ValueError('Device not found')
# Send initial control transfer
screen.set_configuration(1)
initial_payload = bytes.fromhex("a5000100f4da008b0000a2ad00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
screen.ctrl_transfer(0x21, 0x09, 0x0300, 2, initial_payload)
screen.ctrl_transfer(0xA1, 0x01, 0x0300, 2, 64) #GET_REPORT
# Header data = 25 00 01 00 __ 00 38 __
# 0 161 wwww3w
y = 0x00
x = 0xA1
# Split and send data in chunks
chunk_size = 56 # Use 56 bytes for chunk to leave room for the 8-byte header
for i in range(0, len(encoded_image), chunk_size):
header = [0x25, 0x00, 0x01, 0x00, y, 0x00, 0x38, x]
chunk = encoded_image[i:(i + chunk_size)]
# Combine header and chunk
combined = bytes(header) + chunk
# Ensure combined is exactly 64 bytes
if len(combined) < 64:
combined += b'x00' * (64 - len(combined)) # Pad with zeros
#Print the combined packet
print(f'Header: {header}')
print(f'Chunk: {chunk.hex()}')
print(f'Combined: {combined.hex()}')
if x - 1 > -1:
x = int(x) - 1
else:
x = 255
if y + 1 < 256:
y = int(y) + 1
else:
y = 0
print(combined)
screen.ctrl_transfer(0x21, 0x09, 0x0300, 2, combined)
print("Image uploaded.")
usb.util.dispose_resources(screen)