Skip to content

Arctis 9 (0x1038:0x12c2) on Windows: battery BATTERY_UNAVAILABLE / HID communication error (WriteFile ERROR_INVALID_FUNCTION on interface 0) #611

Open

Description

@Scherflon

Description

Version / OS
HeadsetControl 4.2.0 (Windows installer) and the continuous portable build (headsetcontrol-windows-x86_64.exe), Windows 11 Home 25H2 (German locale). Headset powered on and connected to the dongle, SteelSeries GG closed. The headset works normally in Windows and in GG.

headsetcontrol -b output

Found 1 supported device(s):
 SteelSeries Arctis 9 (SteelSeries Arctis 9) [0x1038:0x12c2]
Battery:
        Status: BATTERY_UNAVAILABLE
Error: [battery] HID communication error

The continuous build additionally lists a second entry 0x1038:0x12c4 (only HID interface 5, usage page 0x000c) with the same error.

Manual tests with the dev menu (battery request 0x00, 0x20, padded to 2/8/16/32/64 bytes)

  • --send on interface 0: Failed to send: WriteFile: (0x00000001) Illegal function (German: "Unzulaessige Funktion") for every length
  • --send on interface 2: sending works, then No data received (timeout) after 1000 ms
  • --send-feature (+ --receive-feature 0) on interface 0 and 2: Failed to send feature report: HidD_SetFeature: (0x00000001) Illegal function for every length
  • Listening on interface 0 (--receive, 6 s) while sending on interface 2: nothing received

Source analysis
steelseries_arctis_9.hpp hardcodes interface_id = 0 and sends the request (0x00, 0x20) via hid_write(). On Windows hidapi's hid_write() uses WriteFile(), which needs an output report declared in the interface's report descriptor. On this dongle both WriteFile and HidD_SetFeature fail with ERROR_INVALID_FUNCTION on interface 0 (usage page 0xffc0), so it looks like Windows exposes no writable report there. The Linux kernel driver (hid-steelseries) sends the same request as a control-transfer SET_REPORT, which would explain why it works on Linux. The header comment for 0x12c4 also says it is not verified on hardware. This is my reading of the code and the test results, not a confirmed diagnosis.

Happy to run further tests or provide more output.

Headset Name

SteelSeries Arctis 9

On which OS does the problem happen?

Windows

Device information

Detailed Device Information
Device Found
  VendorID:  0x1038
  ProductID: 0x12c2
  Manufacturer: SteelSeries
  Product: SteelSeries Arctis 9
  Interface: 0
  Usage-Page: 0xffc0  Usage-ID: 0x0001

Device Found
  VendorID:  0x1038
  ProductID: 0x12c2
  Manufacturer: SteelSeries
  Product: SteelSeries Arctis 9
  Interface: 1
  Usage-Page: 0x000c  Usage-ID: 0x0001

Device Found
  VendorID:  0x1038
  ProductID: 0x12c2
  Manufacturer: SteelSeries
  Product: SteelSeries Arctis 9
  Interface: 2
  Usage-Page: 0xff00  Usage-ID: 0x0001

Device Found
  VendorID:  0x1038
  ProductID: 0x12c4
  Manufacturer: SteelSeries
  Product: SteelSeries Arctis 9
  Interface: 5
  Usage-Page: 0x000c  Usage-ID: 0x0001

(Output of headsetcontrol --dev -- --list, filtered to the SteelSeries entries; Path/Serial lines omitted.)

Activity

  1. Scherflon commented on Oct 25, 2026

    @Scherflon
    Author

    Update / correction: the diagnosis in my original description was wrong.

    interface_id = 0 in the Arctis 9 device class does not select interface 0. get_hid_path() checks if (!iid || cur_dev->interface_number == iid), so 0 means "first enumerated device". In my --dev -- --list output the first 0x12c2 entry on Windows is MI_01 (usage page 0x000c, consumer control), which cannot be written to. So all my --interface 0 tests (and the normal -b query) went to that interface, not to MI_00 (0xffc0). The explanation about "no writable report on interface 0" is therefore not supported.

    Selecting the interface by usage page works:

    headsetcontrol --dev -- --device 0x1038:0x12c2 --usage 0xffc0:0x0001 --send "0x00, 0x20, 0x00 (padded to 32 bytes)" --receive --timeout 3000
    0xaa 0x01 0x01 0x77 0x00 0x00 0x00 0x00 0xee 0x00 0x00 ... (zeros)
    

    Byte 3 is 0x77, which is about 35 % with the existing min/max (0x64/0x9A). The same request on --usage 0xff00:0x0001 (MI_02) is accepted by WriteFile but gets no answer (timeout).

    So the likely fix is to pin the usage page in getCapabilityDetail() of steelseries_arctis_9.hpp, e.g.

    return { .usagepage = 0xffc0, .usageid = 0x0001, .interface_id = 0 };

    I have not compiled or tested this change myself, so please treat it as a suggestion. I also do not know how it behaves for 0x12c4, which only shows a single consumer-control interface (interface 5) on my machine.

    As a workaround, the --dev command above can be used from a script to read the battery on Windows.

    Sorry for the misleading title and description.

  2. mattenz commented on Oct 26, 2026

    @mattenz

    This seems similar to #588 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions