Skip to content

Commit b627b4b

Browse files
Sapdaarol
andauthored
Steelseries Gen2 uses absolute battery (#457)
* Steelseries Gen2 uses absolute battery * Fix mingw * Fix static linking * Update lib/devices/steelseries_arctis_nova_7.hpp Co-authored-by: Aaro Luomanen <71641519+aarol@users.noreply.github.com> --------- Co-authored-by: Aaro Luomanen <71641519+aarol@users.noreply.github.com>
1 parent 6611933 commit b627b4b

3 files changed

Lines changed: 56 additions & 15 deletions

File tree

CMakeLists.txt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,11 @@ if(WIN32)
6565
# Use static runtime to avoid VC++ Redistributable dependency
6666
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
6767
else()
68-
# MinGW/GCC flags
69-
set(CMAKE_C_STANDARD_LIBRARIES "-lsetupapi -static-libgcc -static-libstdc++ -lwsock32 -lws2_32 ${CMAKE_CXX_STANDARD_LIBRARIES}")
70-
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Bstatic,--whole-archive -lwinpthread -Wl,--no-whole-archive")
71-
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
68+
# MinGW/GCC flags - statically link runtime to avoid DLL dependencies
69+
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libgcc -static-libstdc++ -Wl,-Bstatic,--whole-archive -lwinpthread -Wl,--no-whole-archive")
70+
set(CMAKE_C_STANDARD_LIBRARIES "-lsetupapi -lwsock32 -lws2_32 ${CMAKE_C_STANDARD_LIBRARIES}")
71+
set(CMAKE_CXX_STANDARD_LIBRARIES "-lsetupapi -lwsock32 -lws2_32 ${CMAKE_CXX_STANDARD_LIBRARIES}")
72+
add_compile_options(-Wall)
7273
endif()
7374
else()
7475
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")

cli/main.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include <algorithm>
3838
#include <cassert>
3939
#include <chrono>
40+
#include <cstdio>
4041
#include <csignal>
4142
#include <cstdlib>
4243
#include <format>
@@ -1101,6 +1102,12 @@ bool checkDeviceConnected(DiscoveredDevice& selected, const Options& opts)
11011102

11021103
int main(int argc, char* argv[])
11031104
{
1105+
#if defined(_WIN32) && defined(__GNUC__)
1106+
// Disable stdout/stderr buffering for MinGW builds
1107+
setvbuf(stdout, nullptr, _IONBF, 0);
1108+
setvbuf(stderr, nullptr, _IONBF, 0);
1109+
#endif
1110+
11041111
Options opts;
11051112
cli::ArgumentParser parser(argv[0]);
11061113
configureParser(parser, opts);

lib/devices/steelseries_arctis_nova_7.hpp

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,16 @@ namespace headsetcontrol {
2929
*/
3030
class SteelSeriesArctisNova7 : public protocols::SteelSeriesNovaDevice<SteelSeriesArctisNova7> {
3131
public:
32-
static constexpr std::array<uint16_t, 8> SUPPORTED_PRODUCT_IDS {
33-
0x2202, // Arctis Nova 7
34-
0x227e, // Arctis Nova 7 Wireless
35-
0x2206, // Arctis Nova 7x
36-
0x2258, // Arctis Nova 7x v2
37-
0x229e, // Arctis Nova 7x v2
38-
0x223a, // Arctis Nova 7 Diablo IV (before Jan Update)
39-
0x22a9, // Arctis Nova 7 Diablo IV (after Jan Update)
40-
0x227a // Arctis Nova 7 WoW Edition
32+
static constexpr std::array<uint16_t, 9> SUPPORTED_PRODUCT_IDS {
33+
0x2202, // Arctis Nova 7 (discrete battery: 0-4)
34+
0x22A1, // Arctis Nova 7 (percentage battery: 0-100, Jan. 2026 update)
35+
0x227e, // Arctis Nova 7 Wireless Gen 2 (percentage battery: 0-100)
36+
0x2206, // Arctis Nova 7x (discrete battery: 0-4)
37+
0x2258, // Arctis Nova 7x v2 (percentage battery: 0-100)
38+
0x229e, // Arctis Nova 7x v2 (percentage battery: 0-100)
39+
0x223a, // Arctis Nova 7 Diablo IV (discrete battery: 0-4, before Jan 2026 update)
40+
0x22a9, // Arctis Nova 7 Diablo IV (percentage battery: 0-100, after Jan 2026 update)
41+
0x227a // Arctis Nova 7 WoW Edition (discrete battery: 0-4)
4142
};
4243

4344
static constexpr int EQUALIZER_BANDS = 10;
@@ -122,12 +123,44 @@ class SteelSeriesArctisNova7 : public protocols::SteelSeriesNovaDevice<SteelSeri
122123
return DeviceError::deviceOffline("Headset not connected");
123124
}
124125

126+
// Auto-detect battery protocol (Gen 2 vs original models):
127+
//
128+
// Original models (0x2202, 0x2206, 0x220a, 0x223a, 0x227a):
129+
// - Battery: data[2] in discrete levels 0-4 (0%/25%/50%/75%/100%)
130+
// - Status: data[3] = 0x01 when charging, other non-zero when on battery
131+
//
132+
// Gen 2 models (0x227e, possibly 0x2258):
133+
// - Battery: data[2] as direct percentage 0-100
134+
// - Status: data[3] = 0x01 charging, 0x02 fully charged, 0x03 on battery
135+
//
136+
// Detection heuristics (since we don't have product_id here):
137+
// 1. Status byte 0x02 or 0x03 → Gen 2 protocol
138+
// 2. Battery value > 4 → Gen 2 protocol (wouldn't be valid in discrete mode)
139+
//
140+
// TODO: Known edge case - Gen 2 at 1-4% battery while actively charging (status=0x01)
141+
// will be misdetected as original protocol and show inflated percentage
142+
// (1%→25%, 2%→50%, 3%→75%, 4%→100%). This is extremely rare because:
143+
// - Requires plugging in exactly at 1-4% battery
144+
// - At low battery, devices typically show status=0x03 (on battery)
145+
// - Self-corrects once battery charges past 4%
146+
// - Only lasts a few seconds/minutes
147+
// Proper fix would require passing product_id to getBattery() method.
148+
bool is_gen2_protocol = (data[3] == 0x02 || data[3] == 0x03) || (data[2] > 4);
149+
125150
enum battery_status status = BATTERY_AVAILABLE;
126-
if (data[3] == 0x01) {
151+
if (data[3] == 0x01 || data[3] == 0x02) {
127152
status = BATTERY_CHARGING;
128153
}
129154

130-
int level = map(data[2], 0, 4, 0, 100);
155+
int level;
156+
if (is_gen2_protocol) {
157+
// Gen 2: Direct percentage reporting (0-100) in data[2]
158+
level = data[2];
159+
} else {
160+
// Original models: Discrete levels (0-4) that need mapping
161+
level = map(data[2], 0, 4, 0, 100);
162+
}
163+
131164
if (level > 100)
132165
level = 100;
133166

0 commit comments

Comments
 (0)