Add Sony InZone H9 II - #569
Conversation
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.
fits in better
Sapd
left a comment
There was a problem hiding this comment.
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_ON → setBluetoothWhenPoweredOn, CAP_MICROPHONE_MUTE_LED_BRIGHTNESS → setMicMuteLedBrightness (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_MODES→CAP_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-modessays that.getMicAttached→getMicAttachmentStatus, 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 yourtestLookupSonyINZONEH9II()signature into #576'stestLookupJabraLink390()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 oncli/main.cpp,lib/device.hpp,protocols/sony_inzone_protocol.hpp,lib/feature_handlers.hppandlib/headsetcontrol_c.h.- Please drop
shell.nix— happy to add it to.gitignoreinstead if it's useful to you locally. sony_inzone_h9_ii.hppoverridessetMicVolume()to return notSupported, butCAP_MICROPHONE_VOLUMEisn't in itsgetCapabilities(), 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 boundpayload.size(), sobuf[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 —--ancand--anc-button-modesfit 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.
|
Correction to my review, on the descriptor point. I said a general The rule instead: So for the button modes, pass a struct rather than a bare int described as 1..7: 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. |
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
SonyINZONEProtocolbase 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:
Not yet supported:
Checklist
-- I don't think this counts as an application feature? Please let me know if I'm wrong in that.