feat: implement initial cyborg multi-action button mappings
- Added support for Single, Long, and Double press actions in libazeron. - Mapped Cyborg surgical command IDs (0x20F6, 0x20F8, 0x204A). - Updated azeron-cli to support --long and --double mapping flags. - Updated protocol documentation with newly discovered Cyborg commands. - Added TODO.md for remaining joystick and timing tasks.
This commit is contained in:
20
TODO.md
Normal file
20
TODO.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Azeron Cyborg Linux Support - Remaining Tasks
|
||||||
|
|
||||||
|
## Protocol Implementation
|
||||||
|
- [ ] **Joystick Bulk Write (`0x26EC`)**: Implement `azeron_protocol_set_stick_config` using the new bulk write format.
|
||||||
|
- Map mode byte (index 3).
|
||||||
|
- Map stick angle (index 8).
|
||||||
|
- Map deadzone/sensitivity.
|
||||||
|
- [ ] **Global Timings (`0x2000`)**: Implement a function to set Long Press and Double Click delays.
|
||||||
|
- [ ] **Verify Read Configuration**: Confirm if `0x26FB` works for Cyborg or if a new read command is required (Heartbeat showed `0x12EA/EB` pairs).
|
||||||
|
- [ ] **Surgical Button Persistence**: Verify if `0x204B` (Commit) is sufficient for all surgical updates or if `0x26ED` is needed.
|
||||||
|
|
||||||
|
## CLI Enhancements
|
||||||
|
- [ ] **Joystick Commands**: Add `set-stick-mode` and `set-stick-angle`.
|
||||||
|
- [ ] **Timing Commands**: Add `set-delays --long <ms> --double <ms>`.
|
||||||
|
- [ ] **Action Feedback**: Update `show-mappings` to display all three actions (Single, Long, Double) per button.
|
||||||
|
|
||||||
|
## Testing & Validation
|
||||||
|
- [ ] **Linux Verification**: Test all surgical updates (`0x20F6/F8/4A`) using `libusb` on Linux.
|
||||||
|
- [ ] **Persistence Check**: Ensure settings survive a device power cycle.
|
||||||
|
- [ ] **Input Monitoring**: Verify that Linux input events (EV_KEY) correctly match the new mappings.
|
||||||
@@ -285,10 +285,14 @@ int cmd_map_button(int argc, char *argv[])
|
|||||||
int device_index = 0;
|
int device_index = 0;
|
||||||
int button_id;
|
int button_id;
|
||||||
const char *key_name;
|
const char *key_name;
|
||||||
|
enum azeron_action_type action_type = AZERON_ACTION_SINGLE;
|
||||||
|
|
||||||
if (argc < 3) {
|
if (argc < 3) {
|
||||||
fprintf(stderr, "Usage: %s map-button <button-id> <key> [options]\n", argv[0]);
|
fprintf(stderr, "Usage: %s map-button <button-id> <key> [options]\n", argv[0]);
|
||||||
fprintf(stderr, "Example: %s map-button 1 KEY_W\n", argv[0]);
|
fprintf(stderr, "Options:\n");
|
||||||
|
fprintf(stderr, " --long Map to long press\n");
|
||||||
|
fprintf(stderr, " --double Map to double press\n");
|
||||||
|
fprintf(stderr, "Example: %s map-button 1 KEY_W --long\n", argv[0]);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,6 +307,8 @@ int cmd_map_button(int argc, char *argv[])
|
|||||||
/* Parse options */
|
/* Parse options */
|
||||||
static struct option long_options[] = {
|
static struct option long_options[] = {
|
||||||
{"device", required_argument, 0, 'd'},
|
{"device", required_argument, 0, 'd'},
|
||||||
|
{"long", no_argument, 0, 'l'},
|
||||||
|
{"double", no_argument, 0, 'b'},
|
||||||
{"help", no_argument, 0, 'h'},
|
{"help", no_argument, 0, 'h'},
|
||||||
{0, 0, 0, 0}
|
{0, 0, 0, 0}
|
||||||
};
|
};
|
||||||
@@ -310,11 +316,17 @@ int cmd_map_button(int argc, char *argv[])
|
|||||||
int opt;
|
int opt;
|
||||||
int option_index = 0;
|
int option_index = 0;
|
||||||
optind = 3; /* Skip command, button-id and key */
|
optind = 3; /* Skip command, button-id and key */
|
||||||
while ((opt = getopt_long(argc, argv, "d:h", long_options, &option_index)) != -1) {
|
while ((opt = getopt_long(argc, argv, "d:lbh", long_options, &option_index)) != -1) {
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 'd':
|
case 'd':
|
||||||
device_index = atoi(optarg);
|
device_index = atoi(optarg);
|
||||||
break;
|
break;
|
||||||
|
case 'l':
|
||||||
|
action_type = AZERON_ACTION_LONG;
|
||||||
|
break;
|
||||||
|
case 'b':
|
||||||
|
action_type = AZERON_ACTION_DOUBLE;
|
||||||
|
break;
|
||||||
case 'h':
|
case 'h':
|
||||||
printf("Usage: %s map-button <button-id> <key> [options]\n", argv[0]);
|
printf("Usage: %s map-button <button-id> <key> [options]\n", argv[0]);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -339,6 +351,7 @@ int cmd_map_button(int argc, char *argv[])
|
|||||||
|
|
||||||
/* Prepare mapping */
|
/* Prepare mapping */
|
||||||
mapping.button_id = (uint8_t)button_id;
|
mapping.button_id = (uint8_t)button_id;
|
||||||
|
mapping.action = action_type;
|
||||||
mapping.type = AZERON_BTN_KEYBOARD;
|
mapping.type = AZERON_BTN_KEYBOARD;
|
||||||
|
|
||||||
int keycode = azeron_keycode_from_string(key_name);
|
int keycode = azeron_keycode_from_string(key_name);
|
||||||
@@ -356,7 +369,13 @@ int cmd_map_button(int argc, char *argv[])
|
|||||||
mapping.key_code = (uint16_t)keycode;
|
mapping.key_code = (uint16_t)keycode;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("Mapping button %d to key 0x%02x (%s)...\n", button_id + 1, mapping.key_code, key_name);
|
const char *action_str = "single";
|
||||||
|
if (action_type == AZERON_ACTION_LONG) action_str = "long";
|
||||||
|
else if (action_type == AZERON_ACTION_DOUBLE) action_str = "double";
|
||||||
|
|
||||||
|
printf("Mapping button %d (%s press) to key 0x%02x (%s)...\n",
|
||||||
|
button_id + 1, action_str, mapping.key_code, key_name);
|
||||||
|
|
||||||
ret = azeron_device_set_button_mapping(device, &mapping);
|
ret = azeron_device_set_button_mapping(device, &mapping);
|
||||||
if (ret != AZERON_SUCCESS) {
|
if (ret != AZERON_SUCCESS) {
|
||||||
fprintf(stderr, "Failed to set button mapping: %s\n", azeron_error_string(ret));
|
fprintf(stderr, "Failed to set button mapping: %s\n", azeron_error_string(ret));
|
||||||
|
|||||||
18379
captures/ana-joy-capture.json
Normal file
18379
captures/ana-joy-capture.json
Normal file
File diff suppressed because it is too large
Load Diff
5396
captures/azeron-capture.json
Normal file
5396
captures/azeron-capture.json
Normal file
File diff suppressed because it is too large
Load Diff
1797
captures/hid-to-ana.json
Normal file
1797
captures/hid-to-ana.json
Normal file
File diff suppressed because it is too large
Load Diff
11303
captures/joy-capture.json
Normal file
11303
captures/joy-capture.json
Normal file
File diff suppressed because it is too large
Load Diff
122
captures/long_press.txt
Normal file
122
captures/long_press.txt
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
# Packet returned when performing a long press
|
||||||
|
Frame 31: Packet, 91 bytes on wire (728 bits), 91 bytes captured (728 bits) on interface \\.\USBPcap2, id 0
|
||||||
|
USB URB
|
||||||
|
[Source: 2.4.5]
|
||||||
|
[Destination: host]
|
||||||
|
USBPcap pseudoheader length: 27
|
||||||
|
IRP ID: 0xffffe20f9867c420
|
||||||
|
IRP USBD_STATUS: USBD_STATUS_SUCCESS (0x00000000)
|
||||||
|
URB Function: URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER (0x0009)
|
||||||
|
IRP information: 0x01, Direction: PDO -> FDO
|
||||||
|
URB bus id: 2
|
||||||
|
Device address: 4
|
||||||
|
Endpoint: 0x85, Direction: IN
|
||||||
|
URB transfer type: URB_INTERRUPT (0x01)
|
||||||
|
Packet Data Length: 64
|
||||||
|
[bInterfaceClass: HID (0x03)]
|
||||||
|
HID Data: 42505f31320d0a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
|
||||||
|
Frame 32: Packet, 27 bytes on wire (216 bits), 27 bytes captured (216 bits) on interface \\.\USBPcap2, id 0
|
||||||
|
USB URB
|
||||||
|
[Source: host]
|
||||||
|
[Destination: 2.4.5]
|
||||||
|
USBPcap pseudoheader length: 27
|
||||||
|
IRP ID: 0xffffe20f9867c420
|
||||||
|
IRP USBD_STATUS: USBD_STATUS_SUCCESS (0x00000000)
|
||||||
|
URB Function: URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER (0x0009)
|
||||||
|
IRP information: 0x00, Direction: FDO -> PDO
|
||||||
|
URB bus id: 2
|
||||||
|
Device address: 4
|
||||||
|
Endpoint: 0x85, Direction: IN
|
||||||
|
URB transfer type: URB_INTERRUPT (0x01)
|
||||||
|
Packet Data Length: 0
|
||||||
|
[Response in: 40]
|
||||||
|
[bInterfaceClass: HID (0x03)]
|
||||||
|
|
||||||
|
Frame 33: Packet, 91 bytes on wire (728 bits), 91 bytes captured (728 bits) on interface \\.\USBPcap2, id 0
|
||||||
|
USB URB
|
||||||
|
[Source: 2.4.5]
|
||||||
|
[Destination: host]
|
||||||
|
USBPcap pseudoheader length: 27
|
||||||
|
IRP ID: 0xffffe20f9861d7e0
|
||||||
|
IRP USBD_STATUS: USBD_STATUS_SUCCESS (0x00000000)
|
||||||
|
URB Function: URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER (0x0009)
|
||||||
|
IRP information: 0x01, Direction: PDO -> FDO
|
||||||
|
URB bus id: 2
|
||||||
|
Device address: 4
|
||||||
|
Endpoint: 0x85, Direction: IN
|
||||||
|
URB transfer type: URB_INTERRUPT (0x01)
|
||||||
|
Packet Data Length: 64
|
||||||
|
[bInterfaceClass: HID (0x03)]
|
||||||
|
HID Data: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
|
||||||
|
Frame 34: Packet, 27 bytes on wire (216 bits), 27 bytes captured (216 bits) on interface \\.\USBPcap2, id 0
|
||||||
|
USB URB
|
||||||
|
[Source: host]
|
||||||
|
[Destination: 2.4.5]
|
||||||
|
USBPcap pseudoheader length: 27
|
||||||
|
IRP ID: 0xffffe20f9861d7e0
|
||||||
|
IRP USBD_STATUS: USBD_STATUS_SUCCESS (0x00000000)
|
||||||
|
URB Function: URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER (0x0009)
|
||||||
|
IRP information: 0x00, Direction: FDO -> PDO
|
||||||
|
URB bus id: 2
|
||||||
|
Device address: 4
|
||||||
|
Endpoint: 0x85, Direction: IN
|
||||||
|
URB transfer type: URB_INTERRUPT (0x01)
|
||||||
|
Packet Data Length: 0
|
||||||
|
[Response in: 45]
|
||||||
|
[bInterfaceClass: HID (0x03)]
|
||||||
|
|
||||||
|
Frame 35: Packet, 91 bytes on wire (728 bits), 91 bytes captured (728 bits) on interface \\.\USBPcap2, id 0
|
||||||
|
USB URB
|
||||||
|
[Source: host]
|
||||||
|
[Destination: 2.4.6]
|
||||||
|
USBPcap pseudoheader length: 27
|
||||||
|
IRP ID: 0xffffe20fb26e3010
|
||||||
|
IRP USBD_STATUS: USBD_STATUS_SUCCESS (0x00000000)
|
||||||
|
URB Function: URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER (0x0009)
|
||||||
|
IRP information: 0x00, Direction: FDO -> PDO
|
||||||
|
URB bus id: 2
|
||||||
|
Device address: 4
|
||||||
|
Endpoint: 0x06, Direction: OUT
|
||||||
|
URB transfer type: URB_INTERRUPT (0x01)
|
||||||
|
Packet Data Length: 64
|
||||||
|
[Response in: 36]
|
||||||
|
[bInterfaceClass: HID (0x03)]
|
||||||
|
HID Data: 5e327e48690a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
|
||||||
|
Frame 36: Packet, 27 bytes on wire (216 bits), 27 bytes captured (216 bits) on interface \\.\USBPcap2, id 0
|
||||||
|
USB URB
|
||||||
|
[Source: 2.4.6]
|
||||||
|
[Destination: host]
|
||||||
|
USBPcap pseudoheader length: 27
|
||||||
|
IRP ID: 0xffffe20fb26e3010
|
||||||
|
IRP USBD_STATUS: USBD_STATUS_SUCCESS (0x00000000)
|
||||||
|
URB Function: URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER (0x0009)
|
||||||
|
IRP information: 0x01, Direction: PDO -> FDO
|
||||||
|
URB bus id: 2
|
||||||
|
Device address: 4
|
||||||
|
Endpoint: 0x06, Direction: OUT
|
||||||
|
URB transfer type: URB_INTERRUPT (0x01)
|
||||||
|
Packet Data Length: 0
|
||||||
|
[Request in: 35]
|
||||||
|
[Time from request: 4.877000 milliseconds]
|
||||||
|
[bInterfaceClass: HID (0x03)]
|
||||||
|
|
||||||
|
Frame 37: Packet, 91 bytes on wire (728 bits), 91 bytes captured (728 bits) on interface \\.\USBPcap2, id 0
|
||||||
|
USB URB
|
||||||
|
[Source: host]
|
||||||
|
[Destination: 2.4.6]
|
||||||
|
USBPcap pseudoheader length: 27
|
||||||
|
IRP ID: 0xffffe20fa1bb2010
|
||||||
|
IRP USBD_STATUS: USBD_STATUS_SUCCESS (0x00000000)
|
||||||
|
URB Function: URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER (0x0009)
|
||||||
|
IRP information: 0x00, Direction: FDO -> PDO
|
||||||
|
URB bus id: 2
|
||||||
|
Device address: 4
|
||||||
|
Endpoint: 0x06, Direction: OUT
|
||||||
|
URB transfer type: URB_INTERRUPT (0x01)
|
||||||
|
Packet Data Length: 64
|
||||||
|
[Response in: 44]
|
||||||
|
[bInterfaceClass: HID (0x03)]
|
||||||
|
HID Data: 001520b10101150101000c0000000001000000000000f01b00000000000000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
7
captures/usb_info.txt
Normal file
7
captures/usb_info.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Output from "Get-PnpDevice -PresentOnly | Where-Object { $_.InstanceId -match '^USB' } | Select-Object FriendlyName, InstanceId"
|
||||||
|
USB Input Device USB\VID_16D0&PID_113C&MI_04\9&6389280&1&0004
|
||||||
|
USB Input Device USB\VID_16D0&PID_113C&MI_03\9&6389280&1&0003
|
||||||
|
USB Input Device USB\VID_16D0&PID_113C&MI_02\9&6389280&1&0002
|
||||||
|
USB Input Device USB\VID_16D0&PID_113C&MI_01\9&6389280&1&0001
|
||||||
|
Xbox 360 Controller for Windows USB\VID_16D0&PID_113C&MI_00\9&6389280&1&0000
|
||||||
|
USB Composite Device USB\VID_16D0&PID_113C\208332B93553
|
||||||
146
docs/protocol.md
146
docs/protocol.md
@@ -51,7 +51,7 @@ This document describes the USB configuration protocol for the Azeron Cyborg key
|
|||||||
- **Transfer Type**: Interrupt transfers (0x01)
|
- **Transfer Type**: Interrupt transfers (0x01)
|
||||||
- **Packet Format**: Fixed 64-byte HID reports
|
- **Packet Format**: Fixed 64-byte HID reports
|
||||||
- **Command Structure**: Request-response pattern
|
- **Command Structure**: Request-response pattern
|
||||||
- **Endianness**: Big-endian for multi-byte values
|
- **Endianness**: Big-endian for multi-byte values (except offsets)
|
||||||
|
|
||||||
### Packet Format
|
### Packet Format
|
||||||
|
|
||||||
@@ -59,135 +59,53 @@ All configuration packets follow this structure:
|
|||||||
|
|
||||||
```
|
```
|
||||||
Byte 0: Flags/Type (0x00=request, 0x01=response)
|
Byte 0: Flags/Type (0x00=request, 0x01=response)
|
||||||
Byte 1: Reserved (0x00)
|
Byte 1: Reserved/Length (0x3a for config writes)
|
||||||
Bytes 2-3: Command ID (big-endian)
|
Bytes 2-3: Command ID (big-endian)
|
||||||
Bytes 4-5: Operation type and parameters
|
Bytes 4-5: Operation type and parameters
|
||||||
Bytes 6-63: Data payload (varies by command)
|
Bytes 6-63: Data payload (varies by command)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Command Reference
|
### Command Reference (Cyborg Model)
|
||||||
|
|
||||||
#### 0x122a - Get Status (Heartbeat)
|
#### 0x122a - Get Status (Heartbeat)
|
||||||
**Purpose:** Periodic device status check (occurs every 1-2 seconds automatically)
|
**Purpose:** Periodic device status check.
|
||||||
|
|
||||||
**Request:**
|
#### 0x2000 - Set Global Settings
|
||||||
```
|
**Purpose:** Configure timing delays for actions.
|
||||||
OUT 0x06: 0000122a010100000000000000000000...
|
- **Bytes 60-61 (Payload):** Long Press Delay (ms, big-endian, e.g., `01f4` = 500ms)
|
||||||
││ ││││└────┬────┘└────┬──────┘
|
- **Bytes 62-63 (Payload):** Double Press Delay (ms, big-endian, e.g., `00c8` = 200ms)
|
||||||
││ ││││ │ └─ Payload (zeros)
|
|
||||||
││ ││││ └─ Operation: 0x0101 (read status)
|
|
||||||
││ ││└─────── Command ID: 0x122a
|
|
||||||
││ └──────── Reserved: 0x00
|
|
||||||
└─────────── Type: 0x00 (request)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response:**
|
#### 0x20F6 - Set Single Press
|
||||||
```
|
#### 0x20F8 - Set Long Press
|
||||||
IN 0x85: 0100122a010100013f4f691b0700ff060606...
|
#### 0x204A - Set Double Press
|
||||||
││ ││││ │└────┬──────┘└────┬──────┘
|
**Purpose:** Surgical update for a specific button action.
|
||||||
││ ││││ │ └─ Device data starts here
|
- **Byte 10 (Payload):** Button ID (0-based)
|
||||||
││ ││││ └─ Status: 0x01 (success)
|
- **Byte 22 (Payload):** Key Type (0xf0 = Keyboard)
|
||||||
││ ││└─────── Command ID: 0x122a
|
- **Byte 23 (Payload):** Key Code (HID)
|
||||||
││ └──────── Reserved: 0x00
|
|
||||||
└─────────── Type: 0x01 (response)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Device Data Bytes:**
|
#### 0x204B - Commit Button Changes
|
||||||
- Bytes 6-7: Profile number (0x0001 = Profile 1)
|
**Purpose:** Makes surgical updates (`0x20F6/F8/4A`) active.
|
||||||
- Bytes 8-11: Joystick X/Y position
|
|
||||||
- Bytes 12-15: Button states (bitmask)
|
#### 0x26EC - Bulk Write Profile
|
||||||
- Bytes 16-19: Analog stick settings
|
**Purpose:** Writes a complete profile block.
|
||||||
- Bytes 20-23: LED color (RGB)
|
- **Byte 3 (Payload):** Joystick Mode (0x00 = Analog, 0x01 = WASD)
|
||||||
- Bytes 24-63: Additional status data
|
- **Byte 8 (Payload):** Stick Angle (degrees)
|
||||||
|
|
||||||
|
#### 0x26ED - Commit Bulk Write
|
||||||
|
**Purpose:** Persists bulk changes to EEPROM.
|
||||||
|
|
||||||
|
### Command Reference (Classic Model)
|
||||||
|
|
||||||
|
*Note: These commands are used by older Azeron versions and may differ from Cyborg.*
|
||||||
|
|
||||||
#### 0x12C8 - Read Configuration
|
#### 0x12C8 - Read Configuration
|
||||||
**Purpose:** Read full device configuration
|
**Purpose:** Read full device configuration.
|
||||||
|
|
||||||
**Request:**
|
|
||||||
```
|
|
||||||
OUT 0x06: 000012c801010000000000000000000000...
|
|
||||||
││ ││││└────┬────┘
|
|
||||||
││ ││││ └─ Operation: 0x0101 (read config)
|
|
||||||
││ ││└─────── Command ID: 0x12c8
|
|
||||||
└──────────── Standard header
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```
|
|
||||||
IN 0x85: 010012c8010101013f4f691b0700ff060606...
|
|
||||||
││ ││││ │└────┬──────┘
|
|
||||||
││ ││││ │ └─ Full configuration data (58 bytes)
|
|
||||||
││ ││││ └─ Status: 0x01 (success)
|
|
||||||
└──────────── Standard header
|
|
||||||
```
|
|
||||||
|
|
||||||
**Configuration Data Structure:**
|
|
||||||
- Bytes 6-9: Device signature/version
|
|
||||||
- Bytes 10-13: Profile 0 settings
|
|
||||||
- Bytes 14-17: Profile 1 settings
|
|
||||||
- Bytes 18-21: Profile 2 settings
|
|
||||||
- Bytes 22-25: Button mapping offsets
|
|
||||||
- Bytes 26-57: Button mappings (30 buttons × 1 byte each)
|
|
||||||
- Bytes 58-61: Joystick configuration
|
|
||||||
- Bytes 62-63: Checksum
|
|
||||||
|
|
||||||
#### 0x26FC - Write Profile Data
|
#### 0x26FC - Write Profile Data
|
||||||
**Purpose:** Write profile configuration to device (does not persist)
|
**Purpose:** Write profile configuration to device (does not persist).
|
||||||
|
|
||||||
**Request:**
|
|
||||||
```
|
|
||||||
OUT 0x06: 003a26fc020139040102000000fff40003f01a0000...
|
|
||||||
││ ││││└────┬────┘└────┬──────┘└────┬──────┘
|
|
||||||
││ ││││ │ │ └─ Button/key codes
|
|
||||||
││ ││││ │ └─ Offset: 0x0439 (1081)
|
|
||||||
││ ││││ └─ Operation: 0x0201 (write profile 1)
|
|
||||||
││ ││└─────── Command ID: 0x26fc
|
|
||||||
└──────────── Length: 0x003a (58 bytes)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```
|
|
||||||
IN 0x85: 000026fc010100013f4f691b0700ff060606...
|
|
||||||
Standard header + status + device data
|
|
||||||
```
|
|
||||||
|
|
||||||
**Profile Data Bytes:**
|
|
||||||
- Bytes 6-9: Offset/address (0x00000439)
|
|
||||||
- Bytes 10-13: Profile header (0x01020000)
|
|
||||||
- Bytes 14-17: LED color (0xfff40003 = RGBA)
|
|
||||||
- Bytes 18-21: Button 1 mapping (0xf01a0000)
|
|
||||||
- Bytes 22-25: Button 2 mapping (0xf0070000)
|
|
||||||
- ... (continues for all 30 buttons)
|
|
||||||
- Bytes 58-61: Joystick settings
|
|
||||||
- Bytes 62-63: Reserved
|
|
||||||
|
|
||||||
**Button Mapping Format:**
|
|
||||||
```
|
|
||||||
Byte 0: Key type (0xf0 = keyboard, 0xf1 = mouse, 0xf2 = gamepad, 0xf3 = macro)
|
|
||||||
Byte 1: Key code (USB HID code)
|
|
||||||
Byte 2: Modifier flags (shift, ctrl, alt)
|
|
||||||
Byte 3: Reserved
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 0x26FD - Save Profile
|
#### 0x26FD - Save Profile
|
||||||
**Purpose:** Commit profile to device EEPROM (persists after power-off)
|
**Purpose:** Commit profile to device EEPROM.
|
||||||
|
|
||||||
**Request:**
|
|
||||||
```
|
|
||||||
OUT 0x06: 003a26fd020201000000000000000000000000...
|
|
||||||
││ ││││└────┬────┘
|
|
||||||
││ ││││ └─ Operation: 0x0202 (save profile 1)
|
|
||||||
││ ││└─────── Command ID: 0x26fd
|
|
||||||
└──────────── Length: 0x003a (58 bytes, mostly zeros)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```
|
|
||||||
IN 0x85: 000026fd010100013f4f691b0700ff060606...
|
|
||||||
Standard header + confirmation
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note:** The save command echoes the write command structure but with operation 0x0202 and minimal data payload. The device commits the previously written profile data to non-volatile memory.
|
|
||||||
|
|
||||||
## Button Mapping Reference
|
## Button Mapping Reference
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,13 @@ enum azeron_button_type {
|
|||||||
AZERON_BTN_LAYER_SWITCH,
|
AZERON_BTN_LAYER_SWITCH,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Button action types (for Cyborg model) */
|
||||||
|
enum azeron_action_type {
|
||||||
|
AZERON_ACTION_SINGLE = 0,
|
||||||
|
AZERON_ACTION_LONG,
|
||||||
|
AZERON_ACTION_DOUBLE,
|
||||||
|
};
|
||||||
|
|
||||||
/* Analog stick modes */
|
/* Analog stick modes */
|
||||||
enum azeron_stick_mode {
|
enum azeron_stick_mode {
|
||||||
AZERON_STICK_ANALOG = 0,
|
AZERON_STICK_ANALOG = 0,
|
||||||
@@ -67,6 +74,7 @@ struct azeron_device_info {
|
|||||||
/* Button mapping */
|
/* Button mapping */
|
||||||
struct azeron_button_mapping {
|
struct azeron_button_mapping {
|
||||||
uint8_t button_id;
|
uint8_t button_id;
|
||||||
|
enum azeron_action_type action;
|
||||||
enum azeron_button_type type;
|
enum azeron_button_type type;
|
||||||
uint16_t key_code; /* Linux input event code */
|
uint16_t key_code; /* Linux input event code */
|
||||||
char *macro; /* For macro type */
|
char *macro; /* For macro type */
|
||||||
|
|||||||
@@ -19,8 +19,15 @@
|
|||||||
/* Command IDs */
|
/* Command IDs */
|
||||||
#define AZERON_CMD_STATUS 0x122a
|
#define AZERON_CMD_STATUS 0x122a
|
||||||
#define AZERON_CMD_READ_CONFIG 0x26FB
|
#define AZERON_CMD_READ_CONFIG 0x26FB
|
||||||
#define AZERON_CMD_WRITE_PROFILE 0x26FC
|
#define AZERON_CMD_WRITE_PROFILE 0x26EC
|
||||||
#define AZERON_CMD_SAVE_PROFILE 0x26FD
|
#define AZERON_CMD_SAVE_PROFILE 0x26ED
|
||||||
|
|
||||||
|
/* Cyborg Specific Surgical Commands */
|
||||||
|
#define AZERON_CMD_SET_SINGLE 0x20F6
|
||||||
|
#define AZERON_CMD_SET_LONG 0x20F8
|
||||||
|
#define AZERON_CMD_SET_DOUBLE 0x204A
|
||||||
|
#define AZERON_CMD_COMMIT_BTN 0x204B
|
||||||
|
#define AZERON_CMD_SET_GLOBAL 0x2000
|
||||||
|
|
||||||
/* Operation types */
|
/* Operation types */
|
||||||
#define AZERON_OP_READ_STATUS 0x0101
|
#define AZERON_OP_READ_STATUS 0x0101
|
||||||
@@ -31,7 +38,7 @@
|
|||||||
#define AZERON_OP_SAVE_PROFILE 0x0202
|
#define AZERON_OP_SAVE_PROFILE 0x0202
|
||||||
|
|
||||||
/* Offsets for profile data */
|
/* Offsets for profile data */
|
||||||
#define AZERON_PROFILE_BASE_OFFSET 0x0439
|
#define AZERON_PROFILE_BASE_OFFSET 0x0339
|
||||||
#define AZERON_BUTTON_MAPPING_SIZE 4
|
#define AZERON_BUTTON_MAPPING_SIZE 4
|
||||||
#define AZERON_STICK_CONFIG_OFFSET 58
|
#define AZERON_STICK_CONFIG_OFFSET 58
|
||||||
|
|
||||||
@@ -277,53 +284,56 @@ int azeron_protocol_set_button_mapping(struct azeron_device *device,
|
|||||||
uint8_t response[64];
|
uint8_t response[64];
|
||||||
size_t response_len;
|
size_t response_len;
|
||||||
uint8_t data[58] = {0};
|
uint8_t data[58] = {0};
|
||||||
uint32_t offset;
|
uint16_t command;
|
||||||
uint16_t operation;
|
|
||||||
uint8_t active_profile;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (!device || !mapping) {
|
if (!device || !mapping) {
|
||||||
return AZERON_ERROR_INVALID_PARAM;
|
return AZERON_ERROR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get active profile to know which operation to use */
|
/* Select surgical command based on action type */
|
||||||
ret = azeron_protocol_get_active_profile(device, &active_profile);
|
switch (mapping->action) {
|
||||||
if (ret != AZERON_SUCCESS) {
|
case AZERON_ACTION_SINGLE: command = AZERON_CMD_SET_SINGLE; break;
|
||||||
return ret;
|
case AZERON_ACTION_LONG: command = AZERON_CMD_SET_LONG; break;
|
||||||
|
case AZERON_ACTION_DOUBLE: command = AZERON_CMD_SET_DOUBLE; break;
|
||||||
|
default: return AZERON_ERROR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (active_profile) {
|
/* Surgical payload structure for Cyborg:
|
||||||
case 0: operation = AZERON_OP_WRITE_PROFILE_0; break;
|
* Byte 4: 0x01 (fixed?)
|
||||||
case 1: operation = AZERON_OP_WRITE_PROFILE_1; break;
|
* Byte 5: 0x01 (fixed?)
|
||||||
case 2: operation = AZERON_OP_WRITE_PROFILE_2; break;
|
* Byte 10: Button ID
|
||||||
default: return AZERON_ERROR_PROTOCOL;
|
* Byte 11: Sub-action index? (usually 0x01 or 0x02)
|
||||||
}
|
* Byte 22: Key Type
|
||||||
|
* Byte 23: Key Code
|
||||||
/* Calculate offset: Base + 8 (header + color) + (ButtonID * 4) */
|
|
||||||
offset = AZERON_PROFILE_BASE_OFFSET + 8 + (mapping->button_id * AZERON_BUTTON_MAPPING_SIZE);
|
|
||||||
|
|
||||||
/* Data payload for 0x26FC:
|
|
||||||
* Bytes 0-3: Offset (LITTLE ENDIAN)
|
|
||||||
* Bytes 4-7: Mapping (4 bytes)
|
|
||||||
*/
|
*/
|
||||||
data[0] = offset & 0xFF;
|
|
||||||
data[1] = (offset >> 8) & 0xFF;
|
data[4] = 0x01;
|
||||||
data[2] = (offset >> 16) & 0xFF;
|
data[10] = mapping->button_id;
|
||||||
data[3] = (offset >> 24) & 0xFF;
|
|
||||||
|
switch (mapping->action) {
|
||||||
|
case AZERON_ACTION_SINGLE: data[11] = 0x01; break;
|
||||||
|
case AZERON_ACTION_LONG: data[11] = 0x00; break; /* As seen in capture */
|
||||||
|
case AZERON_ACTION_DOUBLE: data[11] = 0x00; break; /* As seen in capture */
|
||||||
|
}
|
||||||
|
|
||||||
switch (mapping->type) {
|
switch (mapping->type) {
|
||||||
case AZERON_BTN_KEYBOARD: data[4] = 0xf0; break;
|
case AZERON_BTN_KEYBOARD: data[22] = 0xf0; break;
|
||||||
case AZERON_BTN_MOUSE: data[4] = 0xf1; break;
|
case AZERON_BTN_MOUSE: data[22] = 0xf1; break;
|
||||||
case AZERON_BTN_GAMEPAD: data[4] = 0xf2; break;
|
case AZERON_BTN_GAMEPAD: data[22] = 0xf2; break;
|
||||||
default: return AZERON_ERROR_UNSUPPORTED;
|
default: return AZERON_ERROR_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
data[5] = mapping->key_code & 0xFF;
|
data[23] = mapping->key_code & 0xFF;
|
||||||
data[6] = 0x00; /* No modifiers for now */
|
|
||||||
data[7] = 0x00;
|
|
||||||
|
|
||||||
ret = send_config_command(device, AZERON_CMD_WRITE_PROFILE, operation,
|
ret = send_config_command(device, command, 0x0101,
|
||||||
data, 8, response, &response_len);
|
data, 24, response, &response_len);
|
||||||
|
|
||||||
|
if (ret == AZERON_SUCCESS) {
|
||||||
|
/* Follow up with commitment command to make it live */
|
||||||
|
ret = send_config_command(device, AZERON_CMD_COMMIT_BTN, 0x0101,
|
||||||
|
NULL, 0, response, &response_len);
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user