From 178c37c2493b3e5cd46fbc3f9e839ae6c8981a17 Mon Sep 17 00:00:00 2001 From: Mauricio Ackermann Date: Fri, 16 Sep 2022 14:33:10 -0400 Subject: [PATCH 1/4] adds support for SteelSeries Arctis Nova 7 --- README.md | 2 + src/device_registry.c | 4 +- src/devices/CMakeLists.txt | 2 + src/devices/steelseries_arctis_nova_7.c | 136 ++++++++++++++++++++++++ src/devices/steelseries_arctis_nova_7.h | 3 + 5 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 src/devices/steelseries_arctis_nova_7.c create mode 100644 src/devices/steelseries_arctis_nova_7.h diff --git a/README.md b/README.md index 4e7b2d04..7fe39387 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ talking. This differs from a simple loopback via PulseAudio as you won't have an - Sidetone, Battery, Inactive time, Chat-Mix level, LED on/off (allows to turn off the blinking LED on the base-station) - SteelSeries Arctis 7+ - Sidetone, Battery, Inactive time, Equalizer Presets +- SteelSeries Arctis Nova 7 + - Sidetone, Battery, Inactive time, Equalizer Presets - SteelSeries Arctis 9 - Sidetone, Battery, Inactive time, Chat-Mix level - SteelSeries Arctis Pro Wireless diff --git a/src/device_registry.c b/src/device_registry.c index f1e3934e..68ef9fbc 100644 --- a/src/device_registry.c +++ b/src/device_registry.c @@ -15,11 +15,12 @@ #include "devices/steelseries_arctis_7.h" #include "devices/steelseries_arctis_7_plus.h" #include "devices/steelseries_arctis_9.h" +#include "devices/steelseries_arctis_nova_7.h" #include "devices/steelseries_arctis_pro_wireless.h" #include -#define NUMDEVICES 16 +#define NUMDEVICES 17 // array of pointers to device static struct device*(devicelist[NUMDEVICES]); @@ -42,6 +43,7 @@ void init_devices() elo71USB_init(&devicelist[13]); arctis_7_plus_init(&devicelist[14]); cflight_init(&devicelist[15]); + arctis_nova_7_init(&devicelist[16]); } int get_device(struct device* device_found, uint16_t idVendor, uint16_t idProduct) diff --git a/src/devices/CMakeLists.txt b/src/devices/CMakeLists.txt index 0b6222a4..4f965610 100644 --- a/src/devices/CMakeLists.txt +++ b/src/devices/CMakeLists.txt @@ -19,6 +19,8 @@ set(SOURCE_FILES ${SOURCE_FILES} ${CMAKE_CURRENT_SOURCE_DIR}/steelseries_arctis_7_plus.c ${CMAKE_CURRENT_SOURCE_DIR}/steelseries_arctis_9.c ${CMAKE_CURRENT_SOURCE_DIR}/steelseries_arctis_9.h + ${CMAKE_CURRENT_SOURCE_DIR}/steelseries_arctis_nova_7.h + ${CMAKE_CURRENT_SOURCE_DIR}/steelseries_arctis_nova_7.c ${CMAKE_CURRENT_SOURCE_DIR}/steelseries_arctis_pro_wireless.c ${CMAKE_CURRENT_SOURCE_DIR}/steelseries_arctis_pro_wireless.h ${CMAKE_CURRENT_SOURCE_DIR}/roccat_elo_7_1_air.h diff --git a/src/devices/steelseries_arctis_nova_7.c b/src/devices/steelseries_arctis_nova_7.c new file mode 100644 index 00000000..b193ab1a --- /dev/null +++ b/src/devices/steelseries_arctis_nova_7.c @@ -0,0 +1,136 @@ +#include "../device.h" +#include "../utility.h" + +#include +#include +#include +#include + +#define MSG_SIZE 64 + +static struct device device_arctis; + +#define ID_ARCTIS_NOVA_7 0x2202 + +#define BATTERY_MAX 0x04 +#define BATTERY_MIN 0x00 + +#define HEADSET_OFFLINE 0x01 + +static const uint16_t PRODUCT_IDS[] = { ID_ARCTIS_NOVA_7 }; + +static int arctis_nova_7_send_sidetone(hid_device* device_handle, uint8_t num); +static int arctis_nova_7_send_inactive_time(hid_device* device_handle, uint8_t num); +static int arctis_nova_7_send_equalizer_preset(hid_device* device_handle, uint8_t num); +static int arctis_nova_7_request_battery(hid_device* device_handle); + +int arctis_nova_7_read_device_status(hid_device* device_handle, unsigned char* data_read); + +void arctis_nova_7_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 7", sizeof(device_arctis.device_name)); + + device_arctis.capabilities = B(CAP_SIDETONE) | B(CAP_BATTERY_STATUS) | B(CAP_INACTIVE_TIME) | B(CAP_EQUALIZER_PRESET); + 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_INACTIVE_TIME] = (struct capability_detail) { .usagepage = 0xffc0, .usageid = 0x1, .interface = 3 }; + device_arctis.capability_details[CAP_EQUALIZER_PRESET] = (struct capability_detail) { .usagepage = 0xffc0, .usageid = 0x1, .interface = 3 }; + + device_arctis.send_sidetone = &arctis_nova_7_send_sidetone; + device_arctis.request_battery = &arctis_nova_7_request_battery; + device_arctis.send_inactive_time = &arctis_nova_7_send_inactive_time; + device_arctis.send_equalizer_preset = &arctis_nova_7_send_equalizer_preset; + + *device = &device_arctis; +} + +static int arctis_nova_7_send_sidetone(hid_device* device_handle, uint8_t num) +{ + // num, will be from 0 to 128, we need to map it to the correct value + // the range of the Arctis 7+ is from 0 to 3 + num = map(num, 0, 128, 0, 3); + + uint8_t data[MSG_SIZE] = { 0x00, 0x39, num }; + + return hid_write(device_handle, data, MSG_SIZE); +} + +static int arctis_nova_7_send_inactive_time(hid_device* device_handle, uint8_t num) +{ + // as the value is in minutes, mapping to a different range does not make too much sense here + // the range of the Arctis 7+ is from 0 to 0x5A (90) + + uint8_t data[MSG_SIZE] = { 0x00, 0xa3, num }; + + return hid_write(device_handle, data, MSG_SIZE); +} + +static int arctis_nova_7_request_battery(hid_device* device_handle) +{ + // read device info + unsigned char data_read[6]; + int r = arctis_nova_7_read_device_status(device_handle, data_read); + + if (r < 0) + return r; + + if (r == 0) + return HSC_READ_TIMEOUT; + + if (data_read[1] == HEADSET_OFFLINE) + return BATTERY_UNAVAILABLE; + + if (data_read[3] == 0x01) + return BATTERY_CHARGING; + + int bat = data_read[2]; + + if (bat > BATTERY_MAX) + return 100; + + return map(bat, BATTERY_MIN, BATTERY_MAX, 0, 100); +} + +static int arctis_nova_7_send_equalizer_preset(hid_device* device_handle, uint8_t num) +{ + // This headset supports only 4 presets: + // flat (default), bass boost, smiley, focus + + switch (num) { + case 0: { + uint8_t flat[MSG_SIZE] = { 0x0, 0x33, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x0 }; + return hid_write(device_handle, flat, MSG_SIZE); + } + case 1: { + uint8_t bass[MSG_SIZE] = { 0x0, 0x33, 0x1f, 0x20, 0x1a, 0x15, 0x15, 0x16, 0x16, 0x16, 0x16, 0x23, 0x0 }; + return hid_write(device_handle, bass, MSG_SIZE); + } + case 2: { + uint8_t smiley[MSG_SIZE] = { 0x0, 0x33, 0x1e, 0x1b, 0x15, 0x10, 0x10, 0x13, 0x1b, 0x1e, 0x20, 0x1f, 0x0 }; + return hid_write(device_handle, smiley, MSG_SIZE); + } + case 3: { + uint8_t focus[MSG_SIZE] = { 0x0, 0x33, 0x0e, 0x16, 0x11, 0x13, 0x20, 0x24, 0x1f, 0x11, 0x18, 0x11, 0x0 }; + return hid_write(device_handle, focus, MSG_SIZE); + } + default: { + printf("Device only supports 0-3 range for presets.\n"); + return HSC_OUT_OF_BOUNDS; + } + } +} + +int arctis_nova_7_read_device_status(hid_device* device_handle, unsigned char* data_read) +{ + unsigned char data_request[2] = { 0x00, 0xb0 }; + int r = hid_write(device_handle, data_request, 2); + + if (r < 0) + return r; + + return hid_read_timeout(device_handle, data_read, 6, hsc_device_timeout); +} diff --git a/src/devices/steelseries_arctis_nova_7.h b/src/devices/steelseries_arctis_nova_7.h new file mode 100644 index 00000000..3084013e --- /dev/null +++ b/src/devices/steelseries_arctis_nova_7.h @@ -0,0 +1,3 @@ +#pragma once + +void arctis_nova_7_init(struct device** device); From 5f3f55891212fc9fc6c5a52688598e990d32d203 Mon Sep 17 00:00:00 2001 From: Mauricio Ackermann Date: Tue, 20 Sep 2022 10:48:53 -0400 Subject: [PATCH 2/4] adds the arctis nova 7x id --- src/devices/steelseries_arctis_nova_7.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/devices/steelseries_arctis_nova_7.c b/src/devices/steelseries_arctis_nova_7.c index b193ab1a..43a57f3c 100644 --- a/src/devices/steelseries_arctis_nova_7.c +++ b/src/devices/steelseries_arctis_nova_7.c @@ -11,13 +11,14 @@ static struct device device_arctis; #define ID_ARCTIS_NOVA_7 0x2202 +#define ID_ARCTIS_NOVA_7x 0x2206 #define BATTERY_MAX 0x04 #define BATTERY_MIN 0x00 #define HEADSET_OFFLINE 0x01 -static const uint16_t PRODUCT_IDS[] = { ID_ARCTIS_NOVA_7 }; +static const uint16_t PRODUCT_IDS[] = { ID_ARCTIS_NOVA_7, ID_ARCTIS_NOVA_7x }; static int arctis_nova_7_send_sidetone(hid_device* device_handle, uint8_t num); static int arctis_nova_7_send_inactive_time(hid_device* device_handle, uint8_t num); From 83841eec51fe1d82e64440c872098bd5e194983a Mon Sep 17 00:00:00 2001 From: Mauricio Ackermann Date: Mon, 26 Sep 2022 17:10:49 -0400 Subject: [PATCH 3/4] adds support for chatmix --- README.md | 2 +- src/devices/steelseries_arctis_nova_7.c | 31 ++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7fe39387..35d9b0ef 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ talking. This differs from a simple loopback via PulseAudio as you won't have an - SteelSeries Arctis 7+ - Sidetone, Battery, Inactive time, Equalizer Presets - SteelSeries Arctis Nova 7 - - Sidetone, Battery, Inactive time, Equalizer Presets + - Sidetone, Battery, Chat-Mix level, Inactive time, Equalizer Presets - SteelSeries Arctis 9 - Sidetone, Battery, Inactive time, Chat-Mix level - SteelSeries Arctis Pro Wireless diff --git a/src/devices/steelseries_arctis_nova_7.c b/src/devices/steelseries_arctis_nova_7.c index 43a57f3c..7f2b9f56 100644 --- a/src/devices/steelseries_arctis_nova_7.c +++ b/src/devices/steelseries_arctis_nova_7.c @@ -24,6 +24,7 @@ static int arctis_nova_7_send_sidetone(hid_device* device_handle, uint8_t num); static int arctis_nova_7_send_inactive_time(hid_device* device_handle, uint8_t num); static int arctis_nova_7_send_equalizer_preset(hid_device* device_handle, uint8_t num); static int arctis_nova_7_request_battery(hid_device* device_handle); +static int arctis_nova_7_request_chatmix(hid_device* device_handle); int arctis_nova_7_read_device_status(hid_device* device_handle, unsigned char* data_read); @@ -35,14 +36,16 @@ void arctis_nova_7_init(struct device** device) strncpy(device_arctis.device_name, "SteelSeries Arctis Nova 7", sizeof(device_arctis.device_name)); - device_arctis.capabilities = B(CAP_SIDETONE) | B(CAP_BATTERY_STATUS) | B(CAP_INACTIVE_TIME) | B(CAP_EQUALIZER_PRESET); + device_arctis.capabilities = B(CAP_SIDETONE) | B(CAP_BATTERY_STATUS) | B(CAP_CHATMIX_STATUS) | B(CAP_INACTIVE_TIME) | B(CAP_EQUALIZER_PRESET); 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_CHATMIX_STATUS] = (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_EQUALIZER_PRESET] = (struct capability_detail) { .usagepage = 0xffc0, .usageid = 0x1, .interface = 3 }; device_arctis.send_sidetone = &arctis_nova_7_send_sidetone; device_arctis.request_battery = &arctis_nova_7_request_battery; + device_arctis.request_chatmix = &arctis_nova_7_request_chatmix; device_arctis.send_inactive_time = &arctis_nova_7_send_inactive_time; device_arctis.send_equalizer_preset = &arctis_nova_7_send_equalizer_preset; @@ -96,6 +99,32 @@ static int arctis_nova_7_request_battery(hid_device* device_handle) return map(bat, BATTERY_MIN, BATTERY_MAX, 0, 100); } +static int arctis_nova_7_request_chatmix(hid_device* device_handle) +{ + // read device info + unsigned char data_read[6]; + int r = arctis_nova_7_read_device_status(device_handle, data_read); + + if (r < 0) + return r; + + if (r == 0) + return HSC_READ_TIMEOUT; + + // it's a slider, but setting for game and chat + // are reported as separate values, we combine + // them back into one setting of the slider + + // the two values are between 0 and 100, + // we translate that to a value from 0 to 128 + // with "64" being in the middle + + int game = map(data_read[4], 0, 100, 0, 64); + int chat = map(data_read[5], 0, 100, 0, -64); + + return 64 - (chat + game); +} + static int arctis_nova_7_send_equalizer_preset(hid_device* device_handle, uint8_t num) { // This headset supports only 4 presets: From e41e4b63a508e3469fced6f8c8d858fff5e2540f Mon Sep 17 00:00:00 2001 From: Mauricio Ackermann Date: Thu, 20 Oct 2022 18:16:18 -0400 Subject: [PATCH 4/4] adds support for Arctis Nova 7p --- src/devices/steelseries_arctis_nova_7.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/devices/steelseries_arctis_nova_7.c b/src/devices/steelseries_arctis_nova_7.c index 7f2b9f56..4e62493f 100644 --- a/src/devices/steelseries_arctis_nova_7.c +++ b/src/devices/steelseries_arctis_nova_7.c @@ -10,15 +10,16 @@ static struct device device_arctis; -#define ID_ARCTIS_NOVA_7 0x2202 -#define ID_ARCTIS_NOVA_7x 0x2206 +#define ID_ARCTIS_NOVA_7 0x2202 +#define ID_ARCTIS_NOVA_7x 0x2206 +#define ID_ARCTIS_NOVA_7p 0x220a #define BATTERY_MAX 0x04 #define BATTERY_MIN 0x00 #define HEADSET_OFFLINE 0x01 -static const uint16_t PRODUCT_IDS[] = { ID_ARCTIS_NOVA_7, ID_ARCTIS_NOVA_7x }; +static const uint16_t PRODUCT_IDS[] = { ID_ARCTIS_NOVA_7, ID_ARCTIS_NOVA_7x, ID_ARCTIS_NOVA_7p }; static int arctis_nova_7_send_sidetone(hid_device* device_handle, uint8_t num); static int arctis_nova_7_send_inactive_time(hid_device* device_handle, uint8_t num);