Skip to content

Add Plantronics/Poly Voyager 8200 UC (BT600) support#536

Open
ggiesen wants to merge 1 commit into
Sapd:masterfrom
ggiesen:add-plantronics-voyager-8200-bt600
Open

Add Plantronics/Poly Voyager 8200 UC (BT600) support#536
ggiesen wants to merge 1 commit into
Sapd:masterfrom
ggiesen:add-plantronics-voyager-8200-bt600

Conversation

@ggiesen

@ggiesen ggiesen commented Jul 3, 2026

Copy link
Copy Markdown

Changes made

Adds support for the Plantronics/Poly Voyager 8200 UC headset via the BT600 USB dongle (047F:02EE), implementing sidetone, battery status, lights, voice prompts and volume limiter (CAP_SIDETONE | CAP_BATTERY_STATUS | CAP_LIGHTS | CAP_VOICE_PROMPTS | CAP_VOLUME_LIMITER).

Protocol. The dongle speaks Poly's native get/set protocol tunneled over HID report ID 0x07 in the vendor collection usage-page 0xFFA2 / usage 0x03 on interface 3. Commands are sent as OUTPUT reports (the interface has no interrupt-OUT endpoint, so they go out as SET_REPORT over EP0) and answered as INPUT reports on interrupt EP 0x81. 64-byte frames, zero padded:

07 01 01 10 <len> <addr> 00 00 <op> <grp> <item> <value...>

addr: 0x00 = the dongle, 0x20 = the headset (replies: 0x00 / 0x02)

