Conversation
…both
`bus.rs` reads one contiguous twelve-byte block per tick at register 124 --
present_pwm, present_current, present_velocity, present_position -- and unpacks
all four into `Sensors`. Only position has ever reached `robot.state`. The other
two are measured, paid for, and discarded at the socket.
Neither is recoverable from outside the daemon:
* Differencing `joints` between frames is not velocity. Subscribers are
decimated per connection, so a client asking for 10 Hz differences across
five ticks, and any dropped frame becomes a spike.
* Load has no substitute at all. Present current is the robot's only measure
of external force -- a foot taking weight, a hand pressing the beak, a servo
on its way to an overload shutdown.
Both ride as `#[serde(default, skip_serializing_if = "Vec::is_empty")]`, on the
rule `odom` set: a frame from an older `robotd` still parses, and *absent* stays
distinguishable from *zero*. A robot at rest and a robot that cannot say what it
is doing must not render alike. `API_VERSION` goes 27 -> 28 with an entry in the
history, as v24 and v25 did for additive fields on the state stream.
Cost, measured on a frame assembled the way the loop assembles it, with the IMU,
frames and skeleton present: 3042 B -> 3346 B standing (+304 B, +10 %) and
3422 B walking (+380 B, +12 %); the spread is the digits a velocity carries once
it is counts times 0.229 rpm. That is +15 to +19 KB/s at 50 Hz and +3 to +4 KB/s
at a 10 Hz dashboard. The loop already skips frame assembly entirely when nobody
is subscribed, so a robot with no client pays nothing.
`FakeIo` gains `set_velocities` / `set_currents_ma`, mirroring `set_imu`.
Positions are echoed from what was written, and deriving these the same way
would make any test of the new path pass vacuously. `robotctl`'s monitor
fixture names every field of the frame, so it gains the two, empty: the monitor
draws neither.
Tests: the blocks land under their documented names and survive a round trip;
an older frame reports empty rather than zeros; and the control loop, driven by
`FakeIo` with values distinguishable from each other and from the joint angles,
puts both on the stream.
duck-ipc-proto 50 -> 52, robotd 150 -> 151, the workspace 1363 -> 1366,
0 failed. cargo fmt --all --check clean; cargo clippy --workspace
--all-targets clean under -D warnings.
What this does not show. Every test here runs against `FakeIo`, so it covers the
plumbing from `Sensors` to the wire and nothing below it. The unpack in `bus.rs`
is untouched and unmeasured by this change; whether a real XL330 reports a
sensible current under load is a bench question, and I have no hardware.
Deliberately not included. `robotctl monitor` still displays neither field, and
`serde_json` round-trips some f64 on this wire inexactly (1.4000000000000001
comes back 1.4) -- which affects `joints`, `targets` and `gravity` today and is
about 1e-16 against a sensor resolution near 1e-3. Both belong in their own
change, if you want them at all.
Assisted-by: Claude:claude-fable-5-1
xaqmusic
added a commit
to xaqmusic/xaq-ai
that referenced
this pull request
Sep 10, 2026
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CiVPtCnrXZi5jbREK8ohSG Signed-off-by: xaqmusic <xaqmusic@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
bus.rsreads one contiguous twelve-byte block per tick at register 124 —present_pwm,present_current,present_velocity,present_position— and unpacks all four intoSensors. Only position has ever reachedrobot.state. The other two are measured, paid for, and discarded at the socket.Neither is recoverable from outside the daemon. Differencing
jointsbetween frames is not velocity: subscribers are decimated per connection, so a client at 10 Hz differences across five ticks, and any dropped frame becomes a spike. Load has no substitute at all — present current is the robot's only measure of external force: a foot taking weight, a hand pressing the beak, a servo on its way to an overload shutdown.The change
RobotStategainsvelocities(rad/s) andcurrents_ma, indexed asJOINT_NAMES, filled from theSensorsthe loop already holds. Both ride as#[serde(default, skip_serializing_if = "Vec::is_empty")], on the ruleodomset: a frame from an olderrobotdstill parses, and absent stays distinguishable from zero — a robot at rest and a robot that cannot say what it is doing must not render alike.API_VERSIONgoes 27 → 28 with an entry in the history, as v24 and v25 did for additive fields on the state stream.FakeIogainsset_velocities/set_currents_mabesideset_imu. Positions are echoed from what was written, and deriving these the same way would make any test of the new path pass vacuously.robotctl's monitor fixture names every field of the frame, so it gains the two, empty: the monitor draws neither.Cost
Measured on a frame assembled the way the loop assembles it, with the IMU, frames and skeleton present: 3042 B → 3346 B standing (+304 B, +10 %) and 3422 B walking (+380 B, +12 %); the spread is the digits a velocity carries once it is counts × 0.229 rpm. That is +15 to +19 KB/s at 50 Hz and +3 to +4 KB/s at a 10 Hz dashboard. The loop already skips frame assembly when nobody is subscribed, so a robot with no client pays nothing.
Validation
Three tests: the blocks land under their documented names and survive a round trip; an older frame reports empty rather than zeros; and the control loop, driven by
FakeIowith values distinguishable from each other and from the joint angles, puts both on the stream. duck-ipc-proto 50 → 52, robotd 150 → 151, the workspace 1363 → 1366, 0 failed.cargo fmt --all --checkandcargo clippy --workspace --all-targetsunder-D warningsare clean, andcargo llvm-covreports 72.17 % of lines on this branch against 72.11 % onmainand the floor of 72.What it does not show: everything here runs against
FakeIo, so it covers the plumbing fromSensorsto the wire and nothing below it. The unpack inbus.rsis untouched, and whether an XL330 reports a sensible current under load is a bench question — I have no hardware.Out of scope
robotctl monitorstill displays neither field. Andserde_jsonround-trips some f64 on this wire inexactly (1.4000000000000001comes back1.4), which affectsjoints,targetsandgravitytoday and is about 1e-16 against a sensor resolution near 1e-3; the round-trip test compares approximately, on the ruleSafetyStatestates when it dropsEq. Both belong in their own change, if you want them at all.