Skip to content

Add Sony InZone H9 II - #569

Open
Kirth wants to merge 9 commits into
Sapd:masterfrom
Kirth:sony_inzone_h9_ii
Open

Add Sony InZone H9 II #569
Kirth wants to merge 9 commits into
Sapd:masterfrom
Kirth:sony_inzone_h9_ii

Conversation

@Kirth

@Kirth Kirth commented Aug 29, 2026

Copy link
Copy Markdown

Changes made

Adds support for the Sony INZONE H9 II wireless gaming headset (VID 0x054C, PID 0x0FA8, 2.4 GHz USB dongle, MI_05&COL03).

The H9 II uses the same Sony HCI-over-HID protocol as the INZONE H5, so this PR also extracts that shared logic into a SonyINZONEProtocol base class that both devices now inherit from. I would like to use this as a base to add support for the Sony InZone H6 Air in a follow-up PR :).

In order to support ANC control, ANC Startup Mode (is anc turned on or off when the device gets powered on), and Boom Mic Attachment Status (it can be removed .. and misplaced).

Supported Capabilities:

  • 0x04 CAP_BATTERY_STATUS
  • 0x22 CAP_CHATMIX_STATUS
  • 0x23 CAP_SIDETONE
  • 0x41 CAP_ANC
  • 0x43 CAP_ANC_STARTUP_MODE
  • 0x81 CAP_INACTIVE_TIME
  • 0x84 CAP_VOICE_PROMPTS "guidance"
  • 0x8F CAP_MIC_STATUS
  • 0x63 CAP_BT_WHEN_POWERED_ON
  • 0x24 MIC_VOLUME - reports mic mute state (physical button on top of the left earcup)
  • 0x42 NC_TOGGLE_SETTINGS - configure what the physical ANC button cycles through; e.g. cut out pesky environment mode

Not yet supported:

  • 0x01 2GHZ_CONNECT_STATUS
  • 0x02 MOD_INFO (could go in getMetadata()??)
  • 0x03 FW_VERSION (also getMetadata?)
  • 0x62 BT_SOUND_QUALITY bool: stable connection vs sound quality prio
  • 0x82 LED_SETTINGS: this is about automatically turning the LEDs off after some time, not on off; could this be covered by CAP_LIGHTS or should this be a new capability?
  • 0x83 VP_LANGUAGE_SETTING - en/ja/zh

Checklist

  • I adjusted the README (if needed)
  • For new features in HeadsetControl: I discussed it beforehand in Issues or Discussions and adhered to the wiki
    -- I don't think this counts as an application feature? Please let me know if I'm wrong in that.

Kirth added 9 commits August 29, 2026 22:38
Extracts the Sony HCI-over-HID protocol into SonyINZONEProtocol and
refactors SonyINZONEH5 to use it, then adds SonyINZONEH9II (PID 0x0FA8)
with battery, chatmix, sidetone, inactive time, voice prompts, and
Bluetooth-when-powered-on support.
Adds headphone-side ANC control (EID 0x41) to the Sony INZONE protocol.
Mode 0 = off, 1 = noise cancellation, 2 = ambient sound.
Adds control over the ANC mode the H9 II powers on in (EID 0x43).
Mode 0 = off, 1 = noise cancelling, 2 = ambient sound, 3 = restore last.
Adds a read-only capability (EID 0x8F) that reports whether the
detachable boom mic is physically plugged into the H9 II headset.
Exposed via --mic-status in the CLI and rendered in all output formats.
…_MIC_STATUS

Adds three new columns and a row for the Sony INZONE H9 II.

@Sapd Sapd left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this — the H9 II support is good work, and pulling the shared HCI-over-HID logic into SonyINZONEProtocol is the right call. It builds clean here and the unit suite passes. The README table matches --readme-helper exactly, and appending the new capabilities to the end of CAPABILITIES_XLIST (rather than inserting) was exactly right — the hsc_capability_t values don't shift, so no ABI break.

A few things to sort out before I can merge it.

The one that needs fixing first: the H5 loses its interface selection

The extraction changed a value on the way:

old lib/devices/sony_inzone_h5.hpp:102   { .usagepage = 0xFF04, .usageid = 0x0002, .interface_id = 0 }
new protocols/sony_inzone_protocol.hpp:75 { .usagepage = 0xFF04, .usageid = 0x0002, .interface_id = 5 }

