ch57x-keyboard-tool icon indicating copy to clipboard operation
ch57x-keyboard-tool copied to clipboard

Another 514c:8850 model, 4x4 keyboard + 3 knobs, slightly different configuration

Open yawor opened this issue 1 month ago • 4 comments

Hi,

I saw the issue #136 with the same VID:PID combination, but this one has 16 buttons (4x4) and 3 knobs. The code on the board is @XZKJ-16key_3knob.

Image Image Image

After some fiddling with the code (changing the base to 16) I got the program to at least not panic, but nothing I've programmed really worked, except the mouse left click - the keyboard didn't even send any other HID events. With the mouse click working I've been able to at least confirm that the base 16 is proper for this keyboard (wherever I've set the click it worked properly - so I could set it to all 16 buttons and 3 knobs).

So I've decided to start up a Windows VM (disposable copy without Internet access just to be sure), load the usbmon module and fire up the Wireshark to check what the original software does differently. What I've found is that the payload is similar, but not quite the same as the current implementation.

Here's an example payload when setting a ctrl+alt+delete combo:

     | 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
-----+------------------------------------------------
0000 | 03 fd 01 01 01 00 03 00 00 f1 00 00 f3 00 00 4c
0010 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0020 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0030 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0040 | 00

First observation is that the payload is 65 bytes long instead of 64. I've been able to change the Keyboard trait like this:

    fn get_payload_size(&self) -> usize { 64 }

    fn send(&mut self, msg: &[u8]) -> Result<()> {
        let mut buf = vec![0; self.get_payload_size()];
        buf[..msg.len()].copy_from_slice(msg);

        debug!("send: {:02x?}", buf);
        let written = self
            .get_handle()
            .write_interrupt(self.get_endpoint(), &buf, DEFAULT_TIMEOUT)?;
        ensure!(written == buf.len(), "not all data written");
        Ok(())
    }

I've added the get_payload_size method with a default implementation which returns 64, but now any implementation can override the payload size. Then I've changed the buf variable to be Vec instead of an array.

So the first 5 bytes seems to be the same as for k884x implementation. The only exception is that the 2nd byte is 0xfd, not 0xfe, but I don't know if this makes any difference (I'll test this later). So in this payload I set the button 1 (top-left) on layer 1 to a keyboard key sequence (value 1 at offset 0x04).

Value at offset 0x05 seems to be always 0, at least for the keyboard type. I've seen other value for mouse binds. Value at offset 0x06 is the number of keys in the sequence - for the key to work the value must be at least 1. The sequence starts at offset 0x07. Each key takes 3 consecutive bytes, where first 2 bytes in each key is a 16-bit big endian value of delay in milliseconds and the last byte is the HID code.

Theoretically there's space for up to 19 keys, but I've been able to store only 18.

The difference is that the modifier keys are now a part of the sequence and they combine only with the first non-modifier key that is directly after them. So in the payload above there are 3 keys in the sequence: ALT, CTRL, DELETE. If I would add another DELETE after that then the ALT and CTRL would apply only to the first DELETE, but not the second one. So this allows to have more than a single modifier, but modifiers take space in the sequence.

The modifiers have following values:

0xf1 - Left Ctrl
0xf2 - Left Shift
0xf3 - Left Alt
0xf4 - Left Meta (Win)
0xf5 - Right Ctrl
0xf6 - Right Shift
0xf7 - Right Alt
0xf8 - Right Meta

I've started the implementation in the k8850.rs (is it ok to use that name? there may be some conflicts, so maybe I should name it k8850_4x4) and first tests seem to work. I still need to capture other bind types and maybe even the LED settings.

Implementing support for this keyboard won't be very hard, but right now the keyboard implementation selection is hardcoded in the program and associated with the USB PID. Given that the makers of these keyboards don't guarantee any uniqueness of the PIDs, I think there should be an option to override the implementation selection manually, like right now it is possible for VID, PID and other USB params.

I'm not creating the PR yet, as this still needs some work.

yawor avatar Oct 29 '25 13:10 yawor

