From 72b8648bde940c2ce05b809994fc118734a9caf9 Mon Sep 17 00:00:00 2001 From: Valentin Batz Date: Fri, 19 Jul 2024 16:35:37 +0200 Subject: [PATCH 1/3] Feat: Add support for SteelSeries Arctis Nova 5 The commands are very similar to SteelSeris Arctis Nova Pro Wireless --- README.md | 1 + src/device_registry.c | 2 + src/devices/CMakeLists.txt | 2 + src/devices/steelseries_arctis_nova_5.c | 215 ++++++++++++++++++++++++ src/devices/steelseries_arctis_nova_5.h | 3 + 5 files changed, 223 insertions(+) create mode 100644 src/devices/steelseries_arctis_nova_5.c create mode 100644 src/devices/steelseries_arctis_nova_5.h diff --git a/README.md b/README.md index 2723bcd7..9405f750 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ talking. This differs from a simple loopback via PulseAudio as you won't have an | ROCCAT Elo 7.1 Air | | | | x | x | | | | | | | | | | | | ROCCAT Elo 7.1 USB | | | | x | | | | | | | | | | | | | SteelSeries Arctis Nova 3 | x | | | | | | | | x | x | x | x | | | | +| SteelSeries Arctis Nova 5 | x | x | | | x | | | | | | x | x | x | | | | SteelSeries Arctis Nova 7 | x | x | | | x | x | | | x | x | x | x | x | x | x | | SteelSeries Arctis 7+ | x | x | | | x | x | | | x | x | | | | | | | SteelSeries Arctis Nova Pro Wireless | x | x | | x | x | | | | x | x | | | | | | diff --git a/src/device_registry.c b/src/device_registry.c index f981439d..73a9dc65 100644 --- a/src/device_registry.c +++ b/src/device_registry.c @@ -21,6 +21,7 @@ #include "devices/steelseries_arctis_7_plus.h" #include "devices/steelseries_arctis_9.h" #include "devices/steelseries_arctis_nova_3.h" +#include "devices/steelseries_arctis_nova_5.h" #include "devices/steelseries_arctis_nova_7.h" #include "devices/steelseries_arctis_nova_pro_wireless.h" #include "devices/steelseries_arctis_pro_wireless.h" @@ -64,6 +65,7 @@ void init_devices() add_device(elo71USB_init); // SteelSeries add_device(arctis_nova_3_init); + add_device(arctis_nova_5_init); add_device(arctis_nova_7_init); add_device(arctis_7_plus_init); add_device(arctis_nova_pro_wireless_init); diff --git a/src/devices/CMakeLists.txt b/src/devices/CMakeLists.txt index 2e39df31..cb6e32c6 100644 --- a/src/devices/CMakeLists.txt +++ b/src/devices/CMakeLists.txt @@ -27,6 +27,8 @@ set(SOURCE_FILES ${SOURCE_FILES} ${CMAKE_CURRENT_SOURCE_DIR}/steelseries_arctis_9.h ${CMAKE_CURRENT_SOURCE_DIR}/steelseries_arctis_nova_3.h ${CMAKE_CURRENT_SOURCE_DIR}/steelseries_arctis_nova_3.c + ${CMAKE_CURRENT_SOURCE_DIR}/steelseries_arctis_nova_5.h + ${CMAKE_CURRENT_SOURCE_DIR}/steelseries_arctis_nova_5.c ${CMAKE_CURRENT_SOURCE_DIR}/steelseries_arctis_nova_7.h ${CMAKE_CURRENT_SOURCE_DIR}/steelseries_arctis_nova_7.c ${CMAKE_CURRENT_SOURCE_DIR}/steelseries_arctis_nova_pro_wireless.c diff --git a/src/devices/steelseries_arctis_nova_5.c b/src/devices/steelseries_arctis_nova_5.c new file mode 100644 index 00000000..6f39fa24 --- /dev/null +++ b/src/devices/steelseries_arctis_nova_5.c @@ -0,0 +1,215 @@ +#include "../device.h" + +#include +#include + +static struct device device_arctis; + +enum { ID_ARCTIS_NOVA_5_BASE_STATION = 0x2232 }; + +enum { + MSG_SIZE = 64, + STATUS_BUF_SIZE = 128, +}; + +enum { + CONNECTION_STATUS_BYTE = 0x01, + BATTERY_LEVEL_BYTE = 0x03, + BATTERY_STATUS_BYTE = 0x04, +}; + +enum { + HEADSET_CHARGING = 0x01, + HEADSET_OFFLINE = 0x02, +}; + +static const uint16_t PRODUCT_IDS[] + = { ID_ARCTIS_NOVA_5_BASE_STATION }; + +static const uint8_t SAVE_DATA1[MSG_SIZE] = { 0x0, 0x09 }; // Command1 to save settings to headset +static const uint8_t SAVE_DATA2[MSG_SIZE] = { 0x0, 0x35, 0x01 }; // Command2 to save settings to headset + +static int set_sidetone(hid_device* device_handle, uint8_t num); +static int set_mic_mute_led_brightness(hid_device* device_handle, uint8_t num); +static int set_mic_volume(hid_device* device_handle, uint8_t num); +static int set_inactive_time(hid_device* device_handle, uint8_t num); +static int set_volume_limiter(hid_device* device_handle, uint8_t num); +static BatteryInfo get_battery(hid_device* device_handle); + +static int read_device_status(hid_device* device_handle, unsigned char* data_read); +static int save_state(hid_device* device_handle); + +void arctis_nova_5_init(struct device** device) +{ + device_arctis.idVendor = VENDOR_STEELSERIES; + device_arctis.idProductsSupported = PRODUCT_IDS; + device_arctis.numIdProducts = sizeof(PRODUCT_IDS) / sizeof(PRODUCT_IDS[0]); + + strncpy(device_arctis.device_name, "SteelSeries Arctis Nova 5", sizeof(device_arctis.device_name)); + + device_arctis.capabilities = B(CAP_SIDETONE) | B(CAP_BATTERY_STATUS) | B(CAP_MICROPHONE_MUTE_LED_BRIGHTNESS) | B(CAP_MICROPHONE_VOLUME) | B(CAP_INACTIVE_TIME) | B(CAP_VOLUME_LIMITER); + device_arctis.capability_details[CAP_SIDETONE] = (struct capability_detail) { .usagepage = 0xffc0, .usageid = 0x1, .interface = 3 }; + device_arctis.capability_details[CAP_BATTERY_STATUS] = (struct capability_detail) { .usagepage = 0xffc0, .usageid = 0x1, .interface = 3 }; + device_arctis.capability_details[CAP_MICROPHONE_MUTE_LED_BRIGHTNESS] = (struct capability_detail) { .usagepage = 0xffc0, .usageid = 0x1, .interface = 3 }; + device_arctis.capability_details[CAP_MICROPHONE_VOLUME] = (struct capability_detail) { .usagepage = 0xffc0, .usageid = 0x1, .interface = 3 }; + device_arctis.capability_details[CAP_INACTIVE_TIME] = (struct capability_detail) { .usagepage = 0xffc0, .usageid = 0x1, .interface = 3 }; + device_arctis.capability_details[CAP_VOLUME_LIMITER] = (struct capability_detail) { .usagepage = 0xffc0, .usageid = 0x1, .interface = 3 }; + + device_arctis.send_sidetone = &set_sidetone; + device_arctis.send_microphone_mute_led_brightness = &set_mic_mute_led_brightness; + device_arctis.send_microphone_volume = &set_mic_volume; + device_arctis.request_battery = &get_battery; + device_arctis.send_inactive_time = &set_inactive_time; + device_arctis.send_volume_limiter = &set_volume_limiter; + *device = &device_arctis; +} + +static int read_device_status(hid_device* device_handle, unsigned char* data_read) +{ + unsigned char data_request[2] = { 0x0, 0xB0 }; + int r = hid_write(device_handle, data_request, sizeof(data_request)); + + if (r < 0) + return r; + + return hid_read_timeout(device_handle, data_read, STATUS_BUF_SIZE, hsc_device_timeout); +} + +static int save_state(hid_device* device_handle) +{ + int r = hid_write(device_handle, SAVE_DATA1, MSG_SIZE); + if (r < 0) + return r; + + return hid_write(device_handle, SAVE_DATA2, MSG_SIZE); +} + +static int set_sidetone(hid_device* device_handle, uint8_t num) +{ + // This headset only supports 10 levels of sidetone volume, + // but we allow a full range of 0-128 for it. Map the volume to the correct numbers. + if (num < 13) { + num = 0x00; + } else if (num < 25) { + num = 0x01; + } else if (num < 37) { + num = 0x02; + } else if (num < 49) { + num = 0x03; + } else if (num < 61) { + num = 0x04; + } else if (num < 73) { + num = 0x05; + } else if (num < 85) { + num = 0x06; + } else if (num < 97) { + num = 0x07; + } else if (num < 109) { + num = 0x08; + } else if (num < 121) { + num = 0x09; + } else { + num = 0x0a; + } + uint8_t data[MSG_SIZE] = { 0x0, 0x39, num }; + int r = hid_write(device_handle, data, MSG_SIZE); + if (r < 0) + return r; + + return save_state(device_handle); +} + +static int set_mic_volume(hid_device* device_handle, uint8_t num) +{ + // 0x00 off + // step + 0x01 + // 0x0F max + num = num / 8; + if (num == 16) + num--; + + uint8_t volume[MSG_SIZE] = { 0x0, 0x37, num }; + int r = hid_write(device_handle, volume, MSG_SIZE); + if (r < 0) + return r; + + return save_state(device_handle); +} + +static int set_mic_mute_led_brightness(hid_device* device_handle, uint8_t num) +{ + // Off = 0x00 + // low = 0x01 + // medium (default) = 0x04 + // high = 0x0a + if (num == 2) { + num = 0x04; + } else if (num == 3) { + num = 0x0A; + } + + uint8_t brightness[MSG_SIZE] = { 0x0, 0xAE, num }; + int r = hid_write(device_handle, brightness, MSG_SIZE); + + if (r < 0) + return r; + + return save_state(device_handle); +} + +static BatteryInfo get_battery(hid_device* device_handle) +{ + unsigned char data_read[STATUS_BUF_SIZE]; + int r = read_device_status(device_handle, data_read); + + BatteryInfo info = { .status = BATTERY_UNAVAILABLE, .level = -1 }; + + if (r < 0) { + info.status = BATTERY_HIDERROR; + return info; + } + + if (r == 0) { + info.status = BATTERY_TIMEOUT; + return info; + } + + if (r < 16) + return info; + + unsigned char connection_status = data_read[CONNECTION_STATUS_BYTE]; + if (connection_status == HEADSET_OFFLINE) + return info; + + unsigned char battery_status = data_read[BATTERY_STATUS_BYTE]; + switch (battery_status) { + case HEADSET_CHARGING: + info.status = BATTERY_CHARGING; + break; + default: + info.status = BATTERY_AVAILABLE; + break; + } + + info.level = data_read[BATTERY_LEVEL_BYTE]; + + return info; +} + +static int set_inactive_time(hid_device* device_handle, uint8_t num) +{ + uint8_t data[MSG_SIZE] = { 0x0, 0xA3, num }; + + return hid_write(device_handle, data, MSG_SIZE); +} + +static int set_volume_limiter(hid_device* device_handle, uint8_t num) +{ + unsigned char data[MSG_SIZE] = { 0x0, 0x27, num }; + int r = hid_write(device_handle, data, MSG_SIZE); + + if (r < 0) + return r; + + return save_state(device_handle); +} diff --git a/src/devices/steelseries_arctis_nova_5.h b/src/devices/steelseries_arctis_nova_5.h new file mode 100644 index 00000000..df078784 --- /dev/null +++ b/src/devices/steelseries_arctis_nova_5.h @@ -0,0 +1,3 @@ +#pragma once + +void arctis_nova_5_init(struct device** device); From 78e9dcd8e4f15a070807e681bb6dd6d02ebabd98 Mon Sep 17 00:00:00 2001 From: hrzlgnm Date: Sat, 20 Jul 2024 11:42:34 +0200 Subject: [PATCH 2/3] Fix --readme-helper adding a trailing space --- src/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.c b/src/main.c index 68e7b981..3c10a4f4 100644 --- a/src/main.c +++ b/src/main.c @@ -106,9 +106,9 @@ static void print_readmetable() } printf("\n"); - printf("| --- | "); + printf("| --- |"); for (int j = 0; j < NUM_CAPABILITIES; j++) { - printf("--- | "); + printf(" --- |"); } printf("\n"); From ffa2a3ca7ee2658127383393a6c3a9d2a4ff3fda Mon Sep 17 00:00:00 2001 From: Denis Arnst Date: Mon, 22 Jul 2024 16:20:06 +0200 Subject: [PATCH 3/3] Arctis nova 5: format --- src/devices/steelseries_arctis_nova_5.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/devices/steelseries_arctis_nova_5.c b/src/devices/steelseries_arctis_nova_5.c index 6f39fa24..ab879c92 100644 --- a/src/devices/steelseries_arctis_nova_5.c +++ b/src/devices/steelseries_arctis_nova_5.c @@ -19,8 +19,8 @@ enum { }; enum { - HEADSET_CHARGING = 0x01, - HEADSET_OFFLINE = 0x02, + HEADSET_CHARGING = 0x01, + HEADSET_OFFLINE = 0x02, }; static const uint16_t PRODUCT_IDS[]