Test in the browser
Beta · WebUSBPlug the display in via USB-C, connect, send the test pattern. The browser asks once which device it may use. Linux needs the udev rule (see guide), Windows a WinUSB driver via Zadig, macOS nothing.
Without a connection only the preview is rendered.
What gets sent is a JPEG of 464×1920, i.e. rotated by 90° and padded to a multiple of 8. Models: 2.8" round, 4.6", 5.2", 8.0", 8.8", 9.2" ✓, 12.3". Detected via the USB product ID.
What this is about
The TURZX displays (identical to "Turing Smart Screen", USB vendor ID 0x1CBE) are slim IPS screens that get power and picture over USB-C and are meant as PC status displays. The vendor software is Windows-only. If you want to hang the display off a Raspberry Pi, a Linux server or a Mac, you have to speak the protocol yourself. It is simple: a 500-byte command header, DES-encrypted with a fixed key, followed by a JPEG. The details are below, including the five things that are guaranteed to go wrong on the first attempt.
All of this is verified on the 9.2" model (1920×462, product ID 0x0092), which has been running as a dashboard on a Pi 5 for months. For the sibling models (4.6", 5.2", 8", 8.8", 12.3") the turing-smart-screen-python library uses the same protocol; only the resolution differs.
Quick start on Linux / Raspberry Pi
# 1. Check that the display is detected
lsusb | grep 1cbe
# Bus 002 Device 002: ID 1cbe:0092 Luminary Micro Inc. TURZX1.0
# 2. Access without root: udev rule, then re-plug the cable
sudo curl -o /etc/udev/rules.d/99-turzx.rules https://bergclimber.de/turzx/dl/99-turzx.rules
sudo udevadm control --reload-rules && sudo udevadm trigger
# 3. Dependencies and script
pip3 install pyusb pillow pycryptodome # Debian/RPi OS may need --break-system-packages
curl -o turzx.py https://bergclimber.de/turzx/dl/turzx.py
# 4. Test pattern
python3 turzx.py --brightness 40 test
python3 turzx.py image photo.jpg
python3 turzx.py text "Hello\nWorld"On a Mac, brew install libusb before the pip step is enough; there is no udev there. On Windows, pyusb needs a WinUSB driver for the device (Zadig), but that is not the path this page is written for.
The five traps
1. The init sequence is mandatory
Without commands 111, 112, 13 and 41 after connecting, your images are composited semi-transparently over the factory theme. Order: 10 (sync) → 111 (bulk video mode, disables theme layer 1) → 112 (partial mode off, theme layer 2) → 13 (clear cache) → 41 (raw image mode, no parameters).
2. Always JPEG, never PNG
PNG (command 102) is composited by the display as an overlay with alpha channel; old content shows through. JPEG (command 101) overwrites the framebuffer completely.
3. 464 instead of 462 pixels
The 9.2" panel has 462 pixels on the short side, but JPEG works in 8×8 blocks. 462 pixels produce artefacts on the left edge, 468 or 480 on the right. With 464 (one black pixel top and bottom) both sides are clean. In general: pad the short side to the next multiple of 8.
4. Rotate by 90°
The panel is natively portrait (462×1920); in the case it sits landscape. The landscape image has to be rotated 90° clockwise (PIL: ROTATE_270), otherwise everything is upside down or sideways.
5. Ghosting after restart
After restarting your own service the old content stays in the display buffer. Send a black JPEG right after the init sequence. If a ghost image persists: unplug the USB cable, wait five seconds, plug it back in.
If it still looks odd
- Factory background shows through: init sequence missing or PNG instead of JPEG. Restart the service, unplug the cable if needed.
- Image stacked four times: command 41 was sent with parameters (e.g. MADCTL). 41 must go out without parameters.
- "Resource busy": another process holds the USB interface. Stop the old instance, wait a moment.
- Nothing at all: try another cable or port; some USB-C cables carry power only. The display draws up to 1.6 W.
The Python script
turzx.py is a single script with no configuration: it detects the model via the product ID, pads and rotates the image accordingly and speaks only the five init commands, brightness and JPEG upload. Dependencies: pyusb, pillow, pycryptodome. Playwright only for the web page mode.
turzx.py test test pattern
turzx.py image photo.jpg [--fit contain] image (default: fill and crop)
turzx.py text "line 1\nline 2" text card
turzx.py clear black
turzx.py brightness 40 brightness 0–100
turzx.py url http://localhost:3000/ web page as screenshot loop, --interval 2
(pip3 install playwright && playwright install chromium)As a service (dashboard use)
For a dashboard, the url mode renders any web page in headless Chromium and pushes a screenshot to the display every two seconds. A Pi 5 is practically idle doing this (screenshot ~0.3 s, USB ~0.2 s, ~30 MB RAM). Example unit: turzx.service, udev rule: 99-turzx.rules.
sudo mkdir -p /opt/turzx && sudo cp turzx.py /opt/turzx/
sudo curl -o /etc/systemd/system/turzx.service https://bergclimber.de/turzx/dl/turzx.service
sudo nano /etc/systemd/system/turzx.service # adjust User= and URL
sudo systemctl daemon-reload && sudo systemctl enable --now turzx
journalctl -u turzx -fTwo lessons from continuous operation: do not load the page with wait_until="networkidle" if the page polls on its own (the loop then hangs in timeouts; we had a display frozen for twelve hours). And do not reload per frame, only take screenshots; a reload every few minutes as hygiene is enough. For full robustness, give the service a systemd watchdog.
Protocol reference
Plain USB bulk on interface 0, one OUT and one IN endpoint. Every command is a 512-byte packet; for images the JPEG payload follows in the same transfer. The display answers with 512 bytes, byte 1 or byte 8 = 0xC8 (200) means OK; you can read and discard the reply.
Packet layout
Plaintext header, 500 bytes, zero-filled:
[0] command ID
[2..3] magic 0x1A 0x6D
[4..7] timestamp: milliseconds since local midnight, little-endian
[8..11] only for 101/102: payload size, big-endian
[8] only for 14: brightness 0–102
→ DES-CBC encrypt, key = IV = "slv3tuzx" (zero padding to 504 bytes)
→ place in 512 bytes, bytes 510/511 = 0xA1 0x1A
→ then for 101: append the raw JPEG bytesCommands
| ID | Meaning | Parameters |
|---|---|---|
| 10 | Sync / handshake | — |
| 13 | Clear cache | — |
| 14 | Brightness | byte 8 = 0–102 (maps to 0–100 %) |
| 41 | Raw image mode | send without parameters |
| 101 | JPEG upload | bytes 8–11 = size, big-endian; JPEG follows right after the 512-byte packet |
| 102 | PNG upload | do not use (alpha compositing) |
| 111 | Bulk video mode on | — (disables factory theme) |
| 112 | Partial mode off | — |
Init sequence and image pipeline
Connect: 10 → 111 → 112 → 13 → 14 (brightness) → 41 → black JPEG
Image (9.2"): landscape 1920×462
→ pad to 1920×464 (1 px black top and bottom)
→ rotate 90° clockwise (PIL ROTATE_270) → 464×1920
→ JPEG, quality 90, typically 70–150 KB
→ header 101 with size + JPEG in one bulk writeModels (vendor ID 0x1CBE)
| Product ID | Model | native (portrait) | JPEG size | Status |
|---|---|---|---|---|
| 0x0028 | 2.8" round | 480×480 | 480×480 | per library, untested here |
| 0x0046 | 4.6" | 320×960 | 320×960 | per library, untested here |
| 0x0050 | 5.2" | 720×1280 | 720×1280 | per library, untested here |
| 0x0080 | 8.0" | 800×1280 | 800×1280 | per library, untested here |
| 0x0088 | 8.8" | 480×1920 | 480×1920 | per library, untested here |
| 0x0092 | 9.2" | 462×1920 | 464×1920 | verified on hardware |
| 0x0123 | 12.3" | 720×1920 | 720×1920 | per library, untested here |
Source of the protocol knowledge: my own captures plus the library turing-smart-screen-python (GPL-3.0), which now supports these models as "Turing USB" and additionally does H.264 video, file storage and the factory theme. If you want the full monitoring program, use the library. If you just want an image or a web page on the display, the eight commands above are all you need.
Frequently asked questions
Does the TURZX display work with a Raspberry Pi?
Yes. The 9.2" model runs here on a Pi 5 as a dashboard around the clock. All you need is Python 3 with pyusb, pillow and pycryptodome plus a udev rule for access without root. For the web page mode additionally Playwright with Chromium.
Is there TURZX software for Linux or Mac?
No, the vendor only ships Windows software. The script on this page and the turing-smart-screen-python library replace it for images, text and web pages. The themes and animations of the Windows app are not available this way.
The display shows my image half transparent over the factory picture. Why?
The init sequence is missing (commands 111, 112, 13, 41) or a PNG was sent instead of a JPEG. See "The five traps".
Why 464 pixels when the display has 462?
JPEG encodes in 8×8 blocks. With 462 pixels the last block is incomplete and the display renders a stripe at the edge. 464 pixels with one black pixel top and bottom are clean.
Which USB-C cable?
One that carries data, not just power. The display needs up to 1.6 W; a USB 2 port is enough. It then shows up as "Luminary Micro Inc. TURZX1.0" in lsusb.
Can I use the display as a second monitor?
Not as a real monitor: there is no screen extension, only image uploads over USB. At roughly two frames per second it is fine for dashboards, status displays and photos, not for video or mouse interaction.
What happens in the browser tool if I connect a different model?
It detects the product ID and picks the resolution from the table. According to the library the protocol is the same, but only the 9.2" model has been checked here. Feedback on other models is welcome.
Is any data transmitted or stored?
No. The browser tool renders and sends everything locally via WebUSB. The page loads no third-party scripts and sets no cookies.