PyCH9329-HID
PyCH9329-HID is a high-level Python package for CH9329 HID chip, designed to provide seamless, OS-independent peripheral control via serial communication.
🎯 Target Use Cases
| Use Case | Description |
|---|---|
| AI Vision Agents | Perfect for models like UI-TARS or Claude Computer Use that require high-fidelity interaction with a screen based on visual coordinates |
| Test Automation | Reliable UI testing on macOS/Windows/Linux without requiring OS-level accessibility permissions |
| Embedded Robotics | Controlling computers via Raspberry Pi or ESP32 using a physical CH9329 USB dongle |
📦 Installation
pip install pych9329-hid
Requirements
- Python >= 3
- pyserial
Import Options
You can use either of the full package name or shorter alias:
# Option 1: Full package name
from pych9329_hid import HIDController
# Option 2: Short alias (recommended)
from pych9329 import HIDController
Both options work identically - choose whichever you prefer!
🚀 Quick Start
Basic Usage - Low Level Protocol
from pych9329 import CH9329, SerialTransport
# Create transport and CH9329 instance
with SerialTransport(port='/dev/ttyUSB0', baudrate=115200) as transport:
ch9329 = CH9329(transport)
# Send keyboard report
ch9329.send_keyboard(modifier=0, keycodes=[0x04])
# Send relative mouse movement
ch9329.send_mouse_rel(dx=10, dy=5, buttons=0x01)
# Send absolute mouse position
ch9329.send_mouse_abs(x=100, y=200, buttons=0x01)
# Get device information
info = ch9329.get_info()
print(f"Version: {info['version']}")
print(f"USB Connected: {info['usb_connected']}")
print(f"Num Lock: {info['num_lock_on']}")
print(f"Caps Lock: {info['caps_lock_on']}")
print(f"Scroll Lock: {info['scroll_lock_on']}")
Keymap Support:
- Characters: a-z, A-Z, 0-9, space, punctuation
- Special keys: f1-f12, enter, backspace, tab, esc, home, end, pageup, pagedown, insert, delete, arrows
- Modifiers: ctrl, shift, alt, cmd (also supports: control, option, win, command, meta, super)
High-Level HID Controller
from pych9329 import HIDController, SerialTransport
# Create HID controller
with SerialTransport(port='/dev/ttyUSB0', baudrate=115200) as transport:
controller = HIDController(transport, screen_width=1920, screen_height=1080)
# Keyboard operations
controller.press('a')
controller.write('Hello World')
controller.hotkey('cmd', 'space')
# Mouse operations
controller.moveTo(100, 200, duration=0.5)
controller.click(button='left')
controller.dragRel(50, 30)
# Scrolling
controller.scroll(5) # Scroll up
controller.hscroll(-2) # Scroll left
⚙️ Configuration Example
from pych9329 import CH9329, SerialTransport
with SerialTransport(port='/dev/ttyUSB0', baudrate=115200) as transport:
ch9329 = CH9329(transport)
# Get current configuration
config = ch9329.get_config()
# CH9329Config:
# Work Mode: Keyboard+Mouse (Hardware)
# Serial Mode: Protocol mode (Hardware)
# Address: 0x00
# Baud Rate: 9600
# Packet Interval: 3ms
# VID: 0x861A
# PID: 0x29E1
# Keyboard Submit Interval: 0ms
# Keyboard Release Delay: 1ms
# Auto Enter Flag: 0
# Enter Characters: 0d00000000000000
# Filter Strings: 0000000000000000
# Custom Descriptor Enable: {'vendor': True, 'product': False, 'sn': False}
# Keyboard Fast Submission: 0
# Modify configuration
config.baudrate = 9600
config.vid = 0x1234
config.pid = 0x5678
# Enable vendor and product string descriptors
config.custom_descriptor_enable = {'vendor': True, 'product': True, 'sn': False}
# Apply configuration
if ch9329.set_config(config):
print("Configuration updated successfully!")
# Set USB descriptors
ch9329.set_usb_descriptor(0x00, "My Company") # Vendor
ch9329.set_usb_descriptor(0x01, "CH9329 Keyboard") # Product
ch9329.set_usb_descriptor(0x02, "SN-12345") # Serial Number
# Read back USB descriptors
vendor = ch9329.get_usb_descriptor(0x00)
product = ch9329.get_usb_descriptor(0x01)
serial = ch9329.get_usb_descriptor(0x02)
print(f"Vendor: {vendor}, Product: {product}, SN: {serial}")
# Restore factory defaults
if ch9329.set_config_to_default():
print("Factory settings restored!")
# Reset chip
ch9329.chip_reset()
📚 API Reference
CH9329 (Low-Level Protocol)
Device Information
| Method | Description | Returns |
|---|---|---|
get_info() |
Get device information | dict with keys: version, usb_connected, num_lock_on, caps_lock_on, scroll_lock_on |
Configuration Management
| Method | Description | Returns |
|---|---|---|
get_config() |
Get device configuration | CH9329Config object or None |
set_config(config) |
Set device configuration | bool - True if successful |
set_config_to_default() |
Restore factory default settings | bool - True if successful |
chip_reset() |
Perform software reset | bool - True if successful |
USB String Descriptors
| Method | Description | Returns |
|---|---|---|
get_usb_descriptor(type) |
Get USB string descriptor | str or None |
set_usb_descriptor(type, string) |
Set USB string descriptor | bool - True if successful |
USB Descriptor Types:
0x00: Vendor string descriptor0x01: Product string descriptor0x02: Serial number string descriptor
Keyboard Operations
| Method | Description | Returns |
|---|---|---|
send_keyboard(modifier, keycodes) |
Send keyboard report | bool - True if successful |
Mouse Operations
| Method | Description | Returns |
|---|---|---|
send_mouse_rel(dx, dy, buttons, wheel) |
Send relative mouse movement | bool - True if successful |
send_mouse_abs(x, y, buttons, wheel) |
Send absolute mouse position | bool - True if successful |
⚠️ Error Handling
CH9329 Layer
The CH9329 protocol layer uses a hybrid error handling strategy:
-
Soft Errors (timeout, ACK error): Return
Falsewith a warning- Device doesn't respond within timeout
- Device returns error status in ACK
-
Hard Errors (transport failure): Raise
SerialTransportError- Serial port disconnected
- Serial port read/write failure
- Invalid serial port configuration
HIDController Layer
HIDController does not check return values from CH9329 - failures propagate naturally to the caller. This allows users to decide how to handle errors based on their use case.
SerialTransport Layer
The transport layer provides custom exceptions:
SerialTransportError: Base exception for transport errorsSerialTransportClosedError: Raised when operation attempted on closed transport
HIDController (High-Level API)
Keyboard Operations
| Method | Description | Example |
|---|---|---|
press(key) |
Press and release a key | controller.press('a') |
keyDown(key) |
Press and hold a key | controller.keyDown('shift') |
keyUp(key) |
Release a key | controller.keyUp('shift') |
write(text) |
Type a string | controller.write('Hello\n') |
hotkey(*keys) |
Press key combination | controller.hotkey('cmd', 'a') |
releaseAllKey() |
Release all keyboard keys | controller.releaseAllKey() |
numpadPress(key) |
Press numpad key | controller.numpadPress('7') |
numpadWrite(text) |
Type numpad characters | controller.numpadWrite('123') |
Mouse Button Operations
| Method | Description | Example |
|---|---|---|
click(button, clicks) |
Click mouse button | controller.click('left', clicks=2) |
mouseDown(button) |
Press and hold button | controller.mouseDown('left') |
mouseUp(button) |
Release button | controller.mouseUp('left') |
releaseMouseButton() |
Release all mouse buttons | controller.releaseMouseButton() |
Mouse Movement
| Method | Description | Example |
|---|---|---|
moveTo(x, y, duration) |
Move mouse to coordinates | controller.moveTo(100, 200, duration=0.5) |
moveRel(dx, dy, duration) |
Move mouse relatively | controller.moveRel(10, 5) |
Drag Operations
| Method | Description | Example |
|---|---|---|
dragRel(dx, dy) |
Drag relatively | controller.dragRel(50, 30) |
dragTo(x, y) |
Drag to coordinates | controller.dragTo(100, 200) |
Scrolling
| Method | Description | Example |
|---|---|---|
scroll(clicks) |
Vertical scroll | controller.scroll(5) |
hscroll(clicks) |
Horizontal scroll | controller.hscroll(-2) |
Hardware Operations
| Method | Description | Returns |
|---|---|---|
reset() |
Reset cursor to (0,0) | None |
getDeviceInfo() |
Get device information | dict with device info |
SerialTransport (Transport Layer)
| Method | Description | Returns |
|---|---|---|
write(data) |
Write raw bytes to port | None |
read(size) |
Read up to size bytes |
bytes read (may be empty on timeout) |
read_all() |
Read all available data | All available bytes |
is_open() |
Check if port is open | bool |
close() |
Close the serial port | None |
🔧 Configuration
SerialTransport Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
port |
str | Required | Serial port path (e.g., '/dev/ttyUSB0' or 'COM3') |
baudrate |
int | 115200 | Baud rate (115200 recommended for CH9329) |
timeout |
float | 0.005 | Read/write timeout in seconds |
HIDController Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
screen_width |
int | 1920 | Screen width in pixels |
screen_height |
int | 1080 | Screen height in pixels |
dwelling_time |
float | 0.01 | Delay between operations (seconds) |
move_interval |
float | 0.015 | Mouse movement interval (seconds) |
keypress_hold_time |
float | 0.05 | Key press duration (seconds) |
double_click_interval |
float | 0.1 | Double click interval (seconds) |
scroll_multiplier |
int | 5 | Scroll sensitivity multiplier |
📝 Examples
List Available Serial Devices
import serial.tools.list_ports
ports = serial.tools.list_ports.comports()
for port in ports:
print(f"Port: {port.device}, Description: {port.description}")
Complete Automation Example
from pych9329 import HIDController, SerialTransport
import time
with SerialTransport(port='/dev/ttyUSB0', baudrate=115200) as transport:
controller = HIDController(transport, screen_width=1920, screen_height=1080)
# Open Spotlight (macOS)
controller.hotkey('cmd', 'space')
time.sleep(0.5)
# Type application name
controller.write('Terminal')
time.sleep(0.5)
# Press Enter
controller.press('enter')
time.sleep(1)
# Type command
controller.write('echo "Hello from CH9329!"')
controller.press('enter')
✨ Features
- ✅ Low-level CH9329 protocol implementation with ACK verification
- ✅ High-level HID automation API (keyboard, mouse, scrolling, dragging)
- ✅ Smooth mouse movement with interpolation
- ✅ Drag and drop support for UI automation
- ✅ OS-independent - works on macOS, Windows, Linux
- ✅ Device configuration management - read/write CH9329 configuration
- ✅ Factory default restoration - reset to default settings
- ✅ USB string descriptor support - read/write vendor, product, serial number
- ✅ Software reset - chip reset without unplugging
📄 License
MIT License - see LICENSE file for details
Metadata
Release files for pych9329-hid 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pych9329_hid-0.2.1.tar.gz | 26.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pych9329_hid-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 50.6 kB
Release files / pych9329_hid-0.2.1.tar.gz
| Download URL | pych9329_hid-0.2.1.tar.gz |
|---|---|
| Size | 26.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4b3aa426b8ddf94085317a9a23e0567471088edab23595f62aae170a4a8bdfc9
|
|
BLAKE2b-256 checksum How to use checksums |
15b0921b731b87e662836f433edd06a141d6bbc14c509d904e4f5c19538548bc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.25
|
Release files / pych9329_hid-0.2.1-py3-none-any.whl
| Download URL | pych9329_hid-0.2.1-py3-none-any.whl |
|---|---|
| Size | 24.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d887ba0c1e7fbd9b662334022492116ff8faf37a58d068e1e81ff6b12bd49a59
|
|
BLAKE2b-256 checksum How to use checksums |
197c938845ed2816bd5bd40d7ff2b620d3fbaf5ccc64a9a3f1ad4dbde8dcf25a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.25
|