mathoudebine / mathoudebine/turing-smart-screen-python

Rev C (5", chs_5inch): cdc_acm serial path jams after one frame — proposal for an opt-in libusb transport (Linux)

Open
#1,075 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rich Text Format
Stars
2.3k
Forks
412
Avg merge
21h 10m
Merged PRs (30d)
7

Description

Summary

On Linux, a Turing 5" revision C display (chs_5inch.dev1_rom1.88) cannot be
driven through the stock pyserial/cdc_acm path: the bulk OUT endpoint accepts
exactly one ~250-byte frame and then jams until a USB reset. The identical
endpoints work perfectly when driven with raw libusb bulk transfers. I'd like to
propose an opt-in, Linux-gated libusb transport for rev C (pyserial stays the
default everywhere), and I'm opening this first to gauge interest and preferred
shape.

This is tested on a single unit, and the proposal deliberately changes no
defaults — see the caveats and open questions at the end.

Environment

  • Display: Turing 5" rev C, ID chs_5inch.dev1_rom1.88
    • awake: 1d6b:0106 "Android", serial 20080411/dev/ttyACM1
    • asleep: 1a86:ca21 "UsbMonitor", serial CT21INCH/dev/ttyACM0
  • OS: Linux (kernel 6.x/liquorix), Python 3.13
  • Project: current main (3.10.0-109-g4df61d9), i.e. with the new
    SERIAL_OPEN_ATTEMPTS open-retry loop in lcd_comm.py
  • Kernel driver: cdc_acm binds the awake interface and creates /dev/ttyACM1

Symptom

With REVISION: C, the app opens the port fine, then _hello() reads an empty
display ID
and loops forever ("Display returned invalid or unsupported ID, try
again in 1 second"). Nothing ever draws.

What I measured

I drove the panel through the stock rev C driver (pristine origin/main, no
local changes) and instrumented every serial_write to record the exact stall
point. Key results:

  1. Open works. Stock openSerial (with the retry loop) opens /dev/ttyACM1
    on the first attempt. So the retry loop does its job — the failure is not at
    the open stage.
  2. Handshake fails. The HELLO exchange returns an empty ID; stock _hello
    retries indefinitely.
  3. Byte-level, flow control ON (stock rtscts=True): the very first 250-byte
    write drains 0 bytes (write timeout). The stock port is opened with
    hardware RTS/CTS flow control, and this CDC-ACM gadget appears not to assert
    CTS, so nothing transmits.
  4. Byte-level, flow control OFF (rtscts=False): the first 250-byte frame
    drains, but the next write stalls (Write timeout). i.e. the bulk OUT
    endpoint accepts exactly one ~250-byte frame, then jams. Recovery requires
    a USB reset / physical replug.
  5. Same endpoints over raw libusb: HELLO returns chs_5inch.dev1_rom1.88
    immediately and full-screen frames stream without limit.

So the jam is a cdc_acm data-path limit, reproduced against current
upstream, and independent of the retry loop and of flow control (I tested
both). Happy to share the standalone repro harness.

Corroboration

The OEM Windows software for these panels also bypasses the serial/COM path and
speaks raw USB bulk (WinUsb_WritePipe / LibUsbDotNet) — consistent with the
serial path being unusable for sustained transfer on this hardware.

Proposal

An opt-in, Linux-only libusb transport for rev C:

  • A config option (default off) selects it, e.g. REV_C_TRANSPORT: SERIAL | LIBUSB, read with a safe default so pyserial stays the default for everyone
    and existing rev C users are unaffected.
  • Implemented inside the rev C class (no new REVISION; routes through the
    existing factory).
  • Uses pyusb (already a dependency for TUR_USB). On Linux it detaches
    cdc_acm (detach_kernel_driver) and drives bulk OUT/IN directly.
  • Gated to Linux with graceful fallback: detach_kernel_driver is a
    Linux/usbfs capability (not supported on libusb's Windows backend), so this
    only engages on Linux; Windows/macOS keep pyserial untouched.

Caveats & open questions

  • Single unit tested (5", ROM 88). Rev C USB IDs are unreliable across sizes,
    so I can't promise other rev C variants behave identically — which is exactly
    why this is opt-in and default-off.
  • Do Windows/macOS rev C users see any equivalent stall over the COM port?
    The jam is a Linux cdc_acm behavior; Windows uses usbser.sys. If the serial
    path already works there, the libusb transport only ever needs to engage on
    Linux (as proposed).
  • Preferred opt-in mechanism? I noticed config.yaml changes are blocked by
    CI (do-not-commit-config-yaml), so I'd read the option with a default and let
    you add the documented key to the shipped template — unless you'd prefer a
    different mechanism.

Would you welcome a PR along these lines? Happy to adjust the shape (config key
name, gating, docs) to whatever fits the project.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by tracing the existing rev C class and factory, then read lcd_comm.py and the current SERIAL_OPEN_ATTEMPTS behavior. Review how TUR_USB and config.yaml are handled before evaluating the proposed Linux-only transport. Done means an opt-in path can use libusb on Linux while pyserial remains the default elsewhere, with graceful fallback and no changed defaults.

Written by the indexing model from the issue text.

Assessment

Tech stack
linux, python
Domain
embedded-iot, operating-systems
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.