@@ -29,15 +29,16 @@ namespace headsetcontrol {
2929 */
3030class SteelSeriesArctisNova7 : public protocols ::SteelSeriesNovaDevice<SteelSeriesArctisNova7> {
3131public:
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