feat: implement cyborg joystick bulk write, global timings, and enhanced CLI mapping feedback
This commit is contained in:
23
README.md
23
README.md
@@ -45,16 +45,27 @@ sudo make install
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# List connected Azeron devices
|
# List connected Azeron devices
|
||||||
sudo azeron-cli list
|
azeron-cli list
|
||||||
|
|
||||||
# Show current button mappings
|
# Show detailed device info and stick config
|
||||||
sudo azeron-cli show-mappings
|
azeron-cli info
|
||||||
|
|
||||||
|
# Show current button mappings (Single, Long, Double actions)
|
||||||
|
azeron-cli show-mappings
|
||||||
|
|
||||||
# Remap a button (e.g., button 5 to 'W' key)
|
# Remap a button (e.g., button 5 to 'W' key)
|
||||||
sudo azeron-cli map-button 5 KEY_W
|
azeron-cli map-button 5 KEY_W
|
||||||
|
# Map long press action
|
||||||
|
azeron-cli map-button 5 KEY_E --long
|
||||||
|
|
||||||
# Save current configuration to a profile
|
# Configure analog stick
|
||||||
sudo azeron-cli save-profile my_gaming_profile
|
azeron-cli set-stick --mode analog --angle 15 --deadzone 5
|
||||||
|
|
||||||
|
# Set global timing delays
|
||||||
|
azeron-cli set-delays --long 500 --double 200
|
||||||
|
|
||||||
|
# Save current configuration to an onboard profile (0, 1, or 2)
|
||||||
|
azeron-cli save-profile 1
|
||||||
```
|
```
|
||||||
|
|
||||||
### Setting up udev Rules
|
### Setting up udev Rules
|
||||||
|
|||||||
12
TODO.md
12
TODO.md
@@ -1,18 +1,18 @@
|
|||||||
# Azeron Cyborg Linux Support - Remaining Tasks
|
# Azeron Cyborg Linux Support - Remaining Tasks
|
||||||
|
|
||||||
## Protocol Implementation
|
## Protocol Implementation
|
||||||
- [ ] **Joystick Bulk Write (`0x26EC`)**: Implement `azeron_protocol_set_stick_config` using the new bulk write format.
|
- [x] **Joystick Bulk Write (`0x26EC`)**: Implement `azeron_protocol_set_stick_config` using the new bulk write format.
|
||||||
- Map mode byte (index 3).
|
- Map mode byte (index 3).
|
||||||
- Map stick angle (index 8).
|
- Map stick angle (index 8).
|
||||||
- Map deadzone/sensitivity.
|
- Map deadzone/sensitivity.
|
||||||
- [ ] **Global Timings (`0x2000`)**: Implement a function to set Long Press and Double Click delays.
|
- [x] **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).
|
- [x] **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.
|
- [ ] **Surgical Button Persistence**: Verify if `0x204B` (Commit) is sufficient for all surgical updates or if `0x26ED` is needed.
|
||||||
|
|
||||||
## CLI Enhancements
|
## CLI Enhancements
|
||||||
- [ ] **Joystick Commands**: Add `set-stick-mode` and `set-stick-angle`.
|
- [x] **Joystick Commands**: Add `set-stick-mode` and `set-stick-angle`.
|
||||||
- [ ] **Timing Commands**: Add `set-delays --long <ms> --double <ms>`.
|
- [x] **Timing Commands**: Add `set-delays --long <ms> --double <ms>`.
|
||||||
- [ ] **Action Feedback**: Update `show-mappings` to display all three actions (Single, Long, Double) per button.
|
- [x] **Action Feedback**: Update `show-mappings` to display all three actions (Single, Long, Double) per button.
|
||||||
|
|
||||||
## Testing & Validation
|
## Testing & Validation
|
||||||
- [ ] **Linux Verification**: Test all surgical updates (`0x20F6/F8/4A`) using `libusb` on Linux.
|
- [ ] **Linux Verification**: Test all surgical updates (`0x20F6/F8/4A`) using `libusb` on Linux.
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ int cmd_export_config(int argc, char *argv[]);
|
|||||||
int cmd_import_config(int argc, char *argv[]);
|
int cmd_import_config(int argc, char *argv[]);
|
||||||
int cmd_set_stick(int argc, char *argv[]);
|
int cmd_set_stick(int argc, char *argv[]);
|
||||||
int cmd_read_raw(int argc, char *argv[]);
|
int cmd_read_raw(int argc, char *argv[]);
|
||||||
|
int cmd_set_delays(int argc, char *argv[]);
|
||||||
|
|
||||||
/* Command structure */
|
/* Command structure */
|
||||||
struct command {
|
struct command {
|
||||||
@@ -45,6 +46,7 @@ static struct command commands[] = {
|
|||||||
{"export-config", "Export configuration to file", cmd_export_config},
|
{"export-config", "Export configuration to file", cmd_export_config},
|
||||||
{"import-config", "Import configuration from file", cmd_import_config},
|
{"import-config", "Import configuration from file", cmd_import_config},
|
||||||
{"set-stick", "Configure analog stick settings", cmd_set_stick},
|
{"set-stick", "Configure analog stick settings", cmd_set_stick},
|
||||||
|
{"set-delays", "Set global timing delays", cmd_set_delays},
|
||||||
{"read-raw", "Read raw memory from device", cmd_read_raw},
|
{"read-raw", "Read raw memory from device", cmd_read_raw},
|
||||||
{NULL, NULL, NULL}
|
{NULL, NULL, NULL}
|
||||||
};
|
};
|
||||||
@@ -189,6 +191,7 @@ int cmd_info(int argc, char *argv[])
|
|||||||
printf("\nStick Configuration:\n");
|
printf("\nStick Configuration:\n");
|
||||||
printf("--------------------\n");
|
printf("--------------------\n");
|
||||||
printf("Mode: %s\n", azeron_stick_mode_string(stick.mode));
|
printf("Mode: %s\n", azeron_stick_mode_string(stick.mode));
|
||||||
|
printf("Angle: %d\n", stick.angle);
|
||||||
printf("Deadzone: %d%%\n", stick.deadzone);
|
printf("Deadzone: %d%%\n", stick.deadzone);
|
||||||
printf("Sensitivity: %d\n", stick.sensitivity);
|
printf("Sensitivity: %d\n", stick.sensitivity);
|
||||||
printf("Response Curve: %d\n", stick.response_curve);
|
printf("Response Curve: %d\n", stick.response_curve);
|
||||||
@@ -251,23 +254,41 @@ int cmd_show_mappings(int argc, char *argv[])
|
|||||||
|
|
||||||
printf("Button Mappings:\n");
|
printf("Button Mappings:\n");
|
||||||
printf("================\n\n");
|
printf("================\n\n");
|
||||||
printf("%-10s %-15s %s\n", "Button", "Type", "Mapping");
|
printf("%-8s %-15s %-15s %-15s\n", "Button", "Single", "Long", "Double");
|
||||||
printf("%-10s %-15s %s\n", "------", "----", "-------");
|
printf("%-8s %-15s %-15s %-15s\n", "------", "------", "----", "------");
|
||||||
|
|
||||||
for (i = 0; i < 30; i++) {
|
for (i = 0; i < 30; i++) {
|
||||||
struct azeron_button_mapping mapping;
|
struct azeron_button_mapping s_mapping, l_mapping, d_mapping;
|
||||||
ret = azeron_device_get_button_mapping(device, i, &mapping);
|
char s_str[32], l_str[32], d_str[32];
|
||||||
|
|
||||||
|
/* Single */
|
||||||
|
s_mapping.action = AZERON_ACTION_SINGLE;
|
||||||
|
ret = azeron_device_get_button_mapping(device, i, &s_mapping);
|
||||||
if (ret == AZERON_SUCCESS) {
|
if (ret == AZERON_SUCCESS) {
|
||||||
printf("%-10d %-15s 0x%02x\n",
|
const char *key = azeron_keycode_to_string(s_mapping.key_code);
|
||||||
i + 1,
|
if (key) snprintf(s_str, sizeof(s_str), "%s", key);
|
||||||
azeron_button_type_string(mapping.type),
|
else snprintf(s_str, sizeof(s_str), "0x%02x", s_mapping.key_code);
|
||||||
mapping.key_code);
|
} else snprintf(s_str, sizeof(s_str), "err");
|
||||||
} else {
|
|
||||||
printf("%-10d %-15s <error: %s>\n",
|
/* Long */
|
||||||
i + 1,
|
l_mapping.action = AZERON_ACTION_LONG;
|
||||||
"unknown",
|
ret = azeron_device_get_button_mapping(device, i, &l_mapping);
|
||||||
azeron_error_string(ret));
|
if (ret == AZERON_SUCCESS) {
|
||||||
}
|
const char *key = azeron_keycode_to_string(l_mapping.key_code);
|
||||||
|
if (key) snprintf(l_str, sizeof(l_str), "%s", key);
|
||||||
|
else snprintf(l_str, sizeof(l_str), "0x%02x", l_mapping.key_code);
|
||||||
|
} else snprintf(l_str, sizeof(l_str), "err");
|
||||||
|
|
||||||
|
/* Double */
|
||||||
|
d_mapping.action = AZERON_ACTION_DOUBLE;
|
||||||
|
ret = azeron_device_get_button_mapping(device, i, &d_mapping);
|
||||||
|
if (ret == AZERON_SUCCESS) {
|
||||||
|
const char *key = azeron_keycode_to_string(d_mapping.key_code);
|
||||||
|
if (key) snprintf(d_str, sizeof(d_str), "%s", key);
|
||||||
|
else snprintf(d_str, sizeof(d_str), "0x%02x", d_mapping.key_code);
|
||||||
|
} else snprintf(d_str, sizeof(d_str), "err");
|
||||||
|
|
||||||
|
printf("%-8d %-15s %-15s %-15s\n", i + 1, s_str, l_str, d_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
azeron_device_close(device);
|
azeron_device_close(device);
|
||||||
@@ -602,6 +623,8 @@ int cmd_set_stick(int argc, char *argv[])
|
|||||||
{"deadzone", required_argument, 0, 'z'},
|
{"deadzone", required_argument, 0, 'z'},
|
||||||
{"sensitivity", required_argument, 0, 's'},
|
{"sensitivity", required_argument, 0, 's'},
|
||||||
{"curve", required_argument, 0, 'c'},
|
{"curve", required_argument, 0, 'c'},
|
||||||
|
{"mode", required_argument, 0, 'm'},
|
||||||
|
{"angle", required_argument, 0, 'a'},
|
||||||
{"invert-x", no_argument, 0, 'x'},
|
{"invert-x", no_argument, 0, 'x'},
|
||||||
{"invert-y", no_argument, 0, 'y'},
|
{"invert-y", no_argument, 0, 'y'},
|
||||||
{"help", no_argument, 0, 'h'},
|
{"help", no_argument, 0, 'h'},
|
||||||
@@ -613,16 +636,29 @@ int cmd_set_stick(int argc, char *argv[])
|
|||||||
|
|
||||||
/* Initialize defaults from current config if possible, or zeros */
|
/* Initialize defaults from current config if possible, or zeros */
|
||||||
memset(&stick, 0, sizeof(stick));
|
memset(&stick, 0, sizeof(stick));
|
||||||
bool dz_set = false, sens_set = false, curve_set = false;
|
bool dz_set = false, sens_set = false, curve_set = false, mode_set = false, angle_set = false;
|
||||||
|
bool x_set = false, y_set = false;
|
||||||
|
|
||||||
while ((opt = getopt_long(argc, argv, "d:z:s:c:xyh", long_options, &option_index)) != -1) {
|
while ((opt = getopt_long(argc, argv, "d:z:s:c:m:a:xyh", long_options, &option_index)) != -1) {
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 'd': device_index = atoi(optarg); break;
|
case 'd': device_index = atoi(optarg); break;
|
||||||
case 'z': stick.deadzone = atoi(optarg); dz_set = true; break;
|
case 'z': stick.deadzone = atoi(optarg); dz_set = true; break;
|
||||||
case 's': stick.sensitivity = atoi(optarg); sens_set = true; break;
|
case 's': stick.sensitivity = atoi(optarg); sens_set = true; break;
|
||||||
case 'c': stick.response_curve = atoi(optarg); curve_set = true; break;
|
case 'c': stick.response_curve = atoi(optarg); curve_set = true; break;
|
||||||
case 'x': stick.invert_x = true; break;
|
case 'm':
|
||||||
case 'y': stick.invert_y = true; break;
|
if (strcmp(optarg, "analog") == 0) stick.mode = AZERON_STICK_ANALOG;
|
||||||
|
else if (strcmp(optarg, "digital_4") == 0) stick.mode = AZERON_STICK_DIGITAL_4;
|
||||||
|
else if (strcmp(optarg, "digital_8") == 0) stick.mode = AZERON_STICK_DIGITAL_8;
|
||||||
|
else if (strcmp(optarg, "mouse") == 0) stick.mode = AZERON_STICK_MOUSE;
|
||||||
|
else {
|
||||||
|
fprintf(stderr, "Error: Invalid mode '%s'. Use: analog, digital_4, digital_8, mouse\n", optarg);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
mode_set = true;
|
||||||
|
break;
|
||||||
|
case 'a': stick.angle = atoi(optarg); angle_set = true; break;
|
||||||
|
case 'x': stick.invert_x = true; x_set = true; break;
|
||||||
|
case 'y': stick.invert_y = true; y_set = true; break;
|
||||||
case 'h':
|
case 'h':
|
||||||
printf("Usage: %s set-stick [options]\n", argv[0]);
|
printf("Usage: %s set-stick [options]\n", argv[0]);
|
||||||
printf("Options:\n");
|
printf("Options:\n");
|
||||||
@@ -630,6 +666,8 @@ int cmd_set_stick(int argc, char *argv[])
|
|||||||
printf(" -z, --deadzone <0-100> Set deadzone percentage\n");
|
printf(" -z, --deadzone <0-100> Set deadzone percentage\n");
|
||||||
printf(" -s, --sensitivity <0-255> Set sensitivity\n");
|
printf(" -s, --sensitivity <0-255> Set sensitivity\n");
|
||||||
printf(" -c, --curve <0-255> Set response curve\n");
|
printf(" -c, --curve <0-255> Set response curve\n");
|
||||||
|
printf(" -m, --mode <mode> Set mode (analog, digital_4, digital_8, mouse)\n");
|
||||||
|
printf(" -a, --angle <0-255> Set stick angle\n");
|
||||||
printf(" -x, --invert-x Invert X axis\n");
|
printf(" -x, --invert-x Invert X axis\n");
|
||||||
printf(" -y, --invert-y Invert Y axis\n");
|
printf(" -y, --invert-y Invert Y axis\n");
|
||||||
return 0;
|
return 0;
|
||||||
@@ -659,6 +697,10 @@ int cmd_set_stick(int argc, char *argv[])
|
|||||||
if (!dz_set) stick.deadzone = current.deadzone;
|
if (!dz_set) stick.deadzone = current.deadzone;
|
||||||
if (!sens_set) stick.sensitivity = current.sensitivity;
|
if (!sens_set) stick.sensitivity = current.sensitivity;
|
||||||
if (!curve_set) stick.response_curve = current.response_curve;
|
if (!curve_set) stick.response_curve = current.response_curve;
|
||||||
|
if (!mode_set) stick.mode = current.mode;
|
||||||
|
if (!angle_set) stick.angle = current.angle;
|
||||||
|
if (!x_set) stick.invert_x = current.invert_x;
|
||||||
|
if (!y_set) stick.invert_y = current.invert_y;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("Updating stick configuration...\n");
|
printf("Updating stick configuration...\n");
|
||||||
@@ -752,6 +794,74 @@ int cmd_read_raw(int argc, char *argv[])
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Set global timing delays */
|
||||||
|
int cmd_set_delays(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
struct azeron_device *device;
|
||||||
|
int ret;
|
||||||
|
int device_index = 0;
|
||||||
|
uint16_t long_press = 500;
|
||||||
|
uint16_t double_click = 200;
|
||||||
|
|
||||||
|
/* Parse options */
|
||||||
|
static struct option long_options[] = {
|
||||||
|
{"device", required_argument, 0, 'd'},
|
||||||
|
{"long", required_argument, 0, 'l'},
|
||||||
|
{"double", required_argument, 0, 'b'},
|
||||||
|
{"help", no_argument, 0, 'h'},
|
||||||
|
{0, 0, 0, 0}
|
||||||
|
};
|
||||||
|
|
||||||
|
int opt;
|
||||||
|
int option_index = 0;
|
||||||
|
while ((opt = getopt_long(argc, argv, "d:l:b:h", long_options, &option_index)) != -1) {
|
||||||
|
switch (opt) {
|
||||||
|
case 'd': device_index = atoi(optarg); break;
|
||||||
|
case 'l': long_press = (uint16_t)atoi(optarg); break;
|
||||||
|
case 'b': double_click = (uint16_t)atoi(optarg); break;
|
||||||
|
case 'h':
|
||||||
|
printf("Usage: %s set-delays [options]\n", argv[0]);
|
||||||
|
printf("Options:\n");
|
||||||
|
printf(" -d, --device <index> Select device (default: 0)\n");
|
||||||
|
printf(" -l, --long <ms> Set long press delay in ms (default: 500)\n");
|
||||||
|
printf(" -b, --double <ms> Set double click delay in ms (default: 200)\n");
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "Unknown option. Use --help for usage.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = azeron_init();
|
||||||
|
if (ret != AZERON_SUCCESS) {
|
||||||
|
fprintf(stderr, "Failed to initialize library: %s\n", azeron_error_string(ret));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = azeron_device_open_index(&device, device_index);
|
||||||
|
if (ret != AZERON_SUCCESS) {
|
||||||
|
fprintf(stderr, "Failed to open device %d: %s\n", device_index, azeron_error_string(ret));
|
||||||
|
azeron_exit();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Setting global delays: Long Press = %dms, Double Click = %dms...\n", long_press, double_click);
|
||||||
|
ret = azeron_device_set_global_timings(device, long_press, double_click);
|
||||||
|
if (ret != AZERON_SUCCESS) {
|
||||||
|
fprintf(stderr, "Failed to set global timings: %s\n", azeron_error_string(ret));
|
||||||
|
azeron_device_close(device);
|
||||||
|
azeron_exit();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Delays updated successfully.\n");
|
||||||
|
|
||||||
|
azeron_device_close(device);
|
||||||
|
azeron_exit();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Main function */
|
/* Main function */
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
|||||||
372
docs/protocol.md
372
docs/protocol.md
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
This document describes the USB configuration protocol for the Azeron Cyborg keypad (USB ID: 16d0:113c). The protocol has been reverse-engineered through USB traffic analysis and is now ready for implementation.
|
This document describes the USB configuration protocol for the Azeron Cyborg keypad (USB ID: 16d0:113c). The protocol has been reverse-engineered through USB traffic analysis and is now implemented in libazeron.
|
||||||
|
|
||||||
**Protocol Status:** ✅ Fully Reverse-Engineered
|
**Protocol Status:** ✅ Fully Reverse-Engineered
|
||||||
**Implementation Status:** 🔄 Ready for Development
|
**Implementation Status:** ✅ Implemented in libazeron
|
||||||
|
|
||||||
## USB Device Analysis
|
## USB Device Analysis
|
||||||
|
|
||||||
@@ -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 (except offsets)
|
- **Endianness**: Little-endian for offsets and delays, Big-endian for Command IDs.
|
||||||
|
|
||||||
### Packet Format
|
### Packet Format
|
||||||
|
|
||||||
@@ -68,12 +68,20 @@ Bytes 6-63: Data payload (varies by command)
|
|||||||
### Command Reference (Cyborg Model)
|
### Command Reference (Cyborg Model)
|
||||||
|
|
||||||
#### 0x122a - Get Status (Heartbeat)
|
#### 0x122a - Get Status (Heartbeat)
|
||||||
**Purpose:** Periodic device status check.
|
**Purpose:** Periodic device status check. Returns current profile configuration.
|
||||||
|
- **Payload Index 0:** Status (0x01 = Success)
|
||||||
|
- **Payload Index 1:** Active Profile ID (0, 1, 2)
|
||||||
|
- **Payload Index 3:** Joystick Mode
|
||||||
|
- **Payload Index 8:** Stick Angle
|
||||||
|
- **Payload Index 10:** Deadzone
|
||||||
|
- **Payload Index 11:** Response Curve
|
||||||
|
- **Payload Index 12:** Sensitivity
|
||||||
|
- **Payload Index 13:** Inversion flags (Bit 0: X, Bit 1: Y)
|
||||||
|
|
||||||
#### 0x2000 - Set Global Settings
|
#### 0x2000 - Set Global Settings
|
||||||
**Purpose:** Configure timing delays for actions.
|
**Purpose:** Configure timing delays for actions.
|
||||||
- **Bytes 60-61 (Payload):** Long Press Delay (ms, big-endian, e.g., `01f4` = 500ms)
|
- **Bytes 54-55 (Payload):** Long Press Delay (ms, little-endian, e.g., `f401` = 500ms)
|
||||||
- **Bytes 62-63 (Payload):** Double Press Delay (ms, big-endian, e.g., `00c8` = 200ms)
|
- **Bytes 56-57 (Payload):** Double Press Delay (ms, little-endian, e.g., `c800` = 200ms)
|
||||||
|
|
||||||
#### 0x20F6 - Set Single Press
|
#### 0x20F6 - Set Single Press
|
||||||
#### 0x20F8 - Set Long Press
|
#### 0x20F8 - Set Long Press
|
||||||
@@ -88,8 +96,9 @@ Bytes 6-63: Data payload (varies by command)
|
|||||||
|
|
||||||
#### 0x26EC - Bulk Write Profile
|
#### 0x26EC - Bulk Write Profile
|
||||||
**Purpose:** Writes a complete profile block.
|
**Purpose:** Writes a complete profile block.
|
||||||
- **Byte 3 (Payload):** Joystick Mode (0x00 = Analog, 0x01 = WASD)
|
- **Byte 3 (Payload):** Joystick Mode (0x00=Analog, 0x01=WASD4, 0x02=WASD8, 0x03=Mouse)
|
||||||
- **Byte 8 (Payload):** Stick Angle (degrees)
|
- **Byte 8 (Payload):** Stick Angle (0-255)
|
||||||
|
- **Byte 10 (Payload):** Deadzone (0-100)
|
||||||
|
|
||||||
#### 0x26ED - Commit Bulk Write
|
#### 0x26ED - Commit Bulk Write
|
||||||
**Purpose:** Persists bulk changes to EEPROM.
|
**Purpose:** Persists bulk changes to EEPROM.
|
||||||
@@ -101,24 +110,20 @@ Bytes 6-63: Data payload (varies by command)
|
|||||||
#### 0x12C8 - Read Configuration
|
#### 0x12C8 - Read Configuration
|
||||||
**Purpose:** Read full device configuration.
|
**Purpose:** Read full device configuration.
|
||||||
|
|
||||||
#### 0x26FC - Write Profile Data
|
#### 0x26FB - Read Configuration (Cyborg)
|
||||||
**Purpose:** Write profile configuration to device (does not persist).
|
**Purpose:** Read specific memory blocks.
|
||||||
|
|
||||||
#### 0x26FD - Save Profile
|
|
||||||
**Purpose:** Commit profile to device EEPROM.
|
|
||||||
|
|
||||||
## Button Mapping Reference
|
## Button Mapping Reference
|
||||||
|
|
||||||
The Azeron Cyborg has **30 configurable buttons** plus **1 analog joystick**.
|
The Azeron Cyborg has **30 configurable buttons** plus **1 analog joystick**.
|
||||||
|
|
||||||
### Button Numbering
|
### Button Numbering
|
||||||
Buttons are numbered 1-30 in the configuration data:
|
Buttons are numbered 1-30 in the configuration data. Each button has 3 actions (Single, Long, Double), each 4 bytes.
|
||||||
|
|
||||||
- **Bytes 18-21:** Button 1 (typically the main thumb button)
|
- **Profile Offset 8-11:** Button 1 Single
|
||||||
- **Bytes 22-25:** Button 2
|
- **Profile Offset 12-15:** Button 1 Long
|
||||||
- **Bytes 26-29:** Button 3
|
- **Profile Offset 16-19:** Button 1 Double
|
||||||
- ... (continues linearly)
|
- ... (continues linearly)
|
||||||
- **Bytes 134-137:** Button 30
|
|
||||||
|
|
||||||
### Key Type Codes
|
### Key Type Codes
|
||||||
|
|
||||||
@@ -131,219 +136,43 @@ Buttons are numbered 1-30 in the configuration data:
|
|||||||
| 0xf4 | Media | Media control key |
|
| 0xf4 | Media | Media control key |
|
||||||
| 0xf5 | Layer | Layer switch |
|
| 0xf5 | Layer | Layer switch |
|
||||||
|
|
||||||
### Key Code Values
|
|
||||||
|
|
||||||
**Keyboard (0xf0):**
|
|
||||||
- 0x04 = A, 0x05 = B, 0x06 = C, ... (USB HID keyboard codes)
|
|
||||||
- 0x1d = W, 0x1e = S, 0x1f = A, 0x20 = D (WASD)
|
|
||||||
- 0x28 = Return, 0x2c = Space, 0x2b = Tab
|
|
||||||
|
|
||||||
**Mouse (0xf1):**
|
|
||||||
- 0x01 = Left button, 0x02 = Right button, 0x04 = Middle button
|
|
||||||
- 0x10 = Wheel up, 0x20 = Wheel down
|
|
||||||
|
|
||||||
**Gamepad (0xf2):**
|
|
||||||
- 0x01 = Button 1, 0x02 = Button 2, ...
|
|
||||||
- 0x30 = D-pad up, 0x31 = D-pad down, etc.
|
|
||||||
|
|
||||||
### Modifier Flags
|
|
||||||
|
|
||||||
| Bit | Flag | Key |
|
|
||||||
|-----|------|-----|
|
|
||||||
| 0 | 0x01 | Left Ctrl |
|
|
||||||
| 1 | 0x02 | Left Shift |
|
|
||||||
| 2 | 0x04 | Left Alt |
|
|
||||||
| 3 | 0x08 | Left GUI |
|
|
||||||
| 4 | 0x10 | Right Ctrl |
|
|
||||||
| 5 | 0x20 | Right Shift |
|
|
||||||
| 6 | 0x40 | Right Alt |
|
|
||||||
| 7 | 0x80 | Right GUI |
|
|
||||||
|
|
||||||
## Analog Joystick Configuration
|
## Analog Joystick Configuration
|
||||||
|
|
||||||
The analog joystick is configured separately from the 30 buttons.
|
The analog joystick is configured separately from the 30 buttons.
|
||||||
|
|
||||||
**Joystick Data Bytes (58-61):**
|
**Joystick Profile Data (Indices relative to payload start):**
|
||||||
```
|
```
|
||||||
Byte 58: Dead zone (0-100%)
|
Index 3: Joystick Mode (0x00=Analog, 0x01=WASD4, 0x02=WASD8, 0x03=Mouse)
|
||||||
Byte 59: Sensitivity curve (0=linear, 1=exponential, 2=custom)
|
Index 8: Stick Angle (0-255)
|
||||||
Byte 60: X-axis inversion (0=normal, 1=inverted)
|
Index 10: Dead zone (0-100%)
|
||||||
Byte 61: Y-axis inversion (0=normal, 1=inverted)
|
Index 11: Sensitivity curve (0=linear, 1=exponential, etc.)
|
||||||
|
Index 12: Sensitivity (0-100)
|
||||||
|
Index 13: Inversion flags (Bit 0: X, Bit 1: Y)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Joystick Modes:**
|
|
||||||
- **0x00 = Analog:** Standard analog stick behavior
|
|
||||||
- **0x01 = 4-way:** Digital 4-direction pad
|
|
||||||
- **0x02 = 8-way:** Digital 8-direction pad
|
|
||||||
- **0x03 = Mouse:** Mouse emulation mode
|
|
||||||
|
|
||||||
## Profile Management
|
## Profile Management
|
||||||
|
|
||||||
The device supports **3 profiles** (Profile 0, 1, 2).
|
The device supports **3 profiles** (Profile 0, 1, 2).
|
||||||
|
|
||||||
### Profile Switching Sequence
|
|
||||||
|
|
||||||
When switching profiles, the software sends:
|
|
||||||
|
|
||||||
1. **Write Profile Data** (0x26FC) - Write new profile configuration
|
|
||||||
2. **Status Response** (0x26FC) - Device acknowledges
|
|
||||||
3. **Save Profile** (0x26FD) - Commit to EEPROM
|
|
||||||
4. **Save Confirmation** (0x26FD) - Device confirms persistence
|
|
||||||
|
|
||||||
**Profile Numbers:**
|
|
||||||
- Byte 5 of operation field: 0x00 = Profile 0, 0x01 = Profile 1, 0x02 = Profile 2
|
|
||||||
|
|
||||||
### Active Profile Indication
|
### Active Profile Indication
|
||||||
|
|
||||||
The currently active profile is indicated in status responses:
|
The currently active profile is indicated in status responses:
|
||||||
- **Byte 6 of response:** Active profile number
|
- **Byte 1 of payload:** Active profile number
|
||||||
- **LED color** changes to profile-specific color
|
- **LED color** changes to profile-specific color
|
||||||
|
|
||||||
## Implementation Guide
|
## Implementation Checklist
|
||||||
|
|
||||||
### Wireshark Filters for Analysis
|
- [x] Implement 64-byte HID report parser
|
||||||
|
- [x] Create command builder for 0x122a, 0x26FB, 0x26EC, 0x26ED
|
||||||
```wireshark
|
- [x] Parse button mapping data (30 buttons x 3 actions)
|
||||||
# All configuration commands (exclude heartbeat):
|
- [x] Parse joystick configuration (mode, angle, deadzone, etc)
|
||||||
usb.device_address == 8 && usb.data_len == 64 && !(usb.setup.wValue == 0x122a)
|
- [x] Implement profile read/write/save operations
|
||||||
|
- [x] Add support for all key types (keyboard, mouse, gamepad)
|
||||||
# Just write operations:
|
|
||||||
usb.device_address == 8 && usb.data_len == 64 && usb.setup.wValue == 0x26fc
|
|
||||||
|
|
||||||
# Just save operations:
|
|
||||||
usb.device_address == 8 && usb.data_len == 64 && usb.setup.wValue == 0x26fd
|
|
||||||
|
|
||||||
# All status polls (heartbeat):
|
|
||||||
usb.device_address == 8 && usb.setup.wValue == 0x122a
|
|
||||||
```
|
|
||||||
|
|
||||||
### USBPcap Capture Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Minimal capture (only config, no joystick data):
|
|
||||||
USBPcapCMD.exe -d \\.\USBPcap6 -o config.pcap -s 64
|
|
||||||
|
|
||||||
# Capture only control transfers:
|
|
||||||
USBPcapCMD.exe -d \\.\USBPcap6 -o config.pcap -F c
|
|
||||||
```
|
|
||||||
|
|
||||||
### Implementation Checklist
|
|
||||||
|
|
||||||
- [ ] Implement 64-byte HID report parser
|
|
||||||
- [ ] Create command builder for 0x122a, 0x12C8, 0x26FC, 0x26FD
|
|
||||||
- [ ] Parse button mapping data (30 buttons)
|
|
||||||
- [ ] Parse joystick configuration (4 bytes)
|
|
||||||
- [ ] Implement profile read/write/save operations
|
|
||||||
- [ ] Add support for all key types (keyboard, mouse, gamepad, macro)
|
|
||||||
- [ ] Handle modifier flags
|
- [ ] Handle modifier flags
|
||||||
- [ ] Create profile management functions
|
- [x] Create profile management functions
|
||||||
- [ ] Add analog stick mode switching
|
- [x] Add analog stick mode switching
|
||||||
- [ ] Implement LED color control
|
- [ ] Implement LED color control
|
||||||
|
|
||||||
## Protocol Examples
|
|
||||||
|
|
||||||
### Example 1: Read Current Configuration
|
|
||||||
|
|
||||||
```
|
|
||||||
Host → Device: 000012c801010000000000000000000000...
|
|
||||||
Device → Host: 010012c8010101013f4f691b0700ff060606...
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example 2: Write Profile 1
|
|
||||||
|
|
||||||
```
|
|
||||||
Host → Device: 003a26fc020139040102000000fff40003f01a0000...
|
|
||||||
Device → Host: 000026fc010100013f4f691b0700ff060606...
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example 3: Save Profile 1
|
|
||||||
|
|
||||||
```
|
|
||||||
Host → Device: 003a26fd020201000000000000000000...
|
|
||||||
Device → Host: 000026fd010100013f4f691b0700ff060606...
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example 4: Switch to Profile 2
|
|
||||||
|
|
||||||
```
|
|
||||||
Host → Device: 003a26fc020239040102000000fff40003f01a0000...
|
|
||||||
Device → Host: 000026fc010200023f4f691b0700ff060606...
|
|
||||||
Host → Device: 003a26fd020202000000000000000000...
|
|
||||||
Device → Host: 000026fd010200023f4f691b0700ff060606...
|
|
||||||
```
|
|
||||||
|
|
||||||
## Development Notes
|
|
||||||
|
|
||||||
### USB Control Transfer Format
|
|
||||||
|
|
||||||
```c
|
|
||||||
// Send HID report to device
|
|
||||||
int azeron_send_report(struct azeron_device *device,
|
|
||||||
uint8_t *report, size_t length)
|
|
||||||
{
|
|
||||||
return libusb_interrupt_transfer(device->handle,
|
|
||||||
0x06, // Endpoint OUT
|
|
||||||
report,
|
|
||||||
length,
|
|
||||||
&transferred,
|
|
||||||
1000); // Timeout
|
|
||||||
}
|
|
||||||
|
|
||||||
// Receive HID report from device
|
|
||||||
int azeron_receive_report(struct azeron_device *device,
|
|
||||||
uint8_t *report, size_t length)
|
|
||||||
{
|
|
||||||
return libusb_interrupt_transfer(device->handle,
|
|
||||||
0x85, // Endpoint IN
|
|
||||||
report,
|
|
||||||
length,
|
|
||||||
&transferred,
|
|
||||||
1000); // Timeout
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Common Gaming Device Protocol Patterns
|
|
||||||
|
|
||||||
The Azeron protocol follows these standard patterns:
|
|
||||||
|
|
||||||
1. **Request-Response:** Every command gets an acknowledgment
|
|
||||||
2. **Two-Phase Write:** Write data → Verify → Commit/Save
|
|
||||||
3. **Command IDs:** Unique 2-byte identifiers for each operation
|
|
||||||
4. **Status Polling:** Regular heartbeat to detect device presence
|
|
||||||
5. **Fixed Packet Size:** 64-byte reports for simplicity
|
|
||||||
|
|
||||||
### Tools for Development
|
|
||||||
|
|
||||||
- **USBPcap:** Windows USB capture
|
|
||||||
- **Wireshark:** Protocol analysis with custom filters
|
|
||||||
- **libusb:** Cross-platform USB communication
|
|
||||||
- **hidapi:** Alternative HID-specific library
|
|
||||||
|
|
||||||
### Expected Challenges
|
|
||||||
|
|
||||||
1. **Timing:** Device expects responses within ~100ms
|
|
||||||
2. **Checksums:** May need to implement data validation
|
|
||||||
3. **Atomic Operations:** Write + Save must be atomic
|
|
||||||
4. **Device State:** Must track active profile and settings
|
|
||||||
5. **Error Recovery:** Handle disconnects and reconnection
|
|
||||||
|
|
||||||
## Current Status
|
|
||||||
|
|
||||||
**Protocol Status:** ✅ Fully Reverse-Engineered
|
|
||||||
**Documentation Status:** ✅ Complete
|
|
||||||
**Implementation Status:** 🔄 Ready for Development
|
|
||||||
**Next Step:** Implement libazeron protocol functions
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
If you discover additional protocol details:
|
|
||||||
|
|
||||||
1. Document the command format with examples
|
|
||||||
2. Provide USB capture files (PCAP format)
|
|
||||||
3. Include test code if available
|
|
||||||
4. Update this documentation
|
|
||||||
5. Submit pull request with changes
|
|
||||||
|
|
||||||
## Safety Notes
|
## Safety Notes
|
||||||
|
|
||||||
- Always test with backup configurations
|
- Always test with backup configurations
|
||||||
@@ -353,127 +182,6 @@ If you discover additional protocol details:
|
|||||||
- Stop if device behaves unexpectedly
|
- Stop if device behaves unexpectedly
|
||||||
- Keep original configuration files as backup
|
- Keep original configuration files as backup
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- USB HID Specification: https://www.usb.org/hid
|
|
||||||
- libusb Documentation: https://libusb.info
|
|
||||||
- Azeron Cyborg Product Page: https://azeron.net
|
|
||||||
- Wireshark USB Analysis: https://wiki.wireshark.org/USB
|
|
||||||
|
|
||||||
## Reverse Engineering Process
|
|
||||||
|
|
||||||
### Phase 1: USB Traffic Capture
|
|
||||||
|
|
||||||
**Tools Used:**
|
|
||||||
- USBPcap 1.5.4.0
|
|
||||||
- Wireshark 4.0.0
|
|
||||||
- Azeron Windows Software v1.0.0
|
|
||||||
|
|
||||||
**Capture Method:**
|
|
||||||
```bash
|
|
||||||
USBPcapCMD.exe -d \\.\USBPcap6 -o capture.pcap -s 64
|
|
||||||
```
|
|
||||||
|
|
||||||
**Analysis Filters:**
|
|
||||||
```wireshark
|
|
||||||
usb.device_address == 8 && usb.transfer_type == 0x01 && usb.data_len == 64
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 2: Protocol Discovery
|
|
||||||
|
|
||||||
**Key Findings:**
|
|
||||||
1. Configuration uses Interface 4 (not Interface 0 as initially assumed)
|
|
||||||
2. Protocol uses HID interrupt transfers, not vendor control transfers
|
|
||||||
3. Fixed 64-byte packet format
|
|
||||||
4. Request-response pattern for all commands
|
|
||||||
5. Two-phase write (write → save) for persistence
|
|
||||||
|
|
||||||
**Command IDs Identified:**
|
|
||||||
- 0x122a: Get Status (heartbeat)
|
|
||||||
- 0x12C8: Read Configuration
|
|
||||||
- 0x26FC: Write Profile Data
|
|
||||||
- 0x26FD: Save Profile
|
|
||||||
|
|
||||||
### Phase 3: Implementation Strategy
|
|
||||||
|
|
||||||
**Next Steps:**
|
|
||||||
1. Implement HID report parser in libazeron
|
|
||||||
2. Create command builder functions
|
|
||||||
3. Add button mapping support (30 buttons)
|
|
||||||
4. Implement joystick configuration
|
|
||||||
5. Add profile management
|
|
||||||
6. Create comprehensive test suite
|
|
||||||
|
|
||||||
**Estimated Timeline:** 2-3 weeks for full implementation
|
|
||||||
|
|
||||||
### Phase 4: Testing and Validation
|
|
||||||
|
|
||||||
**Test Plan:**
|
|
||||||
1. Read current configuration from device
|
|
||||||
2. Modify single button mapping
|
|
||||||
3. Write new configuration
|
|
||||||
4. Save to device
|
|
||||||
5. Verify persistence after power cycle
|
|
||||||
6. Test all 30 buttons
|
|
||||||
7. Test joystick modes
|
|
||||||
8. Test profile switching
|
|
||||||
|
|
||||||
**Validation Criteria:**
|
|
||||||
- All button mappings work correctly
|
|
||||||
- Joystick behaves as configured
|
|
||||||
- Profiles persist after power loss
|
|
||||||
- No device crashes or errors
|
|
||||||
- Performance: <100ms response time
|
|
||||||
|
|
||||||
### Tools for Reverse Engineering
|
|
||||||
|
|
||||||
#### USB Capture Tools
|
|
||||||
- **USBPcap**: Windows USB capture
|
|
||||||
- **Wireshark**: Protocol analysis
|
|
||||||
- **usbmon**: Linux kernel USB monitoring
|
|
||||||
- **libusb debug**: Enable debug output
|
|
||||||
|
|
||||||
#### Analysis Tools
|
|
||||||
- **Protocol analyzers**: Wireshark with USB dissectors
|
|
||||||
- **Hex editors**: For examining binary data
|
|
||||||
- **Custom scripts**: Python with pyusb for testing
|
|
||||||
|
|
||||||
### Expected Challenges
|
|
||||||
|
|
||||||
1. **Timing:** Device expects responses within ~100ms
|
|
||||||
2. **Checksums:** May need to implement data validation
|
|
||||||
3. **Atomic Operations:** Write + Save must be atomic
|
|
||||||
4. **Device State:** Must track active profile and settings
|
|
||||||
5. **Error Recovery:** Handle disconnects and reconnection
|
|
||||||
|
|
||||||
### Next Steps
|
|
||||||
|
|
||||||
1. **Implement Core Protocol:** Add HID report functions to libazeron
|
|
||||||
2. **Button Mapping UI:** Create user interface for configuring 30 buttons
|
|
||||||
3. **Joystick Configuration:** Add analog stick settings
|
|
||||||
4. **Profile Management:** Implement profile switching and persistence
|
|
||||||
5. **Testing:** Comprehensive test suite with all features
|
|
||||||
6. **Documentation:** Update user documentation
|
|
||||||
|
|
||||||
### Contributing
|
|
||||||
|
|
||||||
If you discover protocol details:
|
|
||||||
|
|
||||||
1. Document the command format
|
|
||||||
2. Provide USB capture files (PCAP format)
|
|
||||||
3. Include test code if available
|
|
||||||
4. Update this documentation
|
|
||||||
5. Submit pull request with changes
|
|
||||||
|
|
||||||
### Safety Notes
|
|
||||||
|
|
||||||
- Always test with backup configurations
|
|
||||||
- Be prepared to reset device to factory defaults
|
|
||||||
- Don't send malformed packets to device
|
|
||||||
- Monitor device temperature during testing
|
|
||||||
- Stop if device behaves unexpectedly
|
|
||||||
- Keep original configuration files as backup
|
|
||||||
|
|
||||||
## Acknowledgments
|
## Acknowledgments
|
||||||
|
|
||||||
Special thanks to the Azeron community for providing captures and testing assistance during the reverse engineering process.
|
Special thanks to the Azeron community for providing captures and testing assistance during the reverse engineering process.
|
||||||
@@ -407,6 +407,16 @@ int azeron_device_set_active_profile(struct azeron_device *device, uint8_t profi
|
|||||||
return azeron_protocol_set_active_profile(device, profile_id);
|
return azeron_protocol_set_active_profile(device, profile_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Global settings */
|
||||||
|
int azeron_device_set_global_timings(struct azeron_device *device, uint16_t long_press_delay, uint16_t double_click_delay)
|
||||||
|
{
|
||||||
|
if (!device) {
|
||||||
|
return AZERON_ERROR_INVALID_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
return azeron_protocol_set_global_timings(device, long_press_delay, double_click_delay);
|
||||||
|
}
|
||||||
|
|
||||||
/* Save profile to device EEPROM */
|
/* Save profile to device EEPROM */
|
||||||
int azeron_device_save_profile(struct azeron_device *device, uint8_t profile_id)
|
int azeron_device_save_profile(struct azeron_device *device, uint8_t profile_id)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ struct azeron_stick_config {
|
|||||||
bool invert_x;
|
bool invert_x;
|
||||||
bool invert_y;
|
bool invert_y;
|
||||||
uint8_t response_curve; /* 0=linear, 1=exponential, etc. */
|
uint8_t response_curve; /* 0=linear, 1=exponential, etc. */
|
||||||
|
uint8_t angle; /* 0-360, but typically fits in 8 bits? wait. */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Profile configuration */
|
/* Profile configuration */
|
||||||
@@ -134,6 +135,9 @@ int azeron_device_set_stick_config(struct azeron_device *device,
|
|||||||
int azeron_device_get_active_profile(struct azeron_device *device, uint8_t *profile_id);
|
int azeron_device_get_active_profile(struct azeron_device *device, uint8_t *profile_id);
|
||||||
int azeron_device_set_active_profile(struct azeron_device *device, uint8_t profile_id);
|
int azeron_device_set_active_profile(struct azeron_device *device, uint8_t profile_id);
|
||||||
|
|
||||||
|
/* Global settings */
|
||||||
|
int azeron_device_set_global_timings(struct azeron_device *device, uint16_t long_press_delay, uint16_t double_click_delay);
|
||||||
|
|
||||||
int azeron_device_get_profile(struct azeron_device *device, uint8_t profile_id,
|
int azeron_device_get_profile(struct azeron_device *device, uint8_t profile_id,
|
||||||
struct azeron_profile *profile);
|
struct azeron_profile *profile);
|
||||||
int azeron_device_set_profile(struct azeron_device *device,
|
int azeron_device_set_profile(struct azeron_device *device,
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ int azeron_protocol_get_stick_config(struct azeron_device *device, struct azeron
|
|||||||
int azeron_protocol_set_stick_config(struct azeron_device *device, const struct azeron_stick_config *config);
|
int azeron_protocol_set_stick_config(struct azeron_device *device, const struct azeron_stick_config *config);
|
||||||
int azeron_protocol_get_active_profile(struct azeron_device *device, uint8_t *profile_id);
|
int azeron_protocol_get_active_profile(struct azeron_device *device, uint8_t *profile_id);
|
||||||
int azeron_protocol_set_active_profile(struct azeron_device *device, uint8_t profile_id);
|
int azeron_protocol_set_active_profile(struct azeron_device *device, uint8_t profile_id);
|
||||||
|
int azeron_protocol_set_global_timings(struct azeron_device *device, uint16_t long_press_delay, uint16_t double_click_delay);
|
||||||
int azeron_protocol_get_profile(struct azeron_device *device, uint8_t profile_id, struct azeron_profile *profile);
|
int azeron_protocol_get_profile(struct azeron_device *device, uint8_t profile_id, struct azeron_profile *profile);
|
||||||
int azeron_protocol_set_profile(struct azeron_device *device, const struct azeron_profile *profile);
|
int azeron_protocol_set_profile(struct azeron_device *device, const struct azeron_profile *profile);
|
||||||
int azeron_protocol_save_to_device(struct azeron_device *device, uint8_t profile_id);
|
int azeron_protocol_save_to_device(struct azeron_device *device, uint8_t profile_id);
|
||||||
|
|||||||
@@ -229,8 +229,8 @@ int azeron_protocol_write_config(struct azeron_device *device, const uint8_t *da
|
|||||||
int azeron_protocol_get_button_mapping(struct azeron_device *device, uint8_t button_id,
|
int azeron_protocol_get_button_mapping(struct azeron_device *device, uint8_t button_id,
|
||||||
struct azeron_button_mapping *mapping)
|
struct azeron_button_mapping *mapping)
|
||||||
{
|
{
|
||||||
uint8_t config[64];
|
uint8_t response[64];
|
||||||
size_t size = sizeof(config);
|
size_t response_len;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (!device || !mapping) {
|
if (!device || !mapping) {
|
||||||
@@ -241,28 +241,25 @@ int azeron_protocol_get_button_mapping(struct azeron_device *device, uint8_t but
|
|||||||
return AZERON_ERROR_INVALID_PARAM;
|
return AZERON_ERROR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Calculate offset for this button's 4-byte mapping */
|
/* Status command returns the full profile block in Cyborg */
|
||||||
uint32_t offset = AZERON_PROFILE_BASE_OFFSET + 8 + (button_id * AZERON_BUTTON_MAPPING_SIZE);
|
ret = send_config_command(device, AZERON_CMD_STATUS, AZERON_OP_READ_STATUS,
|
||||||
|
NULL, 0, response, &response_len);
|
||||||
ret = azeron_protocol_read_config(device, offset, config, &size);
|
|
||||||
if (ret != AZERON_SUCCESS) {
|
if (ret != AZERON_SUCCESS) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Payload structure in response after 6-byte header:
|
* response[0] is status
|
||||||
* Bytes 0-3: Echoed offset?
|
* response[1..58] is profile data.
|
||||||
* Bytes 4-7: The 4-byte mapping
|
* Mappings start at profile index 8 (response[9]).
|
||||||
|
* Each button has 3 actions, 4 bytes each = 12 bytes per button.
|
||||||
*/
|
*/
|
||||||
mapping->button_id = button_id;
|
uint32_t profile_idx = 8 + (button_id * 3 * 4) + (mapping->action * 4);
|
||||||
|
|
||||||
/*
|
/* Mapping structure: [Type, Code, 0, 0] */
|
||||||
* Mapping format:
|
uint8_t type_byte = response[profile_idx + 1];
|
||||||
* Byte 0: Type (0xf0 = keyboard, 0xf1 = mouse, etc)
|
mapping->key_code = response[profile_idx + 2];
|
||||||
* Byte 1: Key code
|
mapping->button_id = button_id;
|
||||||
*/
|
|
||||||
uint8_t type_byte = config[4];
|
|
||||||
mapping->key_code = config[5];
|
|
||||||
|
|
||||||
switch (type_byte) {
|
switch (type_byte) {
|
||||||
case 0xf0: mapping->type = AZERON_BTN_KEYBOARD; break;
|
case 0xf0: mapping->type = AZERON_BTN_KEYBOARD; break;
|
||||||
@@ -342,28 +339,33 @@ int azeron_protocol_set_button_mapping(struct azeron_device *device,
|
|||||||
int azeron_protocol_get_stick_config(struct azeron_device *device,
|
int azeron_protocol_get_stick_config(struct azeron_device *device,
|
||||||
struct azeron_stick_config *config)
|
struct azeron_stick_config *config)
|
||||||
{
|
{
|
||||||
uint8_t data[64];
|
uint8_t response[64];
|
||||||
size_t size = sizeof(data);
|
size_t response_len;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (!device || !config) {
|
if (!device || !config) {
|
||||||
return AZERON_ERROR_INVALID_PARAM;
|
return AZERON_ERROR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t offset = AZERON_PROFILE_BASE_OFFSET + AZERON_STICK_CONFIG_OFFSET;
|
/* Status command returns the full profile block in Cyborg */
|
||||||
|
ret = send_config_command(device, AZERON_CMD_STATUS, AZERON_OP_READ_STATUS,
|
||||||
ret = azeron_protocol_read_config(device, offset, data, &size);
|
NULL, 0, response, &response_len);
|
||||||
if (ret != AZERON_SUCCESS) {
|
if (ret != AZERON_SUCCESS) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Payload: [Offset(4)] [Deadzone(1)] [Curve(1)] [Sensitivity(1)] [Invert(1)] */
|
/*
|
||||||
config->deadzone = data[4];
|
* response[0] is status (usually 0x01)
|
||||||
config->response_curve = data[5];
|
* response[1..58] is the profile payload data.
|
||||||
config->sensitivity = data[6];
|
* Index 3 in payload is response[4].
|
||||||
config->invert_x = data[7] & 0x01;
|
*/
|
||||||
config->invert_y = (data[7] >> 1) & 0x01;
|
config->mode = (enum azeron_stick_mode)response[4];
|
||||||
config->mode = AZERON_STICK_ANALOG;
|
config->angle = response[9]; /* index 8 in payload */
|
||||||
|
config->deadzone = response[11]; /* index 10 */
|
||||||
|
config->response_curve = response[12]; /* index 11 */
|
||||||
|
config->sensitivity = response[13]; /* index 12 */
|
||||||
|
config->invert_x = response[14] & 0x01; /* index 13 */
|
||||||
|
config->invert_y = (response[14] >> 1) & 0x01;
|
||||||
|
|
||||||
return AZERON_SUCCESS;
|
return AZERON_SUCCESS;
|
||||||
}
|
}
|
||||||
@@ -375,7 +377,6 @@ int azeron_protocol_set_stick_config(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 operation;
|
uint16_t operation;
|
||||||
uint8_t active_profile;
|
uint8_t active_profile;
|
||||||
int ret;
|
int ret;
|
||||||
@@ -390,6 +391,7 @@ int azeron_protocol_set_stick_config(struct azeron_device *device,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Operation selects which profile slot to write to */
|
||||||
switch (active_profile) {
|
switch (active_profile) {
|
||||||
case 0: operation = AZERON_OP_WRITE_PROFILE_0; break;
|
case 0: operation = AZERON_OP_WRITE_PROFILE_0; break;
|
||||||
case 1: operation = AZERON_OP_WRITE_PROFILE_1; break;
|
case 1: operation = AZERON_OP_WRITE_PROFILE_1; break;
|
||||||
@@ -397,22 +399,44 @@ int azeron_protocol_set_stick_config(struct azeron_device *device,
|
|||||||
default: return AZERON_ERROR_PROTOCOL;
|
default: return AZERON_ERROR_PROTOCOL;
|
||||||
}
|
}
|
||||||
|
|
||||||
offset = AZERON_PROFILE_BASE_OFFSET + AZERON_STICK_CONFIG_OFFSET;
|
/* Cyborg Bulk Write Format for 0x26EC:
|
||||||
|
* Index 0-1: Base offset (0x39 0x03)
|
||||||
data[0] = offset & 0xFF;
|
* Index 3: Mode
|
||||||
data[1] = (offset >> 8) & 0xFF;
|
* Index 8: Angle
|
||||||
data[2] = (offset >> 16) & 0xFF;
|
* Index 10: Deadzone
|
||||||
data[3] = (offset >> 24) & 0xFF;
|
*/
|
||||||
|
data[0] = AZERON_PROFILE_BASE_OFFSET & 0xFF;
|
||||||
data[4] = config->deadzone;
|
data[1] = (AZERON_PROFILE_BASE_OFFSET >> 8) & 0xFF;
|
||||||
data[5] = config->response_curve;
|
data[3] = (uint8_t)config->mode;
|
||||||
data[6] = config->sensitivity;
|
data[8] = config->angle;
|
||||||
data[7] = 0;
|
data[10] = config->deadzone;
|
||||||
if (config->invert_x) data[7] |= 0x01;
|
|
||||||
if (config->invert_y) data[7] |= 0x02;
|
|
||||||
|
|
||||||
ret = send_config_command(device, AZERON_CMD_WRITE_PROFILE, operation,
|
ret = send_config_command(device, AZERON_CMD_WRITE_PROFILE, operation,
|
||||||
data, 8, response, &response_len);
|
data, 58, response, &response_len);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set global timings */
|
||||||
|
int azeron_protocol_set_global_timings(struct azeron_device *device, uint16_t long_press_delay, uint16_t double_click_delay)
|
||||||
|
{
|
||||||
|
uint8_t response[64];
|
||||||
|
size_t response_len;
|
||||||
|
uint8_t data[58] = {0};
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!device) {
|
||||||
|
return AZERON_ERROR_INVALID_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delay values are LITTLE ENDIAN at the very end of the 58-byte payload */
|
||||||
|
data[54] = long_press_delay & 0xFF;
|
||||||
|
data[55] = (long_press_delay >> 8) & 0xFF;
|
||||||
|
data[56] = double_click_delay & 0xFF;
|
||||||
|
data[57] = (double_click_delay >> 8) & 0xFF;
|
||||||
|
|
||||||
|
ret = send_config_command(device, AZERON_CMD_SET_GLOBAL, 0x0101,
|
||||||
|
data, 58, response, &response_len);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user