PyCH9329-HID is a high-level Python package for CH9329 HID chip, designed to provide seamless, OS-independent peripheral control via serial communication.
Project description
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
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pych9329_hid-0.2.1.tar.gz.
File metadata
- Download URL: pych9329_hid-0.2.1.tar.gz
- Upload date:
- Size: 26.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.25
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4b3aa426b8ddf94085317a9a23e0567471088edab23595f62aae170a4a8bdfc9
|
|
| MD5 |
5b2a6f23a220e32eab3e435309a52d74
|
|
| BLAKE2b-256 |
15b0921b731b87e662836f433edd06a141d6bbc14c509d904e4f5c19538548bc
|
File details
Details for the file pych9329_hid-0.2.1-py3-none-any.whl.
File metadata
- Download URL: pych9329_hid-0.2.1-py3-none-any.whl
- Upload date:
- Size: 24.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.25
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d887ba0c1e7fbd9b662334022492116ff8faf37a58d068e1e81ff6b12bd49a59
|
|
| MD5 |
e0298e43998fd93953e9486b89d5222b
|
|
| BLAKE2b-256 |
197c938845ed2816bd5bd40d7ff2b620d3fbaf5ccc64a9a3f1ad4dbde8dcf25a
|