summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhathach <[email protected]>2026-03-06 17:56:05 +0700
committerhathach <[email protected]>2026-03-06 17:56:05 +0700
commitaeb121f94cb9c5ecb220e2cf7e9e6b53e5d858b1 (patch)
tree755007599792d1758249ece23358618f6184a39d
parent73cd53129529f2dce70072953bfd50ec9dc6b8ea (diff)
add hil test, update readme
-rw-r--r--README.rst1
-rw-r--r--examples/device/printer_to_cdc/README.md80
-rwxr-xr-xtest/hil/hil_test.py109
3 files changed, 190 insertions, 0 deletions
diff --git a/README.rst b/README.rst
index a3863c375..b24396b5a 100644
--- a/README.rst
+++ b/README.rst
@@ -87,6 +87,7 @@ Supports multiple device configurations by dynamically changing USB descriptors,
- Communication Device Class (CDC)
- Device Firmware Update (DFU): DFU mode (WIP) and Runtime
- Human Interface Device (HID): Generic (In & Out), Keyboard, Mouse, Gamepad etc ...
+- Printer class
- Mass Storage Class (MSC): with multiple LUNs
- Musical Instrument Digital Interface (MIDI)
- Media Transfer Protocol (MTP/PTP)
diff --git a/examples/device/printer_to_cdc/README.md b/examples/device/printer_to_cdc/README.md
new file mode 100644
index 000000000..ecb9a678f
--- /dev/null
+++ b/examples/device/printer_to_cdc/README.md
@@ -0,0 +1,80 @@
+#### Printer to CDC
+
+This example demonstrates a USB composite device with a Printer class interface and a CDC serial interface. Data flows bidirectionally between the two:
+
+- Data sent to the Printer (from host) is forwarded to the CDC serial port
+- Data sent to the CDC serial port (from host) is forwarded to the Printer IN endpoint
+
+This is useful for debugging printer class communication or as a reference for implementing printer class devices.
+
+#### USB Interfaces
+
+| Interface | Class | Description |
+|-----------|-------|-------------|
+| 0 | CDC ACM | Virtual serial port |
+| 2 | Printer | USB Printer (bidirectional, protocol 2) |
+
+#### How to Test
+
+The device exposes two endpoints on the host:
+- `/dev/ttyACM0` (CDC serial port)
+- `/dev/usb/lp0` (USB printer)
+
+Note: the actual device numbers may vary depending on your system.
+
+**Prerequisites (Linux):**
+
+```bash
+# Load the USB printer kernel module if not already loaded
+sudo modprobe usblp
+
+# Check devices exist
+ls /dev/ttyACM* /dev/usb/lp*
+```
+
+**Test Printer to CDC (host writes to printer, reads from CDC):**
+
+```bash
+# Terminal 1: read from CDC
+cat /dev/ttyACM0
+
+# Terminal 2: write to printer
+echo "hello from printer" > /dev/usb/lp0
+# "hello from printer" appears in Terminal 1
+```
+
+**Test CDC to Printer (host writes to CDC, reads from printer):**
+
+```bash
+# Terminal 1: read from printer IN endpoint
+cat /dev/usb/lp0
+
+# Terminal 2: write to CDC
+echo "hello from cdc" > /dev/ttyACM0
+# "hello from cdc" appears in Terminal 1
+```
+
+**Interactive bidirectional test:**
+
+```bash
+# Terminal 1: open CDC serial port
+minicom -D /dev/ttyACM0
+
+# Terminal 2: send to printer
+echo "tinyusb print example" > /dev/usb/lp0
+# Text appears in minicom. Type in minicom to send data back through printer TX.
+```
+
+#### IEEE 1284 Device ID
+
+The device responds to GET_DEVICE_ID requests with:
+
+```
+MFG:TinyUSB;MDL:Printer to CDC;CMD:PS;CLS:PRINTER;
+```
+
+Verify with:
+
+```bash
+cat /sys/class/usbmisc/lp0/device/ieee1284_id
+```
diff --git a/test/hil/hil_test.py b/test/hil/hil_test.py
index 757456806..46cb79e01 100755
--- a/test/hil/hil_test.py
+++ b/test/hil/hil_test.py
@@ -166,6 +166,32 @@ def open_mtp_dev(uid):
return None
+def get_printer_dev(id, vendor_str, product_str, ifnum):
+ """Find /dev/usb/lpX by matching USB serial, vendor, product, and interface number via sysfs"""
+ vendor_str = vendor_str.replace(' ', '_') if vendor_str else ''
+ product_str = product_str.replace(' ', '_') if product_str else ''
+ for lp in glob.glob('/sys/class/usbmisc/lp*'):
+ try:
+ sn = open(f'{lp}/device/../serial').read().strip()
+ if sn == id:
+ return f'/dev/usb/{os.path.basename(lp)}'
+ except (FileNotFoundError, PermissionError, ValueError):
+ pass
+ return None
+
+
+def open_printer_dev(id, vendor_str, product_str, ifnum):
+ """Wait for printer device to enumerate and return its path"""
+ timeout = ENUM_TIMEOUT
+ while timeout > 0:
+ lp_dev = get_printer_dev(id, vendor_str, product_str, ifnum)
+ if lp_dev and os.path.exists(lp_dev):
+ return lp_dev
+ time.sleep(1)
+ timeout -= 1
+ assert False, f'Printer device not found for {id} if{ifnum:02d}'
+
+
# -------------------------------------------------------------
# Flashing firmware
# -------------------------------------------------------------
@@ -552,6 +578,88 @@ def test_device_hid_composite_freertos(id):
pass
+def test_device_printer_to_cdc(board):
+ import threading
+
+ uid = board['uid']
+
+ # Wait for CDC port and printer device
+ cdc_port = get_serial_dev(uid, 'TinyUSB', "TinyUSB_Device", 0)
+ ser = open_serial_dev(cdc_port)
+ lp_dev = open_printer_dev(uid, 'TinyUSB', 'TinyUSB_Device', 2)
+
+ # Test 0: Verify IEEE 1284 Device ID from sysfs
+ expected_id = 'MFG:TinyUSB;MDL:Printer to CDC;CMD:PS;CLS:PRINTER;'
+ lp_name = os.path.basename(lp_dev)
+ sysfs_id_path = f'/sys/class/usbmisc/{lp_name}/device/ieee1284_id'
+ if os.path.exists(sysfs_id_path):
+ with open(sysfs_id_path) as f:
+ ieee1284_id = f.read().strip()
+ if ieee1284_id:
+ assert ieee1284_id == expected_id, (f'IEEE 1284 ID mismatch:\n'
+ f' expected: {expected_id}\n got: {ieee1284_id}')
+
+ def rand_ascii(length):
+ return "".join(random.choices(string.ascii_letters + string.digits, k=length)).encode("ascii")
+
+ sizes = [32, 64, 128, 256, 512, random.randint(2000, 5000)]
+
+ # flush any stale data
+ ser.reset_input_buffer()
+
+ # Test 1: Printer -> CDC with multiple sizes
+ for size in sizes:
+ test_data = rand_ascii(size)
+ with open(lp_dev, 'wb') as lp:
+ lp.write(test_data)
+ lp.flush()
+ rd = b''
+ while len(rd) < size:
+ chunk = ser.read(size - len(rd))
+ assert chunk, f'Printer->CDC timeout at {len(rd)}/{size} bytes'
+ rd += chunk
+ assert rd == test_data, (f'Printer->CDC wrong data ({size} bytes):\n'
+ f' expected: {test_data[:64]}\n received: {rd[:64]}')
+
+ # Test 2: CDC -> Printer with multiple sizes
+ # Use a thread to read from printer since /dev/usb/lp read blocks
+ for size in sizes:
+ test_data = rand_ascii(size)
+ rd_result = [b'', None] # [data, error]
+
+ def lp_reader():
+ try:
+ rd = b''
+ with open(lp_dev, 'rb') as lp:
+ while len(rd) < size:
+ chunk = lp.read(size - len(rd))
+ if not chunk:
+ break
+ rd += chunk
+ rd_result[0] = rd
+ except Exception as e:
+ rd_result[1] = e
+
+ reader = threading.Thread(target=lp_reader, daemon=True)
+ reader.start()
+
+ # Write to CDC in chunks
+ offset = 0
+ while offset < size:
+ chunk_size = min(random.randint(1, 64), size - offset)
+ ser.write(test_data[offset:offset + chunk_size])
+ ser.flush()
+ offset += chunk_size
+
+ reader.join(timeout=10)
+ assert not reader.is_alive(), f'CDC->Printer timeout ({size} bytes)'
+ assert rd_result[1] is None, f'CDC->Printer read error: {rd_result[1]}'
+ assert rd_result[0] == test_data, (f'CDC->Printer wrong data ({size} bytes):\n'
+ f' expected: {test_data[:64]}\n received: {rd_result[0][:64]}')
+
+ ser.close()
+
+
def test_device_mtp(board):
uid = board['uid']
@@ -623,6 +731,7 @@ device_tests = [
'device/dfu_runtime',
'device/cdc_msc_freertos',
'device/hid_boot_interface',
+ 'device/printer_to_cdc',
# 'device/mtp'
]