diff --git a/README.md b/README.md index 31676df..e92e182 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ talking. This differs from a simple loopback via PulseAudio as you won't have an - Sidetone, Battery, Inactive time, Chat-Mix level - SteelSeries Arctis Pro Wireless - Sidetone, Battery, Inactive time +- SteelSeries Arctis Nova Pro Wireless + - Sidetone, Battery, Inactive time - Logitech G PRO - Sidetone, Battery, Inactive time - Logitech Zone Wired/Zone 750 diff --git a/src/device_registry.c b/src/device_registry.c index c3b07da..0cf1758 100644 --- a/src/device_registry.c +++ b/src/device_registry.c @@ -18,11 +18,12 @@ #include "devices/steelseries_arctis_7_plus.h" #include "devices/steelseries_arctis_9.h" #include "devices/steelseries_arctis_nova_7.h" +#include "devices/steelseries_arctis_nova_pro_wireless.h" #include "devices/steelseries_arctis_pro_wireless.h" #include -#define NUMDEVICES 19 +#define NUMDEVICES 20 // array of pointers to device static struct device*(devicelist[NUMDEVICES]); @@ -48,6 +49,7 @@ void init_devices() g535_init(&devicelist[16]); arctis_nova_7_init(&devicelist[17]); calphaw_init(&devicelist[18]); + arctis_nova_pro_wireless_init(&devicelist[19]); } 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 9db9ab2..2673bf2 100644 --- a/src/devices/CMakeLists.txt +++ b/src/devices/CMakeLists.txt @@ -25,6 +25,8 @@ set(SOURCE_FILES ${SOURCE_FILES} ${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_nova_pro_wireless.c + ${CMAKE_CURRENT_SOURCE_DIR}/steelseries_arctis_nova_pro_wireless.h ${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_pro_wireless.c b/src/devices/steelseries_arctis_nova_pro_wireless.c new file mode 100644 index 0000000..31132c9 --- /dev/null +++ b/src/devices/steelseries_arctis_nova_pro_wireless.c @@ -0,0 +1,183 @@ +#include "../device.h" +#include "../utility.h" + +#include +#include + +static struct device device_arctis; + +enum { ID_ARCTIS_NOVA_PRO_WIRELESS_BASE_STATION = 0x12e0 }; + +enum { STATUS_BUF_SIZE = 128 }; + +enum { + BATTERY_MIN = 0x00, + BATTERY_MAX = 0x08, +}; + +enum headset_status { + HEADSET_OFFLINE = 0x01, + HEADSET_CABLE_CHARGING = 0x02, + HEADSET_ONLINE = 0x08, +}; + +enum inactive_time { + INACTIVE_TIME_DISABLED = 0, + INACTIVE_TIME_1_MINUTES = 1, + INACTIVE_TIME_5_MINUTES = 2, + INACTIVE_TIME_10_MINUTES = 3, + INACTIVE_TIME_15_MINUTES = 4, + INACTIVE_TIME_30_MINUTES = 5, + INACTIVE_TIME_60_MINUTES = 6, +}; + +static const uint16_t PRODUCT_IDS[] = { ID_ARCTIS_NOVA_PRO_WIRELESS_BASE_STATION }; + +static int set_sidetone(hid_device* device_handle, uint8_t num); +static int get_battery(hid_device* device_handle); +static int set_inactive_time(hid_device* device_handle, uint8_t minutes); + +static int read_device_status(hid_device* device_handle, unsigned char* data_read); +static int save_state(hid_device* device_handle); + +void arctis_nova_pro_wireless_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 Pro Wireless", sizeof(device_arctis.device_name)); + + device_arctis.capabilities = B(CAP_SIDETONE) | B(CAP_BATTERY_STATUS) | B(CAP_INACTIVE_TIME); + device_arctis.capability_details[CAP_SIDETONE] = (struct capability_detail) { .interface = 4 }; + device_arctis.capability_details[CAP_BATTERY_STATUS] = (struct capability_detail) { .interface = 4 }; + device_arctis.capability_details[CAP_INACTIVE_TIME] = (struct capability_detail) { .interface = 4 }; + + device_arctis.send_sidetone = &set_sidetone; + device_arctis.request_battery = &get_battery; + device_arctis.send_inactive_time = &set_inactive_time; + + *device = &device_arctis; +} + +static int set_sidetone(hid_device* device_handle, uint8_t num) +{ + if (num > 3) { + printf("Device only supports 0-3 range for sidetone (off, low, med, high).\n"); + return HSC_OUT_OF_BOUNDS; + } + + const unsigned char data_request[31] = { 0x06, 0x39, num }; + int res = hid_write(device_handle, data_request, 31); + if (res < 0) + return res; + + return save_state(device_handle); +} + +static int get_battery(hid_device* device_handle) +{ + // read device info + unsigned char data_read[STATUS_BUF_SIZE]; + int res = read_device_status(device_handle, data_read); + + if (res < 0) + return res; + + if (res == 0) + return HSC_READ_TIMEOUT; + + if (res < 16) + return HSC_ERROR; + + uint8_t status = data_read[15]; + switch (status) { + case HEADSET_OFFLINE: + return BATTERY_UNAVAILABLE; + case HEADSET_CABLE_CHARGING: + return BATTERY_CHARGING; + case HEADSET_ONLINE: + break; + default: + fprintf(stderr, "Unknown headset status 0x%.2x.\n", status); + return HSC_ERROR; + } + + int bat = data_read[6]; + + return map(bat, BATTERY_MIN, BATTERY_MAX, 0, 100); +} + +static int set_inactive_time(hid_device* device_handle, uint8_t minutes) +{ + // Cannot set minutes directly, round to nearest + uint8_t num = INACTIVE_TIME_DISABLED; + if (minutes >= 45) { + num = INACTIVE_TIME_60_MINUTES; + } else if (minutes >= 23) { + num = INACTIVE_TIME_30_MINUTES; + } else if (minutes >= 13) { + num = INACTIVE_TIME_15_MINUTES; + } else if (minutes >= 8) { + num = INACTIVE_TIME_10_MINUTES; + } else if (minutes >= 3) { + num = INACTIVE_TIME_5_MINUTES; + } else if (minutes > 0) { + num = INACTIVE_TIME_1_MINUTES; + } + + uint8_t data[31] = { 0x06, 0xc1, num }; + + int res = hid_write(device_handle, data, 31); + if (res < 0) + return res; + + return save_state(device_handle); +} + +/** + * Device status: + * 0-1: packet id (0x06 0xb0) + * 2: BT Default / Bluetooth powerup state: 0 (Off), 1 (On) + * 3: Bluetooth auto mute: 0 (Off), 1 (-12db), 2 (On), + * 4: Bluetooth power status: 1 (Off), 4 (On), + * 5: Bluetooth connection: 0 (Off), 1 (Connected), 2 (Not connected) + * 6: Headset battery charge: 0-8 (0%-100%) + * 7: Charge slot battery charge: 0-8 (0%-100%) + * 8: Transparent noise cancelling level: 0-10 + * 9: Mic status: 0 (Unmuted), 1 (Muted) + * 10: Noise cancelling: 0 (Off), 1 (Transparent on), 2 (ANC on) + * 11: Mic led brightness: 0-10 + * 12: Auto off / Inactive time: See `enum inactive_time` + * 13: 2.4ghz wireless mode: 0 (Speed), 1 (Range) + * 14: Headset pairing: 1 (Not paired), 4 (Paired but offline), 8 (Connected) + * 15: Headset power status: See `enum headset_status` + */ +static int read_device_status(hid_device* device_handle, unsigned char* data_read) +{ + + unsigned char data_request[31] = { 0x06, 0xb0 }; + int res = hid_write(device_handle, data_request, 31); + if (res < 0) + return res; + + // read device info + res = hid_read_timeout(device_handle, data_read, STATUS_BUF_SIZE, hsc_device_timeout); + + if (res < 0) + return res; + + if (res < 2 || !(data_read[0] == 0x06 && data_read[1] == 0xb0)) { + fprintf(stderr, "Wrong id for device status packet\n"); + return HSC_ERROR; + } + + return res; +} + +static int save_state(hid_device* device_handle) +{ + uint8_t data[31] = { 0x06, 0x09 }; + + return hid_write(device_handle, data, 31); +} \ No newline at end of file diff --git a/src/devices/steelseries_arctis_nova_pro_wireless.h b/src/devices/steelseries_arctis_nova_pro_wireless.h new file mode 100644 index 0000000..bb40331 --- /dev/null +++ b/src/devices/steelseries_arctis_nova_pro_wireless.h @@ -0,0 +1,3 @@ +#pragma once + +void arctis_nova_pro_wireless_init(struct device** device);