Skip to content

feature: model URDF <transmission> in the BB DSL#110

Open
jimsynz wants to merge 4 commits into
mainfrom
feature/transmission-dsl
Open

feature: model URDF <transmission> in the BB DSL#110
jimsynz wants to merge 4 commits into
mainfrom
feature/transmission-dsl

Conversation

@jimsynz
Copy link
Copy Markdown
Contributor

@jimsynz jimsynz commented May 20, 2026

Closes #108.

Summary

Adds a transmission block to joints in the BB DSL, mirroring URDF's
<transmission> element for the simple 1:1 case (one joint, one
actuator). Each transmission carries:

  • reduction — gear ratio (default 1.0)
  • offset — joint-space angle (or metres) corresponding to the actuator's zero
  • reversed? — polarity (default false)

All three fields accept param/1 references for runtime adjustment.

Runtime model

BB.Actuator.Server (the wrapper around every actuator driver) resolves
the joint's transmission at init and applies it to incoming
Command.Position / Command.Velocity / Command.Effort /
Command.Trajectory messages before delegating to the user driver. User
drivers see motor-space only.

The transmission is exposed to callbacks via
BB.Actuator.current_transmission/0 for the outbound direction (drivers
that publish joint-space JointState from motor-space hardware readings).

BB.Sim.Actuator runs through the same pipeline: it receives motor-space
from the wrapper, calls BB.Transmission.unapply_position/2 to recover
joint-space, runs the existing clamp + motion-timing logic, and
publishes joint-space BeginMotion exactly as before. Simulated motion
matches real-hardware motion through the transmission.

URDF

  • BB.Urdf.Parser captures <transmission> (SimpleTransmission only —
    coupled types warn and skip) and exposes them keyed by joint name.
  • BB.Urdf.Importer emits a transmission do reduction N end block
    inside each joint with a non-1.0 mechanical reduction.
  • BB.Urdf.Exporter emits <transmission> blocks for joints with a
    non-1.0 reduction.

URDF doesn't have a standard place for BB-specific offset and
reversed?, so transmissions with only those extensions don't
round-trip through URDF — documented limitation.

Out of scope

Coupled transmissions (differential, four-bar) and the top-level
transmissions section are deliberately deferred to a follow-up
issue. The internal data model is per-joint, so they can land as
non-breaking additions later.

Igniter upgrade helper

BB.Igniter.Transmission.lift_reverse_question/3 is a shared upgrade
helper used by the four driver packages
(bb_servo_feetech, bb_servo_robotis, bb_servo_pca9685, bb_servo_pigpio)
to lift their now-removed reverse? opt into the joint's transmission.

Companion PRs

  • beam-bots/bb_servo_feetech#TBD
  • beam-bots/bb_servo_robotis#TBD
  • beam-bots/bb_servo_pca9685#TBD
  • beam-bots/bb_servo_pigpio#TBD
  • beam-bots/bb_so101#TBD
  • beam-bots/bb_example_so101#TBD
  • beam-bots/bb_example_wx200#TBD

This PR is the prerequisite; the driver PRs depend on BB.Transmission,
BB.Transmission.Resolver, and the wrapper changes here.

Test plan

  • mix check --no-retry clean
  • 986 tests pass (was 968 before this branch); 18 new tests cover
    the math module, DSL surface, wrapper integration, sim
    round-trip, URDF parser/importer/exporter, and Igniter upgrader
  • Manually verify the SO-101 and WX200 example robots still run in
    simulation: :kinematic mode after their respective upgrader runs

jimsynz added 4 commits May 19, 2026 08:17
Adds a `transmission` block to joints in the BB DSL, mirroring URDF's
`<transmission>` element for the simple 1:1 case (one joint, one
actuator). The block carries `reduction` (gear ratio), `offset`
(zero-point calibration in radians or metres depending on joint type),
and `reversed?` (polarity). All three fields accept `param/1`
references for runtime adjustment.

Runtime model: `BB.Actuator.Server` (the wrapper around every
actuator driver) resolves the joint's transmission at init and applies
it to incoming `Command.Position`, `Command.Velocity`, `Command.Effort`,
and `Command.Trajectory` messages before delegating to the user driver.
User drivers operate purely in motor-space. The transmission is exposed
to callbacks via `BB.Actuator.current_transmission/0` for the outbound
direction (e.g. publishing joint-space JointState from motor-space
hardware readings).

`BB.Sim.Actuator` follows the same pipeline: receives motor-space from
the wrapper, calls `BB.Transmission.unapply_position/2` to recover
joint-space, then runs the existing clamp + motion-timing logic and
publishes joint-space `BeginMotion` as before. Simulated motion matches
real-hardware motion through the transmission.

Coupled transmissions (differential, four-bar) and the top-level
`transmissions` section are deliberately out of scope for this PR; the
internal data model keeps the runtime as a per-joint concept so they
can land as non-breaking additions later.

Refs: #108
The URDF parser now captures `<transmission>` blocks (URDF-standard
SimpleTransmission only — coupled types warn and skip as before) and
exposes them on the parsed robot keyed by joint name. The importer
emits a `transmission do reduction N end` block inside each joint with
a non-1.0 mechanical reduction.

The exporter emits `<transmission>` XML for joints with a non-1.0
reduction. URDF does not have a standard place for the BB-specific
`offset` and `reversed?` fields, so transmissions with only those
extensions are deliberately not round-tripped through URDF — that's a
documented limitation rather than a bug.

`BB.Urdf.Xml.element/3` now also accepts a binary text body for
elements like `<type>...</type>` and `<mechanicalReduction>...</mechanicalReduction>`.

Refs: #108
Provides the shared Igniter logic each driver package's upgrade task
delegates to. Given a driver module and a `lift_offset?` flag, walks
every module in the project and for each `joint :name do ... end` block:

- Finds `actuator :_, {Driver, opts}` calls (also matches the
  Sourceror-wrapped form `{:__block__, _, [{Driver, opts}]}`).
- Removes the `reverse?:` key from `opts`.
- If `reverse?: true` was present, inserts a `transmission do reversed?
  true end` block after the joint's `limit` block.
- With `lift_offset?: true` (Feetech and Robotis), additionally computes
  `offset = (lower + upper) / 2` from the joint's literal limits and
  emits it in the transmission, preserving the auto-centering the
  drivers used to do internally for asymmetric joints.

Refs: #108
- Format the verifier and Igniter upgrader to satisfy mix format.
- Reorder aliases in BB.Transmission.Resolver alphabetically.
- Refactor BB.Urdf.Parser.parse_transmission/1 (split a cond branch
  out into smaller classify/build_transmission heads to drop the
  cyclomatic complexity below the credo threshold).
- Refactor BB.Igniter.Transmission.lift_reverse_question/3
  (extracted lift_in_module/4) and unit_literal/1 (collapsed the
  nested case statements via with) so neither nests past two levels.
- Use Enum.map_join/3 in BB.Urdf.Parser.text_content/1.
- Drop the unreachable format_number/1 fallback clause flagged by
  dialyzer.

Refs: #108
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.

Model URDF <transmission> blocks in the BB DSL

1 participant