Another difference I've noticed is that this program sends a packet 03 fd fe ff after every key bind, but the original software sends it only once after all the binds are sent.

yawor avatar Oct 29 '25 13:10 yawor

The mouse support is quite interesting in this keyboard. The original software doesn't even use all available features, but I've just tried sending different value combinations anyway. For example the original software can configure wheel up/down with one optional modifier (ctrl, shift, alt) and also it can bind left, right or middle click (without modifiers or combination of the buttons).

But looking at the payload structure it seemed to be possible so I've tried it. It is actually possible to have buttons with modifier and also click any combination of buttons. Also the wheel values 0x01 (up) and 0xff (down) look just like signed 8-bit value, so 0xff is actually equal to -1. So I've tried other values for the scroll wheel and I got faster scrolling. So for example values 0x03 and 0xfd give 3 times faster scrolling (but less precise), because each event scrolls by 3 steps instead of 1.

Also this keyboard supports mouse movement too (even though the software doesn't expose such bindings). It is possible to move mouse by the specified number of steps in the X and Y direction.

It is also possible to combine button clicks, scroll wheel and mouse movement into the same bind.

Here's the mouse bind payload:

03 fd 10 01 03 01 04 00 00 f1 00 00 00 00 00 00 00 00 00 00 00 01 [padded with zeros]
Bytes Desc
03 Report ID
FD Command ?
10 Key to bind
01 Layer
03 Mouse bind
01 I think this is the number of possible modifiers
04 This seems to be the number of mouse options, as below there are 4 sections for the mouse (button, X and Y movement and wheel). Both this and previous byte sums to 5, which is the total number of 3 byte sections below
00 00 F1 Modifier - the same values as with the keyboard, so F1 = Left Ctrl
00 00 00 Buttons - bitmap: 0x01 = LMB, 0x02 = RMB, 0x04 = MMB. The value goes to the 3rd byte
00 00 00 Mouse X movement (signed value in the 3rd byte)
00 00 00 Mouse Y movement (signed value in the 3rd byte)
00 00 01 Scroll Wheel (signed value in the 3rd byte)

yawor avatar Oct 29 '25 17:10 yawor

Very good research, thanks!

Support for mouse move and scroll speed is already implemented, I'll upload it soon, so you may reuse it.

About manual model selection: I hoped we may somehow ask keyboard for it's internal model. Doesn't official software receive any data from keyboard at the very beginning of interaction?

kriomant avatar Oct 31 '25 17:10 kriomant

Yes, the official software sends a query after which the keyboard sends 25 reports, one for each bind position. The report seems to have the same format as the one sent from the software to configure the bind. This is repeated 3 times, once for every layer.

I only have this one keyboard so I don't know if other models respond in some other way. Or maybe the command to retrieve current configuration is different and not compatible between models, so the command won't do anything on non-compatible model. It could be worse if command for one model would mess with the configuration on another though.

yawor avatar Oct 31 '25 17:10 yawor

I tried to build yawor's branch that hasn't been merged yet but cannot get my 4x4 macropad with Hardware ID USB\VID_514C&PID_8850&REV_0100 USB\VID_514C&PID_8850 to work with this tool. How are you meant to use this? Any help would be greatly appreciated as I really do not want to download some shady software for it. Thanks.

xCISACx avatar Nov 24 '25 22:11 xCISACx

Do you have the same model as me? I don't mean the VID/PID pair, as these are reused with different products unfortunately.

yawor avatar Nov 24 '25 22:11 yawor

Do you have the same model as me? I don't mean the VID/PID pair, as these are reused with different products unfortunately.

I believe so, yes. It looks the same as the one in your picture. I bought it off AliExpress and can send you the listing if you want.

xCISACx avatar Nov 24 '25 22:11 xCISACx

The command line I've used is:

./ch57x-keyboard-tool  --vendor-id 0x514c --product-id 0x8850 upload < mapping.yaml

You either need to run this as root or add an udev rules file to give an access from a user or seat. The recommended way, if your distribution is using systemd, is to use UACCESS tag in udev:

cat << EOF | sudo tee /etc/udev/rules.d/60-ch57x-keyboard.rules
SUBSYSTEM=="usb", ATTRS{idVendor}=="514c", ATTRS{idProduct}=="8850", TAG+="uaccess"
EOF

This will create the file /etc/udev/rules.d/60-ch57x-keyboard.rules with a correct rule. After that reload udev rules:

sudo udevadm control --reload && sudo udevadm control --reload-rules

and re-plug the keyboard.

Also just to be sure: you need to connect the keyboard directly with USB cable. It won't work over dongle or BT.

yawor avatar Nov 24 '25 22:11 yawor

Sorry, I forgot to mention I'm on Windows and not Linux Trying ./ch57x-keyboard-tool --vendor-id 0x514c --product-id 0x8850 upload < example-mapping.yaml on the Terminal as administrator gives the error:

At line:1 char:70
+ ... eyboard-tool  --vendor-id 0x514c --product-id 0x8850 upload < mapping ...
+                                                                 ~
The '<' operator is reserved for future use.
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : RedirectionNotSupported

I tried ./ch57x-keyboard-tool --vendor-id 0x514c --product-id 0x8850 upload 16key.yaml which did technically work by not throwing an error but I get this message:

Error: unsupported combination of buttons and knobs count

My configuration has:

rows: 4
columns: 4
knobs: 3

and

layers:
  - buttons:
      # Array of buttons.
      # In horizontal orientations it's `rows` rows `columns` buttons each.
      # In vertical: `columns` rows `rows` buttons each.
      # Each entry is either a sequence of 'chords' or a mouse event.
      # A chord is a combination of one key with optional modifiers,
      # like 'b', 'ctrl-alt-a' or 'win-rctrl-backspace'.
      # It can also be just modifiers without a key: 'ctrl-alt'.
      # You may combine up to 5 chords into a sequence using commas: 'ctrl-v,ctrl-c'.
      # Arbitrary HID usage codes (decimal) may be given like this: '<101>'.
      # See https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf (section 10)
      # for HID usage code list.
      - ["F13", "F14", "F15", "F16"]
      - ["F17", "F18", "F19", "F20"]
      - ["F21", "F22", "F23", "F24"]
      - ["b", "c", "d", "e"]
    knobs:
      # Knobs are listed from left to right if horizontal.
      # Knobs are listed from top to bottom if vertical.
      # Knobs can be rotated counter-clockwise (ccw) or clockwise (cw)
      # and pressed down.
      - ccw: "wheelup"
        press: "click"
        cw: "wheeldown"
      - ccw: "shift-wheelup"
        press: "shift-click"
        cw: "shift-wheeldown"
      - ccw: "volumedown"
        press: "mute"
        cw: "volumeup"

I'm not sure what I did wrong. I've used this program before to configure a keyboard with a different layout successfully, but this one is failing. Is my configuration file wrong?

validate returns "config is valid 👌"

xCISACx avatar Nov 24 '25 22:11 xCISACx

Are you sure that you've checked out and built from my PR?

yawor avatar Nov 24 '25 23:11 yawor

Are you sure that you've checked out and built from my PR?

I went to https://github.com/yawor/ch57x-keyboard-tool/tree/k8850_4x4, downloaded the source code and built it using cargo install ch57x-keyboard-tool It created an .exe file in the .cargo/bin folder, which I moved into the ch57x-keyboard-tool-k8850_4x4 folder that also contained my yaml configuration. Is this not how you do it? I've never built a rust application before, so forgive my lack of knowledge.

xCISACx avatar Nov 24 '25 23:11 xCISACx

How did you download the code? Did you download the ZIP file or did you use git clone? Check the source code if you have the k8850_4x4.rs file in the src/keyboard directory. I don't use Windows so I can't tell you for sure, but the build process shouldn't be any different than on Linux. Obviously you don't need the udev files on Windows :).

