Vhi.LSLCommunicationController
VHI_godot¶
Vhi¶
LSLCommunicationController Class¶
All Lab Streaming Layer I/O for VHI - the only node that touches LSL directly (and even then only through LSLWrapper, never SharpLSL).
One stream per DOF, in. Every control VHI exports is its own LSL stream,
named by its own address and one channel wide: vhi.prediction.index,
vhi.control.pose.thumb.flexion, and their siblings — the same names
GetControlManifest publishes. A sample is applied to the hand the moment it
arrives, and the DOFs that did not deliver hold what they were last commanded to.
There is no whole-pose frame and nothing waits for one: the DOFs are independently
actuated, may come from different producers and may update at different rates, so a
hand whose index has moved and whose thumb has not is a real pose.
Two nine-channel outlets, out, unchanged: VHI_Control and
VHI_Predict publish each hand's whole pose at 60 Hz. A read-back is a
recording, and a recording wants one row per instant.
Stream resolution blocks ~1 s so it runs on a background Task.Run thread;
results and log lines are marshalled back to Godot's main thread via
CallDeferred. Sample pulls themselves are non-blocking and happen in
_Process. Streams are \<i>found\</i> by name, but a found stream is only opened
when it is \<b>exactly one channel wide\</b> — see Vhi.LSLCommunicationController.ResolveMissingInletsAsync.
Inheritance System.Object → Godot.GodotObject → Godot.Node → LSLCommunicationController
Fields¶
LSLCommunicationController.ControlOutletName Field¶
Name of the LSL outlet that publishes the control hand's pose at 60 Hz. Consumed by MyoGestic as a regression-target source.
Field Value¶
LSLCommunicationController.ControlPoseStaleAfterSeconds Field¶
Silence after which a control-pose DOF's inlet is assumed gone.
Field Value¶
Remarks¶
Presence is the only thing that hands the control hand to these streams, so "the
producer stopped" has to be observable here rather than inferred from an error —
PullSample(object, float[], double) catches every exception and returns 0.0, so
no error ever reaches this file and a lost producer is indistinguishable from an idle
one. The clock is the whole signal.
It settles two things, not one: the DOF stops counting toward ControlPoseLive, and its inlet is dropped so the by-name resolve can find whoever publishes that name next. Without the second, the first producer to exit cleanly pinned that inlet to a corpse for the life of the process.
LSLCommunicationController.ExpectedChannels Field¶
Channel count of the 9-DOF hand pose the two outlets publish.
Field Value¶
LSLCommunicationController.PoseConvention Field¶
What +1 means on both pose outlets, published in their metadata.
Field Value¶
Remarks¶
standard: +1 is the direction the channel's name denotes, 0 is
rest, the domain is [-1, 1]. Recordings made before this existed are in
legacy — the rig's own units, where the five flexion channels ran the other way
— and a reader that finds no pose_convention must assume that, not this.
LSLCommunicationController.PredictedOutletName Field¶
Name of the LSL outlet that publishes the predicted hand's pose at 60 Hz. For monitoring / recording alongside the EMG.
Field Value¶
LSLCommunicationController.PredictionStaleAfterSeconds Field¶
Silence after which a prediction DOF's inlet is assumed dead and dropped.
Field Value¶
Remarks¶
Generous on purpose. A `ControlBus` outlet sends at its own rate whether or not the values changed, so real silence means the producer went away — but a script that pushes a frame and then sleeps is also legitimate, and dropping its inlet only costs a re-resolve. Long enough not to punish that; short enough that replacing an outlet (which the control-map editor does on every save) reconnects while you are still looking at the hand.
Properties¶
LSLCommunicationController.ControlPoseLive Property¶
Whether \<i>any\</i> control-pose stream is delivering right now.
Property Value¶
Remarks¶
`ControlHandSkeleton` reads this to decide whether to render what it was commanded or run its movement state machine. It is the whole of the old `Declare(control_pose=true)` handshake: publish a stream and the hand follows it.
Any, not all. There are nine control-pose streams now and a producer is
under no obligation to publish more than one — driving a single DOF from a single
producer is the capability this shape exists for, so requiring all nine would mean such
a producer never took the hand at all. One live DOF is a client driving this hand, and
the eight that are silent are DOFs nobody is driving, which is not the same as nobody
driving the hand. The falling edge is therefore the last stream going quiet,
which is when the hand is genuinely unclaimed and StopToRest is right.
Methods¶
LSLCommunicationController.SendControlData(List\<float>) Method¶
Push a 9-DOF pose sample to the VHI_Control outlet.
The sample is silently
dropped if the outlet has not been created or if its length does not
match ExpectedChannels. Called by
ControlHandSkeleton every _PhysicsProcess tick.
Parameters¶
data System.Collections.Generic.List<System.Single>
9 float channels - see the LSL stream reference for the channel layout.
LSLCommunicationController.SendPredictedData(List\<float>) Method¶
Push a 9-DOF pose sample to the VHI_Predict outlet.
Same shape and contract as
SendControlData(List<float>). Called by
PredictedHandSkeleton every _PhysicsProcess tick.
Parameters¶
data System.Collections.Generic.List<System.Single>
9 float channels - see the LSL stream reference for the channel layout.
what System.Int32
delta System.Double