You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
EDIT:This issue originally concerned the CH9328 USB HID keyboard module, but work pivoted to compatibility with the CH9350L. The issue has been re-named. I'm leaving the original first post here in-place for future reference. Readers may raise a future issue if CH9328 compatibility is still desirable.
The "sister" module to the CH9329, the CH9328, appears to be better supported and more widely available, including from reliable distributors such as Adafruit: https://www.adafruit.com/product/5973
I have purchased a CH9328 module and will test with this code to see if it works, as is.
If so, we can update the documentation to include it.
If not, I will investigate supporting it (e.g. based on an options menu drop-down in the GUI client).
I looked into this today to purchase the necessary test hardware.
From my understanding of CH9350L, it appears to be designed to capture input from USB-HID peripherals, and convert their signals to RS485 Serial to transmit?
Here's the linked device on electrodragon, and another module I found on AliExpress:
The device contains a USB-A female port to plug peripherals into. To my understanding, this is the opposite of the functionality provided by the KVM Serial application, which transmits input over serial which is output as USB-HID signals by CH9329 & friends.
Module
Receive
Output
CH9329
RS232 Serial
USB-HID protocol messages
CH9350L
USB-HID protocol messages
RS485 Serial
Please correct me if I am wrong, as this would mean that the suggested candidate isn't suitable to this application?
Thanks for the comment @jpmorrison and for linking to the diagram. Sounds like CH9350 does actually support the required functionality after all.
So for use with this application, I'd need the following test hardware:
a USB-A to USB-A cable
between Computer or other USB hosts and CH9350L Chip SEL#0
wired to a USB RS485 TTL Serial Converter Adapter e.g. FT232RL / CP2102 / CH343G
connected to sending host
This looks achievable. It requires some code refactoring for an alternative protocol implementation to be supported, and appropriate command line switches and GUI options to be added.
The work breakdown is more or less (updated 05-05-2026):
CH9350Comm: add command line switch to kvm-control, e.g. --ch9350 (and probably add redundant switch --ch9329 for imperative declaration)
GUI: Add item to Options menu for Protocol to switch device, persist in .kvm-settings.ini file.
Update documentation to reflect code changes (README.md)
Update driver installation instructions to include common RS485 modules
A final note here is that the protocol documentation for CH9350L is not especially comprehensive. It provides some sample packets but not the full protocol spec (mouse data packets specifically are absent). However, as the hardware can send packets it would be very much possible to sniff the packet format with a RS485 TTL adapter, and use the captured information create a more comprehensive protocol guide.
This won't be especially quick for me to implement in the short-term, but I'll get the hardware ordered and keep it on my projects radar.
Hardware received. I think my first step will be to add a CH9350 protocol spec document to this repo based on real device data.
Test setup:
Mouse & USB HID CH9350 Module RS485 BUS CH9350 Module USB HID Test
Keyboard ======> (TX mode) ======> (RX mode) ======> Computer
|
V
RS485
Receiver
(inline)
|
V
Serial Capture
on Receiving PC
Plan is to capture data (should be unidirectional) from original hardware in TX mode, while testing the device I've bought functions correctly in RX mode.
I don't currently have much time to work on this, so if this is a feature you would strongly like to see implemented in this package, do +1 this message and I'll see if I can prioritise it.
I tried to use the CH9350 (lower computer mode) for 2 different projects. First attempt was to bridge a CH9350 to CH9329. Keyboard strait forward but mouse HID reports unresolved. Second attempt was to use the CH9350 to remote a keyboard and mouse to a computer via serial interface. The idea was to parse HID data and send keyboard/mouse events to the OS. Keyboard works fine but mouse, each device seems to have a different report structure ( data packet ).
CH9350 appears to be a CH559 with custom firmware. ( Assumption not fact )
This project needs upper computer mode. Serious reverse engineering of the communications between the 2 CH9350 devices is needed.
The CH9350 RS485 boards I have in my hands needed the RS485 jumpers in cross. The receive signal from CH340 can be wired to Rx/Tx for monitoring.
For testing is easier to bridge the 2 boards via USB and have a man in the middle computer to log communications.
Complete: CH9350L support on feature/13-ch9350L-support
End-to-end implementation merged-and-ready on the branch. Working summary plus the empirical findings worth recording.
All four CH9350L working states are implemented and verified against real hardware:
State
Mode
Status
0/1
Descriptor handshake (paired)
✅ Verified — keyboard, relative mouse, attach/reattach, LED echo, all 309 unit tests pass
2
BIOS keyboard + relative mouse
✅ Verified
3
BIOS keyboard + absolute mouse
✅ Verified
4
BIOS keyboard + HID Digitizers
✅ Verified against a single-monitor target. Multi-monitor (the canonical state-4 use case) untested — needs a Windows multi-monitor host to exercise the digitizer routing. Functionally identical to state 3 at the UART layer; the difference is in what descriptor the UC presents to the target
Wire-up is via --ch9350 / --ch9350-state on the CLI, and protocol + state selectors in the GUI (settings persisted across runs).
Empirical findings (worth surfacing)
A few non-obvious things came out of testing that aren't in the datasheet:
State 0/1 cannot send absolute mouse reports, despite datasheet §4.3 documenting LEN=0x0A as a valid LC→UC frame. The UC accepts the descriptor handshake, the target enumerates, but the chip's frame-forwarder silently drops 0x0A reports. Verified with synthetic frames and with a real CH9329 chip plugged into the LC's USB-host port as the report source — even the genuine article's RID 5 absolute frames don't reach the target's input pipeline. State 0/1/2 emulate absolute via translate-to-relative-deltas (Wacom-style); for true absolute, use state 3 or 4.
State 3/4's 0x04 X/Y range is 10-bit (0..1023), not 16-bit. The chip's USB-side descriptor declares an 11-bit signed field; the wire field is 16-bit, but the upper 6 bits are unused / Const padding. Sending values >1023 produces a periodic clip-sweep-clip pattern with period 2048 — diagnostic signature recorded in CH9350L_PROTO.md §Divergences. Notably different from the CH9329's 12-bit (0..4095) range despite the matching wire format — the chips declare different USB-side ranges even though the UART framing is byte-identical.
State 0/1/2 inherit target-side pointer acceleration as a property of relative-mouse-over-anything. Slow drags track faithfully, fast drags overshoot. Mitigations (disabling Enhance pointer precision / equivalent on macOS / Linux) and the alternative (state 3) are documented in the user guide.
Documentation
docs/CH9350L_PROTO.md — protocol spec covering all four states, attach sequence, state machine, frame formats, and a §Divergences section logging where the V2.3 datasheet contradicts what real hardware emits
309 passing across all groups (tests/utils/, tests/backend/, tests/kvm/, tests/test_control.py). CH9350Comm is covered including the descriptor-announce protocol, state transitions, LED echo, and the wire-format edge cases (signed-byte clamps, multi-frame fan-out, drag preserves held-button state, etc.).
Reference materials
ch9350_poc.py gist — bidirectional sniffer + LC emulator, kept up to date alongside the in-tree implementation. This is a public-domain
reference implementation which I'm releasing for anyone who wishes to implement the LC protocol.
Captures from this investigation referenced inline in CH9350L_PROTO.md
Note on CH9328
This issue originally also covered the CH9328, the Adafruit-stocked sister chip. Investigation pivoted entirely to CH9350L during the work. CH9328 has not been tested with this codebase. Anyone interested in CH9328 support should open a fresh issue rather than reopening this one — it's likely a small change (CH9328 may "just work" with CH9329Comm) but needs hardware to verify.
Raising a PR next! This one will land on PyPI as v1.5.6. I'd originally targeted 1.6, but this deserves an intermediate release for immediate use rather than waiting on closing other issues.
Activity
vladyst commentedon Oct 14, 2025
CH9328 does not support mouse emulation.
A better candidate is CH9350L.
https://www.electrodragon.com/product/usb-hid-to-uart-rs-485-extension-board/
[-]Test CH9328 module[/-][+]Test CH9328/CH9350L module[/+][-]Test CH9328/CH9350L module[/-][+]Test CH9328 / CH9350L module[/+][-]Test CH9328 / CH9350L module[/-][+]Test CH9328 / CH9350L modules[/+]sjmf commentedon Feb 3, 2026
I looked into this today to purchase the necessary test hardware.
From my understanding of CH9350L, it appears to be designed to capture input from USB-HID peripherals, and convert their signals to RS485 Serial to transmit?
Here's the linked device on electrodragon, and another module I found on AliExpress:
The device contains a USB-A female port to plug peripherals into. To my understanding, this is the opposite of the functionality provided by the KVM Serial application, which transmits input over serial which is output as USB-HID signals by CH9329 & friends.
Please correct me if I am wrong, as this would mean that the suggested candidate isn't suitable to this application?
jpmorrison commentedon Mar 8, 2026
CH9350 mode depends on dip switches - you can plug it into a PC or connect keyboard/mouse to it.
The 9350 is a TTL UART. It has a transmit signal so you can use it with an RS485 driver/IC in a KVM application.
https://github.com/aotebai/esp32-kvm-usb-mouse-keyboard-switcher/blob/main/PDF_drawing/Pin_connection_diagram_e.pdf
sjmf commentedon Mar 8, 2026
Thanks for the comment @jpmorrison and for linking to the diagram. Sounds like CH9350 does actually support the required functionality after all.
So for use with this application, I'd need the following test hardware:
Computer or other USB hostsandCH9350L Chip SEL#0FT232RL/CP2102/CH343GThis looks achievable. It requires some code refactoring for an alternative protocol implementation to be supported, and appropriate command line switches and GUI options to be added.
The work breakdown is more or less (updated 05-05-2026):
CH9329CommandCH9350CommCH9329Commclass: basically already written. Move implementation, fix importsCH9350Commclass: implement from CH9350 protocol documentation__init__()function ofBaseOpCH9329Comm: default, preserve existing functionalityCH9350Comm: add command line switch tokvm-control, e.g.--ch9350(and probably add redundant switch--ch9329for imperative declaration)Protocolto switch device, persist in.kvm-settings.inifile.README.md)A final note here is that the protocol documentation for CH9350L is not especially comprehensive. It provides some sample packets but not the full protocol spec (mouse data packets specifically are absent). However, as the hardware can send packets it would be very much possible to sniff the packet format with a RS485 TTL adapter, and use the captured information create a more comprehensive protocol guide.
This won't be especially quick for me to implement in the short-term, but I'll get the hardware ordered and keep it on my projects radar.
[-]Test CH9328 / CH9350L modules[/-][+]Implement support for CH9328 / CH9350L modules[/+][-]Implement support for CH9328 / CH9350L modules[/-][+]Support for CH9328 / CH9350L modules[/+]sjmf commentedon Mar 9, 2026
Parts ordered for testing and implementation.
sjmf commentedon Mar 18, 2026
Hardware received. I think my first step will be to add a CH9350 protocol spec document to this repo based on real device data.
Test setup:
Plan is to capture data (should be unidirectional) from original hardware in TX mode, while testing the device I've bought functions correctly in RX mode.
I don't currently have much time to work on this, so if this is a feature you would strongly like to see implemented in this package, do +1 this message and I'll see if I can prioritise it.
vladyst commentedon Mar 19, 2026
I tried to use the CH9350 (lower computer mode) for 2 different projects. First attempt was to bridge a CH9350 to CH9329. Keyboard strait forward but mouse HID reports unresolved. Second attempt was to use the CH9350 to remote a keyboard and mouse to a computer via serial interface. The idea was to parse HID data and send keyboard/mouse events to the OS. Keyboard works fine but mouse, each device seems to have a different report structure ( data packet ).
CH9350 appears to be a CH559 with custom firmware. ( Assumption not fact )
This project needs upper computer mode. Serious reverse engineering of the communications between the 2 CH9350 devices is needed.
The CH9350 RS485 boards I have in my hands needed the RS485 jumpers in cross. The receive signal from CH340 can be wired to Rx/Tx for monitoring.
For testing is easier to bridge the 2 boards via USB and have a man in the middle computer to log communications.
34 remaining items
Add debug log for state-0/1/2 abs->rel mouse translation (towards #13)
Recalibrate CH9350Comm state-3/4 absolute range to 14-bit (towards #13)
Recalibrate CH9350Comm state-3/4 absolute range to 10-bit (towards #13)
Document CH9350L state-3/4 absolute X/Y range (towards #13)
Document target-side pointer acceleration in state 0/1/2 (towards #13)
Fix drag broken by on_move clearing held button (towards #13)
[-]Support for CH9350L/CH9328 modules[/-][+]Support for CH9350L module[/+]sjmf commentedon May 10, 2026
Complete: CH9350L support on
feature/13-ch9350L-supportEnd-to-end implementation merged-and-ready on the branch. Working summary plus the empirical findings worth recording.
All four CH9350L working states are implemented and verified against real hardware:
Wire-up is via
--ch9350/--ch9350-stateon the CLI, and protocol + state selectors in the GUI (settings persisted across runs).Empirical findings (worth surfacing)
A few non-obvious things came out of testing that aren't in the datasheet:
0x0Aas a valid LC→UC frame. The UC accepts the descriptor handshake, the target enumerates, but the chip's frame-forwarder silently drops0x0Areports. Verified with synthetic frames and with a real CH9329 chip plugged into the LC's USB-host port as the report source — even the genuine article's RID 5 absolute frames don't reach the target's input pipeline. State 0/1/2 emulate absolute via translate-to-relative-deltas (Wacom-style); for true absolute, use state 3 or 4.0x04X/Y range is 10-bit (0..1023), not 16-bit. The chip's USB-side descriptor declares an 11-bit signed field; the wire field is 16-bit, but the upper 6 bits are unused / Const padding. Sending values >1023 produces a periodic clip-sweep-clip pattern with period 2048 — diagnostic signature recorded inCH9350L_PROTO.md§Divergences. Notably different from the CH9329's 12-bit (0..4095) range despite the matching wire format — the chips declare different USB-side ranges even though the UART framing is byte-identical.Documentation
docs/CH9350L_PROTO.md— protocol spec covering all four states, attach sequence, state machine, frame formats, and a §Divergences section logging where the V2.3 datasheet contradicts what real hardware emitsdocs/CH9350L_GUIDE.md— user guide: hardware sourcing, dipswitch configuration, physical setup, state-selection guidance, GUI/CLI usage, troubleshootingdocs/CH9329_GUIDE.mdanddocs/CH9329_PROTO.md— for parity, since the chip family now has two membersREADME.mdandSUPPORTED_DEVICES.mdTests
309 passing across all groups (
tests/utils/,tests/backend/,tests/kvm/,tests/test_control.py).CH9350Commis covered including the descriptor-announce protocol, state transitions, LED echo, and the wire-format edge cases (signed-byte clamps, multi-frame fan-out, drag preserves held-button state, etc.).Reference materials
ch9350_poc.pygist — bidirectional sniffer + LC emulator, kept up to date alongside the in-tree implementation. This is a public-domainreference implementation which I'm releasing for anyone who wishes to implement the LC protocol.
CH9350L_PROTO.mdNote on CH9328
This issue originally also covered the CH9328, the Adafruit-stocked sister chip. Investigation pivoted entirely to CH9350L during the work. CH9328 has not been tested with this codebase. Anyone interested in CH9328 support should open a fresh issue rather than reopening this one — it's likely a small change (CH9328 may "just work" with
CH9329Comm) but needs hardware to verify.Raising a PR next! This one will land on PyPI as v1.5.6. I'd originally targeted 1.6, but this deserves an intermediate release for immediate use rather than waiting on closing other issues.
Fix CH9350L abs coordinate range in DataComm docstring (towards #13)
Clamp wheel delta to ±127 in state-0/1 mouse frame (towards #13)
Type comm_manager as DataCommManager via TYPE_CHECKING import (towards …
Use -1 sentinel for CH9329 state in _PROTOCOL_OPTIONS (towards #13)
Add tests for ch9350 internals, parse_frames edge cases, manager erro…