Documentation
¶
Overview ¶
Package luma talks to a VITURE Luma Ultra headset in pure Go, with CGO_ENABLED=0.
⛔ THE HEADSET NEVER SPEAKS FIRST. Measured 2026-09-06 over 75 seconds, with the buttons on the arm pressed and the head moving, listening on all five of its inbound pipes: twelve identical frames in the first ten milliseconds -- a buffer flushed at open -- and then nothing at all. It is a request and response device, so no amount of listening decodes it. Every fact here came from asking.
That is the opposite of the Beast, which ANNOUNCES what its own buttons do and can be decoded by watching. Do not carry habits from one to the other: they do not even share an envelope.
Index ¶
Constants ¶
const ( // VendorID and ProductID are the Luma Ultra's raw vendor interface. The // companion 35ca:1102 is NOT this: it is an audio-key set whose name -- // "VITURE Microphone" -- has now misled two separate investigations. VendorID uint16 = 0x35ca ProductID uint16 = 0x1104 // EndpointOut and EndpointIn are where a command goes and where its answer // comes back. EndpointOut byte = 0x04 EndpointIn byte = 0x85 )
The device, and the two pipes that carry the protocol.
⭐ MEASURED, not assumed. 35ca:1104 publishes seven endpoints; the two below are the ones the vendor's own getters use, and the ones this package proved on the hardware.
const ( // DrainTimeout bounds each read of the leftover-clearing pass. DrainTimeout = 100 * time.Millisecond // Timeout bounds the command and its answer. Timeout = 1500 * time.Millisecond // DrainReads is how many leftover frames are cleared before asking. DrainReads = 4 )
How long each half of an exchange is given.
⭐ THE VENDOR'S OWN NUMBERS: 100 ms for the drain and 1500 ms for the command and its answer, read out of the getters this package was decoded from.
const ( // CmdVersionA answered "02 01 09" on the headset this was written for. CmdVersionA byte = 0x80 // CmdVersionB answered "00", and is the second frame the vendor's getter // sends. What distinguishes the two is not established. CmdVersionB byte = 0xc3 )
Commands this package has PROVEN on the hardware.
⛔ NOTHING HERE IS GUESSED. Both are frames the vendor's own carina_a1088_get_firmware_version sends, which is why they were safe to try: a version read changes nothing. Twenty-five other command bytes are known to exist and are deliberately absent, because knowing a number is not knowing what it does -- and an unknown opcode written to a headset somebody is wearing is not a probe.
Variables ¶
var ErrNoDevice = errors.New("luma: no VITURE Luma Ultra found")
ErrNoDevice says no Luma Ultra is on the bus.
var ErrNotAFrame = errors.New("luma: not a frame")
ErrNotAFrame says a reply did not begin with Magic.
var ErrUnsupported = errors.New("luma: only macOS is wired up")
ErrUnsupported is returned off macOS.
var ErrWrongCommand = errors.New("luma: the reply answers a different command")
ErrWrongCommand says a reply answered a different command than the one asked.
⭐ THE HEADSET REPEATS THE COMMAND BYTE, which is what makes this checkable without a sequence number -- and worth checking, because the pipe is shared and a late answer to an earlier question looks exactly like an early answer to this one.
var Magic = [2]byte{0xFA, 0x55}
Magic is the two bytes every frame begins with, in the order they go on the wire.
Functions ¶
func Request ¶
Request builds the frame that asks for a command.
FA 55 | command | length | payload
⚠ THE LENGTH IS BIG-ENDIAN HERE, AND ONLY HERE. The vendor's frame builders store it with a 16-bit little-endian write of 0x0100, 0x0200, 0x0300, 0x0800 and 0x4000 -- which put the bytes 00 01, 00 02, 00 03, 00 08, 00 40 on the wire, and the number of payload bytes that follow matches every time when they are read big-endian. A REPLY appears to use the other order, and that is not yet settled: see Reply.
Types ¶
type Glasses ¶
type Glasses struct {
// contains filtered or unexported fields
}
Glasses is one open headset.
func Open ¶
Open finds the headset and claims its vendor interface.
⛔ THE INTERFACE IS EXCLUSIVE. VITURE's own SpaceWalker holds it while it runs, and Open then fails with the system's exclusive-access error rather than silently reading nothing. Measured: it opens freely with that app closed and is refused with it running.
func (*Glasses) Ask ¶
Ask sends one command and returns what came back.
⛔ IT DRAINS FIRST, and that is not housekeeping. The vendor's code clears the inbound pipe before EVERY command, because the pipe is shared and a late answer to an earlier question is indistinguishable from an early answer to this one -- it has the right shape, it arrives at the right moment, and it says the wrong thing. The echoed command byte catches what the drain misses.
func (*Glasses) Version ¶
Version asks for CmdVersionA and returns the bytes it answered.
⚠ THE BYTES, NOT A VERSION. "02 01 09" reads as 2.1.9 and that is a guess about presentation, not a measurement: nothing has confirmed the field order or that all three are version numbers. A caller that wants to print it can; this will not pretend on its behalf.
type Reply ¶
type Reply struct {
// Command is the byte the headset echoed back.
Command byte
// Field is bytes 3 and 4, whatever they mean.
Field [2]byte
// Rest is everything after them, unparsed.
Rest []byte
}
Reply is what came back, split as far as it is understood.
⚠ THE LENGTH FIELD OF A REPLY IS NOT SETTLED, so this hands back the two bytes AND everything after them rather than pretending to know. Measured:
fa 55 80 00 00 02 01 09 "00 00" then THREE bytes fa 55 c3 01 00 00 "01 00" then ONE byte
Read little-endian the second is right and the first is not. Two readings remain open -- those bytes may not be a length at all in a reply -- and guessing here is how nineteen attempts were lost on the sibling headset, which turned out to answer in one byte order and accept commands in another.
func ParseReply ¶
ParseReply splits a frame the headset sent.