Python driver for the Thermalright Trofeo Vision 11.3 LCD USB panel (0416:5408, "USBDISPLAY", 1920x440 framebuffer). The frame protocol was reverse-engineered from USB captures of the vendor's TRCC software and verified byte-for-byte against them. There is no official software for Linux/macOS; this library fills that gap.
From GitHub (no manual cloning needed, pip handles it):
pip install "git+https://github.com/surrounds/trofeo-lcd.git"Line for another project's requirements.txt:
trofeo-lcd @ git+https://github.com/surrounds/trofeo-lcd.git
From a local folder (e.g. for development):
pip install /path/to/trofeo-lcd # or -e for editableDependencies: pyusb, pillow. libusb must be present on the system
(macOS: brew install libusb, Debian/Ubuntu: apt install libusb-1.0-0).
Permissions: on Linux add a udev rule for 0416:5408 (MODE="0666"),
otherwise run as root.
from PIL import Image, ImageDraw
from trofeo_lcd import TrofeoPanel, WIDTH, HEIGHT
import time
img = Image.new("RGB", (WIDTH, HEIGHT), (10, 12, 20))
ImageDraw.Draw(img).text((40, 40), "Hello, panel!", fill=(255, 255, 255))
with TrofeoPanel.open() as panel:
while True: # see "Watchdog" below
panel.send_image(img)
time.sleep(1.0)A resilient loop with recovery:
import time
from trofeo_lcd import TrofeoPanel
panel = None
while True:
try:
if panel is None:
panel = TrofeoPanel.open()
panel.send_image(render()) # your 1920x440 PIL image
except Exception as e:
print(f"error: {e}")
if panel is not None:
panel.recover(hard=True)
panel.close()
panel = None
time.sleep(2)
time.sleep(1.0)TrofeoPanel.open(quiet=False)- find and open the panel (context manager, closes itself)panel.send_image(pil_img, quality=85)- send a PIL image; resizes to 1920x440/RGB, encodes and pads automaticallypanel.send_frame(jpeg, field)- low level: send a pre-padded JPEGpanel.recover(hard=False)- clear endpoint halts, drain buffers, with hard=True also perform a USB resetpanel.drain_in()- drain heartbeat packets (send_image already does this after every frame)find_device()- locate the device without opening it- Protocol helpers:
frame_field(len),pad_jpeg_com(jpeg),build_chunks(jpeg, field),encode_frame(img), constantsWIDTH, HEIGHT, TARGET_LEN
Non-default VID/PID: environment variables TV_VID / TV_PID.
- Watchdog: without a fresh frame the panel reverts to the factory logo after 1-2 seconds. Send frames in a loop at ~1s intervals.
- The firmware hangs on a malformed stream (write timeout on the first blocks) and only a physical cable replug revives it; a USB reset does not help. Never change the protocol constants or formulas.
- JPEG must be baseline 4:2:0 (encode_frame produces the right bitstream). Progressive or 4:4:4 JPEG hangs the firmware.
- The panel emits a heartbeat packet on the IN endpoint every ~2s; the library drains it after every frame.
- The device needs no handshake, and sending one is harmful: any extra packet breaks the stream alignment.
A frame is 524 blocks of 512 bytes: a 16-byte header
(01 ff + u16 field + a constant + u32 block number) followed by
496 bytes of JPEG data. Transport slicing: bulk writes of 65x4096 plus
one 2048. The JPEG is padded with COM segments up to 258507 bytes (the
length of TRCC's reference frame), which makes the field 0xF1CB. The
field depends only on the JPEG length: frame_field() was recovered
from 19 frames across two independent captures.