Documentation
¶
Overview ¶
Package beast talks to a VITURE Beast headset in pure Go, with CGO_ENABLED=0.
⛔ IT IS NOT THE SAME PROTOCOL AS THE LUMA, and the two do not even share an envelope: a Beast frame begins 0x10 and a Luma frame begins FA 55. Habits do not carry across. What they do share is a house style -- ask before you write, and judge by what the device answers rather than by what the screen does.
⭐ THE HEADSET ANSWERS EVERY COMMAND WITH A STATUS, and that is worth more than watching the display: a screen that does not change cannot tell a refusal from a command that never arrived. Finding that out took nineteen failed writes.
The frames themselves live in go-macos/iokit/viture, which decoded them. This package is the transport and the ergonomics on top: open the right interface, listen before asking, and hand back what the headset said.
Index ¶
- Constants
- Variables
- type DOF
- type Glasses
- func (g *Glasses) Close() error
- func (g *Glasses) DOF() (DOF, error)
- func (g *Glasses) Get(msg byte) (uint16, error)
- func (g *Glasses) Info() (Info, error)
- func (g *Glasses) Native() (bool, error)
- func (g *Glasses) Nudge(msg byte, by int, max uint16) (uint16, error)
- func (g *Glasses) Recenter() error
- func (g *Glasses) Set(msg byte, value uint16) error
- func (g *Glasses) SetDOF(d DOF) error
- type Info
- type Status
Constants ¶
const ( VendorID uint16 = 0x35ca UsagePage uint16 = 0xff00 )
The device this speaks to.
⭐ 35ca:1201 ON THE VENDOR USAGE PAGE is the control interface, and it is the only one of the three the glasses publish that carries the protocol: the others are called "VITURE Microphone" and are a Consumer-page audio set. The name misleads and the usage page does not -- a mistake this fleet has now made in both directions.
const ( // DirText is the high half of the ids that answer with text. DirText byte = 0x30 // MsgBoardSerial identifies the board, MsgPackageSerial the product, and // MsgFirmwareVersion is the version a person recognises. MsgBoardSerial byte = 0x02 MsgPackageSerial byte = 0x05 MsgFirmwareVersion byte = 0x03 )
⭐⭐ A FAMILY OF READS THIS PACKAGE COULD NOT REACH.
Glasses.Get sends viture.DirRead, which is 0x31 -- and that byte is the HIGH half of a 16-bit message id, so every read this package could make was 0x31xx. The ids that answer with TEXT are 0x30xx: a neighbouring family, one nibble away, and unreachable through Get.
The ids come from VITURE's own web updater, which carries an R6 path for this headset, and every one is a READ -- which is what makes them safe to send.
const Wait = 2 * time.Second
Wait is how long the headset is given to answer.
Generous for a device that answers in milliseconds, and short enough that a menu row does not appear to hang.
Variables ¶
var ErrNoAnswer = errors.New("beast: the headset did not answer")
ErrNoAnswer says the headset did not reply in time.
var ErrNoDevice = errors.New("beast: no VITURE Beast found")
ErrNoDevice says no Beast control interface is on the bus.
var ErrNoSetting = errors.New("beast: the headset has no such setting")
ErrNoSetting means the headset answered that it has no such message.
⚠ AND IT IS A STATE, NOT A FACT ABOUT THE PROTOCOL. Measured 2026-09-05: 0x22 and 0x30 both answered a read at 17:00 and both were gone by 18:46, with the same cable and no replug -- while every other id still answered. 0x22 came back the next morning. So report it; do not compile it in.
var ErrUnsupported = errors.New("beast: only macOS is wired up")
ErrUnsupported is returned off macOS.
Functions ¶
This section is empty.
Types ¶
type DOF ¶ added in v0.2.0
type DOF uint16
DOF is where the picture sits while the head moves.
⭐ THE GLASSES DO THIS THEMSELVES. Nothing is computed here and no camera is opened: the Beast tracks its own orientation and composites the host's video where the tracking says. That is the whole difference from the Luma Ultra, whose 6DOF is visual-inertial odometry run on the host from the cameras -- and it is why this is a command rather than a project.
⛔ THERE IS NO 6DOF HERE, AND ASKING FOR ONE WOULD BE INVENTING IT. The manufacturer's public header lists exactly three native modes and no more. Position tracking lives in viture_device_carina.h, which is the Luma's.
const ( // DOFNone is the picture fixed to the glasses: it goes where the head goes. DOFNone DOF = 0 // DOF3 anchors the picture in space. Turn your head and it stays put. DOF3 DOF = 1 // DOFSmoothFollow lets the picture trail the head instead of being nailed // to either: it drifts back to centre rather than staying behind. DOFSmoothFollow DOF = 2 )
type Glasses ¶
type Glasses struct {
// contains filtered or unexported fields
}
Glasses is one open headset.
func (*Glasses) Close ¶
Close releases it.
⛔ IT WAITS FOR THE LISTENER. Closing a HID device while a stream still holds it does not fail, it ends the PROCESS -- macOS kills the program with "os_unfair_lock is corrupt", no Go panic, no stack, nothing on the program's own output. go-macos/iokit refuses that now; this does it in the right order anyway.
func (*Glasses) DOF ¶ added in v0.2.0
DOF reads which of the three the glasses are in.
⛔⛔ THE MESSAGE IS 0x43 AND NOT 0x44, AND GETTING THAT WRONG REFRAMES SOMEBODY'S DESK. What the headset ANNOUNCES and what it is TOLD are numbered differently and collide: 0x44 announces the tracking mode and is WRITTEN to set the side mode. Writing 1 there to anchor a picture makes it small and puts it bottom-left, with nothing anchored -- measured, on a real desk. See viture.CmdNativeDOF for the three measurements that settled it. ⛔⛔ AND IT DOES NOT GO THROUGH Glasses.Get, BECAUSE Get CANNOT READ ONE OF THE THREE MODES. A read reply carrying 2 is ambiguous on this protocol: it is either the value two or the status "no such message", and Get resolves that ambiguity the only way it can in general -- as the refusal. Smooth follow IS two. So the general reader reports the headset has no tracking setting at the exact moment it is in the middle one.
It is resolved here because this message is not the general case: 0x43 was measured answering 0 and 1 on real hardware, so the setting exists and a 2 is the value. That reasoning does not transfer to any other message and is not pushed down into Get.
func (*Glasses) Get ¶
Get reads one setting and returns its value.
⛔ A READ CHANGES NOTHING, which is what makes it the right first experiment on any device: it is safe to repeat, and its success is visible in a way a command's is not.
⭐ AND A READ ANSWERING 2 MEANS "NO SUCH MESSAGE", which makes the whole id space discoverable without ever writing. Measured: twenty messages exist between 0x00 and 0x7f and every other id answers 2.
func (*Glasses) Info ¶ added in v0.3.0
Info asks the headset for its firmware version and serials.
⛔ A FIELD IT COULD NOT READ STAYS EMPTY. There is no second number to fall back on and no reason to invent one: a wrong version on a screen is worse than a blank, because nobody can tell it is wrong.
func (*Glasses) Native ¶ added in v0.2.0
Native reports whether the glasses composite the picture themselves (true) or show the host's video as it arrives (false).
⭐ IT IS THE PRECONDITION FOR EVERYTHING ELSE IN THIS FILE. A headset in bypass has nothing to anchor: it is a monitor.
func (*Glasses) Nudge ¶
Nudge moves a setting by one step, clamped, and says where it ended up.
⛔ IT READS FIRST. A key that means "brighter" has to know what it is brighter than, and the headset is the only thing that knows: somebody may have used the buttons on the arm since anything here last looked.
func (*Glasses) Recenter ¶ added in v0.2.0
Recenter puts an anchored picture back in front of whoever is wearing the glasses.
⛔ IT IS A SEPARATE COMMAND, AND ANCHORING WITHOUT IT LEAVES THE PICTURE WHEREVER THE HEAD HAPPENED TO BE. The vendor library writes a payload of zero to 0x30, which is also the byte the headset announces its VOLUME on -- another collision between the two numberings, and another reason the two lists are kept apart.
func (*Glasses) Set ¶
Set writes one setting and reports what the headset said about it.
⛔ THE VALUE GOES IN TWICE, LITTLE-ENDIAN. That one detail is what nineteen attempts had wrong: the headset's own REPLIES carry a value little-endian and then big-endian, and copying that shape into a command has it refused. A reply and a command are not the same frame.
func (*Glasses) SetDOF ¶ added in v0.2.0
SetDOF puts the glasses in one of the three modes.
⚠ IT NEEDS THE GLASSES TO BE IN NATIVE MODE, and this does not put them there. The manufacturer's header says native tracking is refused outright while the device is in bypass -- showing the host's video as it arrives rather than compositing it. Glasses.Native reports which it is in.
type Info ¶ added in v0.3.0
type Info struct {
// FirmwareVersion is the application firmware: "20.0.01.027_20260825" on
// the headset this was written against.
FirmwareVersion string
// BoardSerial identifies the board. PackageSerial identifies the product
// and is EMPTY on a headset that has none -- this one answers 0xff repeated
// for it, which is the vendor's own way of saying absent.
BoardSerial, PackageSerial string
}
Info is what the headset says about itself.
type Status ¶
type Status uint16
Status is what the headset answers to a write.
⭐ IT ANSWERS EVERY ONE, and that is the whole reason this package judges by the reply rather than by the display. Nineteen writes were lost watching a screen: a screen that does not change cannot tell a refusal from a command that never arrived, and one read settled in a second what those nineteen had not.
const ( // StatusOK means the command was taken. StatusOK Status = 0 // StatusRefused means the headset understood and said no -- which is how // its own limits were mapped: brightness accepted 0 to 8 and refused 9, // with nothing to watch and nobody guessing. StatusRefused Status = 4 // StatusTooShort means the command carried fewer than two payload bytes. StatusTooShort Status = 6 )
What the headset says back.