The new H5 doesn't override getCapabilityDetail(), so it inherits interface_id = 5. In lib/hid_utility.cpp:55 the usage-page match is inside #if defined(WIN32), so on Linux and macOS we fall through to if (!iid || cur_dev->interface_number == iid) — where interface_id = 0 means "take the first match". That's what the H5 relied on, and the old file had a comment saying so which got dropped in the move.

So as it stands the H5 will only open interface 5 on Linux. Nobody on this PR has an H5 to notice.

I'd remove getCapabilityDetail() from the protocol base altogether — that's device topology, not wire protocol — and put the original back on the H5. The base's current default describes neither device anyway (usageid 0x0002 is the H5's, interface_id 5 is the H9 II's). The H9 II already overrides with {0xFF04, 0x0001, 5}, so nothing else changes.

While you're in there: the old H5 had really good layout comments on exchange() (why TID 1 is skipped), buildCommand() (byte offsets, checksum range) and parseEvent(). Those got lost in the move — could you bring them across? That documentation is the most valuable part of the file.

mic_muted is missing from YAML and ENV

$ headsetcontrol --test-device --microphone-mute-status --microphone-attachment-status -o json
      "mic_attached": 1,
      "mic_muted": 0
$ ... -o yaml
    mic_attached: 1
$ ... -o env
DEVICE_0_MIC_ATTACHED=1

JSON and standard emit both, YAML and ENV only emit mic_attached. Needs the matching writes in cli/output/output.cpp.

--anc clobbers ambient volume and voice focus

sony_inzone_protocol.hpp:257 sends { mode, 20, 0xFF, 0 } — ambient level hard-coded to 20, voice focus to off, on every call. Someone who set ambient level 10 and voice focus on in INZONE Hub loses both every time they run headsetcontrol --anc 2. Can you GET 0x41 first and copy bytes 1-3 from the reply, overwriting only byte 0? If GET isn't supported for that EID, say so in a comment and at least give the constants names.

Question on the mic attachment byte

sony_inzone_protocol.hpp:218 reads attached = (resp->payload[0] == 0) — an inversion on an event called MIC_ATTACHED_STATUS. Is the byte really a "removed" flag? A Hub capture in a comment would settle it. Also worth validating payload[0] is 0 or 1 the way getSonyMicMuteStatus does — battery and chatmix already handle a 0xFF offline placeholder, and here that would silently read as "detached".

Naming

You asked whether five new capabilities counts as an application feature — it does, mainly because these names are permanent: they go into the C API, the CLI, the JSON/YAML keys and the README. So let's fix them now rather than live with them.

The main thing is that the codebase camel-cases acronyms in method names and never shouts them: CAP_BT_WHEN_POWERED_ONsetBluetoothWhenPoweredOn, CAP_MICROPHONE_MUTE_LED_BRIGHTNESSsetMicMuteLedBrightness (Led, not LED). Your result structs already follow this (AncResult), it's just the methods that don't.

CAP_* CLI flag virtual result struct
CAP_ANC --anc <0|1|2> setAnc AncResult
CAP_ANC_STARTUP_MODE --anc-startup-mode <0..3> setAncStartupMode AncStartupModeResult
CAP_ANC_BUTTON_MODES --anc-button-modes off,anc,ambient setAncButtonModes AncButtonModesResult
CAP_MICROPHONE_ATTACHMENT_STATUS --microphone-attachment-status getMicAttachmentStatus MicAttachmentStatusResult
CAP_MICROPHONE_MUTE_STATUS --microphone-mute-status getMicMuteStatus MicMuteStatusResult

Two renames worth explaining:

  • CAP_ANC_TOGGLE_MODESCAP_ANC_BUTTON_MODES. "Toggle" reads as "toggle ANC on and off", but what it actually configures is which modes the physical button cycles through. --anc-button-modes says that.
  • getMicAttachedgetMicAttachmentStatus, so the two info getters are parallel with each other and with their capability names.

CAP_ANC and CAP_ANC_STARTUP_MODE I'd keep as you have them — CAP_SIDETONE / CAP_SIDETONE_STATUS is the existing precedent for a bare primary with qualified siblings.