HELLO (open session):  07 01 01 10 07 <addr> 00 00 01 01 02
HELLO ack:             07 01 01 10 06 <addr> 00 00 08 01 02
GET (read a setting):  07 01 01 10 06 20 00 00 02 GG II
SET (write a setting): 07 01 01 10 07 20 00 00 05 GG II VV
GET reply:             07 01 01 10 LL 02 00 00 03 GG II <value>
SET ack ("changed"):   07 01 01 10 LL 02 00 00 0a GG II VV
  • The dongle ignores every GET/SET until sessions are opened. The required init (replayed from what the Windows stack sends at device attach, and verified live against a freshly power-cycled headset) is: a 2-byte 06 01 output report, a HELLO addressed to the dongle (this arms the relay to the headset), then a HELLO addressed to the headset. Sessions persist until the dongle loses power or the headset reboots; the sequence is idempotent and is sent before each operation.
  • Sidetone: group 0x04 / item 0x10, 3 discrete levels (0=Low, 1=Medium, 2=High), mapped from the 0-128 input with the existing mapSidetoneToDiscrete<3>().
  • Lights: group 0x04 / item 0x09 (the headset's "online indicator" LED, 0/1).
  • Voice prompts: group 0x04 / item 0x11 (the "notification tones" setting, 0/1).
  • Volume limiter: group 0x0F / item 0x0E (the "noise exposure" limit: 0=none, 1=85 dB, 2=80 dB). Mapped to 85 dB on / no limiting off, the same convention as the Lenovo Wireless VoIP implementation; the 80 dB middle value is documented but not exposed since the capability is boolean.
  • All SETs are verified against the device's "changed" ack, so writing a setting with the headset absent reports device-offline instead of silently succeeding.
  • Battery: group 0x0A / item 0x1A, value <level> <level_count> <charging> <minutes:u16be> 01. Percentage is level / (level_count - 1) * 100 -- Poly's own formula (its software computes level/numLevels*100), verified live against Poly Studio at 70% (level 7) and 60% (level 6). The remaining talk time (minutes) is a separate field reported as time_to_empty_min; it drifts within a level band, so it is not used for the percentage.
  • With the headset powered off or out of range, nothing on this channel is answered (including the headset-addressed HELLO), so read timeouts are reported as device-offline.

Platforms. Linux and Windows. The Windows usagepage/usageid values were confirmed against the dongle's HID report descriptor (report 0x07 lives in the 0xFFA2/0x03 collection). macOS is left disabled: the dongle exposes five top-level HID collections and macOS opens only the first enumerated one, which I cannot test with this device; it can be enabled later if someone with a Mac confirms it works.

How it was derived. Reverse-engineered from USBPcap captures of Plantronics Hub and usbmon captures of Poly Studio, then validated live against the hardware on Linux (headset firmware v2120):

  • Session HELLO verified on a cold dongle: before it, all requests are ignored; after it, they answer immediately.
  • Battery percentage verified live against Poly Studio at two levels (level 7 = 70%, level 6 = 60%); charging flag verified in both directions on/off the charge cable.
  • All three sidetone levels apply, are acked by the device, and read back correctly (cross-checked against the value set in Poly Studio).
  • Lights, voice prompts and volume limiter were mapped by toggling each setting in Poly Studio while capturing (one SET per toggle), then validated live: each SET is acked and reads back with the new value.
  • With the headset powered off, operations report device-offline.

Tests. Unit tests cover the battery reply parser (discharge/charging/clamping/malformed) and reply matching (SET acks, unsolicited events, post-HELLO catalogue fragments) using frames captured from the hardware, plus a registry lookup test. ctest passes; built with GCC 13 on Linux.

Notes.

  • The BT600 dongle ships with several Poly headsets; this was only tested with the Voyager 8200 UC. Other Voyager models pairing with the same dongle will match this implementation and will likely work (the protocol is Poly-generic), but I cannot verify them.
  • Poly software exposes more settings over the same group/item mechanism (mute alert type, ringtone selection, auto-answer, auto-pause, anti-startle, extended range mode, HD voice, streaming audio); these have been mapped from captures and are documented in the device header so the protocol knowledge is preserved, in case HeadsetControl grows matching capabilities.

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 (N/A: adds device support only, no application changes)

@ggiesen ggiesen force-pushed the add-plantronics-voyager-8200-bt600 branch 4 times, most recently from 15430af to e1d0c9d Compare July 3, 2026 14:13
Adds a device implementation for the Plantronics/Poly Voyager 8200 UC
headset via the BT600 USB dongle (047F:02EE), supporting sidetone,
battery status, lights (the online indicator LED), voice prompts (the
notification tones setting) and volume limiter (the noise exposure
limit).

The dongle speaks Poly's native get/set protocol tunneled over HID report
ID 0x07 in the vendor collection usage-page 0xFFA2 / usage 0x03 on
interface 3. Commands are sent as OUTPUT reports and answered as INPUT
reports on interrupt EP 0x81. The address byte selects the target
(0x00 = dongle, 0x20 = headset; replies carry 0x00 / 0x02):

  HELLO (open session): 07 01 01 10 07 <addr> 00 00 01 01 02
  HELLO ack:            07 01 01 10 06 <addr> 00 00 08 01 02
  GET setting:          07 01 01 10 06 20 00 00 02 <grp> <item>
  SET setting:          07 01 01 10 07 20 00 00 05 <grp> <item> <value>
  GET reply:            07 01 01 10 <len> 02 00 00 03 <grp> <item> <value>
  SET ack:              07 01 01 10 <len> 02 00 00 0a <grp> <item> <value>

The dongle ignores all GETs/SETs until sessions are opened with a 06 01
output report, a HELLO to the dongle (arms the relay to the headset) and
a HELLO to the headset - the same init the Windows stack performs at
device attach. Sessions persist until the dongle loses power or the
headset reboots; the sequence is idempotent and sent before every
operation.

- Sidetone:       group 0x04 / item 0x10, 3 levels (0=Low, 1=Med, 2=High)
- Lights:         group 0x04 / item 0x09 ("online indicator", 0/1)
- Voice prompts:  group 0x04 / item 0x11 ("notification tones", 0/1)
- Volume limiter: group 0x0F / item 0x0E ("noise exposure": 0=none,
  1=85 dB, 2=80 dB; mapped to 85 dB on/off, 80 dB left unexposed)
- Battery:        group 0x0A / item 0x1A -> value
  <level> <level_count> <charging> <minutes:u16be> 01. Percentage is
  level / (level_count - 1) * 100 (Poly's own formula, verified live at
  70% and 60%); the remaining talk time is reported as time-to-empty.

SETs are verified against the device's "changed" ack. The headset only
answers while it has an active Bluetooth link to the dongle, so read
timeouts are reported as device-offline. macOS support is left disabled:
the dongle exposes five top-level HID collections and macOS opens only
the first enumerated one, which is untested with this device.

Includes unit tests for battery reply parsing and reply matching (using
frames captured from the hardware) plus a registry lookup test, and the
regenerated README supported-devices row.

Derived from USBPcap/usbmon captures of Plantronics Hub and Poly Studio
and validated live against the hardware (headset firmware v2120).
@ggiesen ggiesen force-pushed the add-plantronics-voyager-8200-bt600 branch from e1d0c9d to 6f6d4b5 Compare July 3, 2026 15:18
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.

1 participant