yawor avatar Nov 25 '25 08:11 yawor

Also I see that my PR is now quite behind this repo. I'll try to rebase it on the current code when I have some time. There are some new features like mouse movement for example.

yawor avatar Nov 25 '25 09:11 yawor

How did you download the code? Did you download the ZIP file or did you use git clone? Check the source code if you have the k8850_4x4.rs file in the src/keyboard directory. I don't use Windows so I can't tell you for sure, but the build process shouldn't be any different than on Linux. Obviously you don't need the udev files on Windows :).

I downloaded the zip directly and yes, I do have that file so I do believe it's the right version.

Even if it is behind, should it still work for my macropad without that error? I tried to search the source code for the error to see if I could understand what's causing it but can't find it there either.

xCISACx avatar Nov 25 '25 14:11 xCISACx

Yes, it should. I've just checked and I don't have any uncommitted or not pushed changes locally, so it's a complete code in my repo on the k8850_4x4 branch.

I'm building it by simply executing cargo build and the resulting binary is in target/debug directory.

Here's an example yaml file:

orientation: normal

rows: 4
columns: 4
knobs: 3

layers:
  - buttons:
      - ["win-1", "win-2", "win-3", "win-4"]
      - ["win-5", "win-6", "win-7", "win-8"]
      - ["shift", "shift", "shift", "shift"]
      - ["shift", "shift", "shift", "shift"]
    knobs:
      - ccw: wheelup
        cw: wheeldown
        press: click
      - ccw: "shift"
        cw: "shift"
        press: "shift"
      - ccw: volumedown
        cw: volumeup
        press: mute

I didn't have time yet to make something more advanced :D. I'm using shift as a filler.

yawor avatar Nov 25 '25 14:11 yawor

Yes, it should. I've just checked and I don't have any uncommitted or not pushed changes locally, so it's a complete code in my repo on the k8850_4x4 branch.

I'm building it by simply executing cargo build and the resulting binary is in target/debug directory.

Here's an example yaml file:

orientation: normal

rows: 4 columns: 4 knobs: 3

layers:

  • buttons:
    • ["win-1", "win-2", "win-3", "win-4"]
    • ["win-5", "win-6", "win-7", "win-8"]
    • ["shift", "shift", "shift", "shift"]
    • ["shift", "shift", "shift", "shift"] knobs:
    • ccw: wheelup cw: wheeldown press: click
    • ccw: "shift" cw: "shift" press: "shift"
    • ccw: volumedown cw: volumeup press: mute I didn't have time yet to make something more advanced :D. I'm using shift as a filler.

I just tested your yaml file on my current build and it still gives me the same error hah

Error: unsupported combination of buttons and knobs count

Any idea where this error is coming from? I can't find it in the source code.

UPDATE: I tried to build it with cargo build instead of cargo install ch57x-keyboard-tool and it doesn't throw the error anymore! I can finally map my macropad! Thank you so much for making this version <3

xCISACx avatar Nov 25 '25 14:11 xCISACx

Awesome. I'm glad I could help :).

What command line did you use before to build the program if not cargo build? Did you use rustc directly? Can you paste the command line you've used?

--edit--

Ah, I've read your comment too fast and I didn't notice that you've been using cargo install ch57x-keyboard-tool. So this was your issue, because cargo install doesn't care at all about the source code you've downloaded. It gets the crate (that's how rust packages are called) from the https://crates.io/ repository, so you've been installing the official release.

yawor avatar Nov 25 '25 14:11 yawor

Awesome. I'm glad I could help :).

What command line did you use before to build the program if not cargo build? Did you use rustc directly? Can you paste the command line you've used?

--edit--

Ah, I've read your comment too fast and I didn't notice that you've been using cargo install ch57x-keyboard-tool. So this was your issue, because cargo install doesn't care at all about the source code you've downloaded. It gets the crate (that's how rust packages are called) from the https://crates.io/ repository, so you've been installing the official release.

Ahh, that makes a lot of sense... Sorry! Now I know how to properly build it, so thanks again :)

xCISACx avatar Nov 25 '25 19:11 xCISACx