Two smaller points in the same area:

Ordering. The list currently reads ANC, ANC_STARTUP_MODE, MICROPHONE_ATTACHMENT_STATUS, MICROPHONE_MUTE_STATUS, ANC_BUTTON_MODES because the button-modes cap arrived in the last commit. That order is permanent once released (C ABI values, README column order), so please group the three ANC caps together and the two mic caps together while it's still free.

The library API only exposes 2 of the 5. Headset::getMicMuteStatus and setANCToggleModes are in headsetcontrol.hpp / the C API, the other three aren't. There's precedent for not exposing (CAP_NOISE_FILTER has no Headset:: method), but the 2-of-5 split will just confuse FFI users. I'd expose all five.

--anc-button-modes and the descriptor table

capability_descriptors.hpp declares min_value = 1, max_value = 7 — a bitmask — but value_hint is <off,anc,ambient> and the real parsing is parseANCToggleModes() in cli/main.cpp. So the library accepts the int 7 while the CLI rejects "3" with "unknown ANC toggle mode". Two different contracts for one capability, and anything building a UI off CAPABILITY_DESCRIPTORS gets it wrong.

For this PR I'd just make the CLI parser also accept the raw mask and fix the hint — a few lines, and the two paths agree.

The better answer is a ParamKind { Range, Enum, Flags } plus a value_names table on CapabilityDescriptor, which would delete the bespoke parser and let --anc off work by name too. But that's a change to the descriptor model that affects every capability, and I'd rather do it as its own PR than bundle it here — there's a related discussion coming about an RGB colour capability that hits the same limitation, so it makes sense to design them together.

Housekeeping

  • Needs a rebase — #574, #575, #576 and #577 have landed since you branched. Two conflicts, both mechanical, but watch tests/test_device_registry.cpp: git splits it into two hunks and a naive "keep both sides" interleaves your testLookupSonyINZONEH9II() signature into #576's testLookupJabraLink390() body. It compiles into nonsense rather than failing loudly. Keep the two functions whole and separate.
  • Regenerate the README after rebasing (--readme-helper) rather than merging it by hand — your five new columns and the two new Jabra rows both have to land.
  • make format — clang-format 18 currently fails on cli/main.cpp, lib/device.hpp, protocols/sony_inzone_protocol.hpp, lib/feature_handlers.hpp and lib/headsetcontrol_c.h.
  • Please drop shell.nix — happy to add it to .gitignore instead if it's useful to you locally.
  • sony_inzone_h9_ii.hpp overrides setMicVolume() to return notSupported, but CAP_MICROPHONE_VOLUME isn't in its getCapabilities(), so it's unreachable. The comment explaining why the H9 II's mic volume is a Windows endpoint setting is worth keeping — just as a comment.
  • Minor: buildCommand() doesn't bound payload.size(), so buf[13 + payload_len] would overflow past ~50 bytes. Every current caller sends 4 bytes or fewer so it isn't reachable, but it's cheap to guard while you're in the file.
  • Minor: the three --anc* flags landed under "POWER & BLUETOOTH" in the help. ANC is audio — --anc and --anc-button-modes fit better under AUDIO.

Once the H5 regression and the naming are sorted the rest is mechanical, and I'm happy to take it. Looking forward to the H6 Air follow-up.

@Sapd

Sapd commented Sep 11, 2026

Copy link
Copy Markdown
Owner

Correction to my review, on the descriptor point.

I said a general ParamKind {Range, Enum, Flags} in CAPABILITY_DESCRIPTORS was the better answer and should be a follow-up PR. Scrap that - I don't want it. It's a few hundred lines of plumbing serving two cases that both have a cheaper path.

The rule instead: int in FeatureParam is for scalars in a range. Anything else is a typed struct in the variant, min/max = nullopt, with a value_hint - which is what CAP_EQUALIZER already does.

So for the button modes, pass a struct rather than a bare int described as 1..7:

struct AncButtonModes { bool off, anc, ambient; };

Result<AncButtonModesResult> setAncButtonModes(hid_device*, const AncButtonModes&)

That makes the CLI and the library agree by construction, so the "make the parser also accept the raw mask" fix I suggested isn't needed either - drop it.

Same rule just went into #572 for the colour capability, so the two land consistently.

Rest of the review stands.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants