Beam Bots Resilient Robotics on the BEAM - A framework for building fault-tolerant robotics applications in Elixir Zola 2026-03-09T00:00:00+00:00 https://beambots.dev/atom.xml Week Thirteen: Other People's Robots 2026-03-09T00:00:00+00:00 2026-03-09T00:00:00+00:00 Unknown https://beambots.dev/blog/progress-2026-03-09/ <p>Two quiet weeks from me. Two very not-quiet weeks from other people. This is the best kind of progress update.</p> <h2 id="first-community-driver-bb-ufactory">First Community Driver: bb_ufactory</h2> <p><a rel="external" href="https://github.com/houlette">Holden Oullette</a> has built <a rel="external" href="https://github.com/BeautifulGirlCoffee/bb_ufactory">bb_ufactory</a> — a full Beam Bots integration for uFactory xArm cobot arms. This is the first driver built entirely by someone outside the project, and it's not a toy.</p> <p>What's in the box:</p> <ul> <li><strong>Joint and Cartesian motion</strong> — command individual joints or go straight to end-effector pose (IK solved on-arm)</li> <li><strong>Gripper and linear track</strong> — G2 gripper position control plus RS485-proxied linear axis</li> <li><strong>Force/torque sensing</strong> — 6-axis F/T data streamed from 135-byte report frames</li> <li><strong>Collision detection</strong> — configurable sensitivity with event publishing</li> <li><strong>100Hz ETS control loop</strong> — batched joint commands dispatched at high frequency</li> <li><strong>Push-based state</strong> — joint angles, TCP pose, and torques arrive via the real-time report socket, no polling</li> <li><strong>Safety integration</strong> — <code>disarm/1</code> opens a fresh TCP connection to stop the arm even if the GenServer has crashed</li> <li><strong>Five arm models</strong> — xArm5, xArm6, xArm7, Lite6, and xArm850</li> </ul> <p>The architecture follows the same Controller/Actuator/Sensor pattern as the existing servo drivers, communicating over two TCP connections using uFactory's Modbus-TCP variant: a command socket on port 502 and a real-time report socket on port 30003.</p> <p>It even ships with a pre-built <code>BB.Ufactory.Robots.XArm6</code> module so you can get moving with minimal DSL boilerplate. Seriously impressive work, Holden.</p> <h2 id="bb-policy-gets-a-champion">bb_policy Gets a Champion</h2> <p><a rel="external" href="https://github.com/joelpaulkoch">Joel Koch</a> has picked up the <a rel="external" href="https://github.com/beam-bots/proposals/blob/main/accepted/0002-bb-policy.md">bb_policy proposal</a> and is taking a swing at implementation. The goal: let Beam Bots robots execute learned behaviours — think HuggingFace LeRobot models running inference on the BEAM, with policies mapping observations to actions at 20+ Hz.</p> <p>The proposal defines a <code>BB.Policy</code> behaviour with <code>observe/3</code>, <code>act/2</code>, and <code>action_to_commands/3</code> callbacks, a <code>Runner</code> GenServer for the control loop, and an ONNX implementation via Ortex as the primary deployment path. The BEAM is genuinely well-suited for this — a crashed policy shouldn't crash a robot, and slow inference shouldn't block sensors. Supervision trees and lightweight processes give you that for free.</p> <p>Early days, but this is exactly the kind of thing that makes an open-source robotics framework worth building. More as it develops.</p> <h2 id="elixir-1-20-approaches">Elixir 1.20 Approaches</h2> <p><a rel="external" href="https://github.com/elixir-lang/elixir/releases/tag/v1.20.0-rc.2">Elixir 1.20.0-rc.2</a> dropped on March 4th. The highlights:</p> <ul> <li><strong>~10% faster compilation</strong> plus a new interpreted mode that scales to your core count</li> <li><strong>Type inference across clauses</strong> — the compiler finds more bugs and dead code automatically</li> <li><strong>Redundant clause detection</strong> — warns on dead code paths</li> <li><strong>Fewer recompilations</strong> when modifying struct definitions (files that only pattern match or update structs no longer recompile)</li> </ul> <p>Phoenix 1.8.5 and LiveView 1.1.26 have already shipped Elixir 1.20 compatibility fixes. Both have been bumped across the Beam Bots ecosystem via Dependabot. Testing BB against the RC is on the short list.</p> <h2 id="upstream-releases">Upstream Releases</h2> <p>A few other notable releases since the last update:</p> <table><thead><tr><th>Package</th><th>Version</th><th>What's interesting</th></tr></thead><tbody> <tr><td><a rel="external" href="https://hex.pm/packages/kino/0.19.0">Kino</a></td><td>0.19.0</td><td><code>:min</code>, <code>:max</code>, <code>:step</code> on <code>Kino.Input.number/2</code> — directly useful for <code>bb_kino</code> joint control widgets</td></tr> <tr><td><a rel="external" href="https://github.com/livebook-dev/livebook/releases/tag/v0.19.0">Livebook</a></td><td>0.19.0</td><td>Linux desktop app via Tauri (beta)</td></tr> <tr><td><a rel="external" href="https://hex.pm/packages/spark/2.4.1">Spark</a></td><td>2.4.1</td><td>OTP 28 Dialyzer warning fixes, sibling section handling</td></tr> <tr><td><a rel="external" href="https://hex.pm/packages/phoenix/1.8.5">Phoenix</a></td><td>1.8.5</td><td>Socket reconnection fix, Elixir 1.20 compatibility</td></tr> <tr><td><a rel="external" href="https://hex.pm/packages/phoenix_live_view/1.1.26">LiveView</a></td><td>1.1.26</td><td><code>phx-click-away</code> fixes, Elixir 1.20 compatibility</td></tr> </tbody></table> <p>The Kino number input constraints are the most immediately actionable — constraining joint angle inputs to valid ranges with appropriate step sizes is an obvious win for the Livebook widgets.</p> <h2 id="dependency-gardening">Dependency Gardening</h2> <p>Dependabot's been busy while I haven't. Across the ecosystem:</p> <table><thead><tr><th>Package</th><th>From</th><th>To</th><th>Bumped in</th></tr></thead><tbody> <tr><td>spark</td><td>2.4.0</td><td>2.4.1</td><td>bb</td></tr> <tr><td>phoenix_live_view</td><td>1.1.24</td><td>1.1.26</td><td>bb_liveview, bb_example_wx200</td></tr> <tr><td>phoenix</td><td>1.8.3</td><td>1.8.5</td><td>bb_example_wx200</td></tr> <tr><td>kino</td><td>0.18.0</td><td>0.19.0</td><td>bb_kino</td></tr> <tr><td>igniter</td><td>0.7.2</td><td>0.7.3</td><td>bb, bb_liveview, bb_kino, bb_servo_pca9685, bb_servo_robotis, bb_example_wx200</td></tr> <tr><td>credo</td><td>1.7.16</td><td>1.7.17</td><td>bb, bb_liveview, bb_kino, bb_servo_pca9685, bb_servo_robotis</td></tr> <tr><td>git_ops</td><td>2.9.0</td><td>2.9.2</td><td>bb, bb_kino, bb_servo_feetech, feetech</td></tr> <tr><td>bandit</td><td>1.10.2</td><td>1.10.3</td><td>bb_liveview</td></tr> </tbody></table> <p>Nothing breaking. The garden stays tidy.</p> <h2 id="what-s-next">What's Next</h2> <p>The servo driver rework is still the priority on my end. But honestly, this fortnight's highlight is other people building things with the framework — that's the whole point. If you're working on something with Beam Bots, come say hello on <a rel="external" href="https://discord.gg/QSag7Vuc4N">Discord</a>.</p> <h2 id="links">Links</h2> <ul> <li><a rel="external" href="https://github.com/BeautifulGirlCoffee/bb_ufactory">bb_ufactory on GitHub</a></li> <li><a rel="external" href="https://github.com/beam-bots/proposals/blob/main/accepted/0002-bb-policy.md">bb_policy proposal</a></li> <li><a rel="external" href="https://github.com/elixir-lang/elixir/releases/tag/v1.20.0-rc.2">Elixir 1.20.0-rc.2</a></li> <li><a rel="external" href="https://hex.pm/packages/kino/0.19.0">Kino 0.19.0 on Hex</a></li> <li><a rel="external" href="https://github.com/livebook-dev/livebook/releases/tag/v0.19.0">Livebook 0.19.0</a></li> <li><a rel="external" href="https://github.com/beam-bots/bb">bb on GitHub</a></li> <li><a rel="external" href="https://github.com/beam-bots/proposals">Proposals repo on GitHub</a></li> <li><a rel="external" href="https://discord.gg/QSag7Vuc4N">Discord</a></li> </ul> Week Eleven: The Quiet Fortnight 2026-02-23T00:00:00+00:00 2026-02-23T00:00:00+00:00 Unknown https://beambots.dev/blog/progress-2026-02-23/ <p>No progress post last week — sometimes the interesting work is the kind that doesn't ship yet. Here's what's been happening across the last fortnight.</p> <h2 id="beam-bots-at-nerves-meetup-eu">Beam Bots at Nerves Meetup EU</h2> <p>The highlight of the fortnight came from someone else entirely. Gus Workman presented <a rel="external" href="https://youtu.be/0igh46GLI6c">"An early peek at the official Nerves hardware starter kit"</a> at the <a rel="external" href="https://nervesmeetup.eu/">Nerves Meetup EU</a> on February 11th. The talk included a shoutout to Beam Bots and a mention of our plans to build a balance bot with the starter kit — I'm excited to get my hands on one. Thanks, Gus!</p> <h2 id="servo-driver-architecture-rework-in-progress">Servo Driver Architecture Rework (In Progress)</h2> <p>The SO-101 demo video that was "coming soon" two weeks ago is still coming. The delay is deliberate: I'm reworking the driver architecture for both the Feetech and Robotis servo integrations to make moves smoother and avoid collisions on their respective serial links.</p> <p>This work shouldn't require changes to <code>bb</code> core, but it will change how the servo driver packages coordinate serial bus access. The current approach works fine for single commands, but when you're running coordinated multi-servo moves at speed, you need tighter control over who's talking on the wire and when.</p> <p>More on this when there's code to show.</p> <h2 id="nx-0-11-0">Nx 0.11.0</h2> <p>The <a rel="external" href="https://hex.pm/packages/nx/0.11.0">Nx 0.11.0</a> release landed on February 19th and has already been bumped in <code>bb</code> core (<a rel="external" href="https://github.com/beam-bots/bb/pull/58">#58</a>). Highlights include FP8 E4M3FN support, <code>Nx.Mesh</code> and <code>Nx.Defn.shard_jit/3</code> for multi-device execution, <code>Nx.runtime_call/3</code>, and a fix for <code>Nx.sort/2</code> gradients. No breaking changes — just a clean upgrade.</p> <h2 id="dependency-gardening">Dependency Gardening</h2> <p>Dependabot kept busy while I didn't. Across the ecosystem:</p> <table><thead><tr><th>Package</th><th>Bumped in</th><th>Change</th></tr></thead><tbody> <tr><td>bb</td><td>0.15.0 → 0.15.1</td><td>bb_servo_feetech, bb_servo_pca9685, bb_servo_robotis, bb_ik_dls, bb_ik_fabrik, bb_kino, bb_liveview, bb_reactor</td></tr> <tr><td>phoenix_live_view</td><td>1.1.22 → 1.1.24</td><td>bb_liveview, bb_example_wx200</td></tr> <tr><td>feetech</td><td>0.2.1 → 0.2.2</td><td>bb_servo_feetech</td></tr> <tr><td>bb_servo_feetech</td><td>0.2.0 → 0.2.1</td><td>bb_example_so101</td></tr> <tr><td>bb_reactor</td><td>0.2.0 → 0.2.1</td><td>bb_example_so101, bb_example_wx200</td></tr> <tr><td>bb_liveview</td><td>0.2.3 → 0.2.4</td><td>bb_example_wx200</td></tr> </tbody></table> <p>Nothing breaking — the ecosystem stays tidy even when the maintainer is elbow-deep in serial protocols.</p> <h2 id="what-s-next">What's Next</h2> <p>The servo driver rework is the priority. Once that's solid, the SO-101 demo video can finally happen. For real this time. Probably.</p> <h2 id="links">Links</h2> <ul> <li><a rel="external" href="https://youtu.be/0igh46GLI6c">Gus's Nerves Starter Kit talk</a></li> <li><a rel="external" href="https://nervesmeetup.eu/">Nerves Meetup EU</a></li> <li><a rel="external" href="https://hex.pm/packages/nx/0.11.0">Nx 0.11.0 on Hex</a></li> <li><a rel="external" href="https://github.com/beam-bots/bb">bb on GitHub</a></li> <li><a rel="external" href="https://github.com/beam-bots/proposals">Proposals repo on GitHub</a></li> <li><a rel="external" href="https://discord.gg/QSag7Vuc4N">Discord</a></li> </ul> Week Nine: No Hardware Required 2026-02-10T00:00:00+00:00 2026-02-10T00:00:00+00:00 Unknown https://beambots.dev/blog/progress-2026-02-10/ <p>This one's a day late. I'd hoped to ship an exciting SO-101 demo video yesterday, but the universe had other plans. Still, the code is out there and that's what matters.</p> <h2 id="the-so-101-example-is-live">The SO-101 Example Is Live</h2> <p>The big news this week: <a rel="external" href="https://github.com/beam-bots/bb_example_so101">bb_example_so101</a> is now on GitHub.</p> <p>It's a complete Phoenix application demonstrating the Beam Bots framework with a <a rel="external" href="https://github.com/TheRobotStudio/SO-ARM100">SO-101 robot arm</a> - the open-source 6-DOF arm from TheRobotStudio that uses Feetech STS3215 servos. If you've been following along and wondering "but what does a real Beam Bots application actually look like?" - this is your answer.</p> <p>What's included:</p> <ul> <li><strong>Robot definition</strong> using the BB DSL - all six joints plus gripper, with proper kinematic chain from the official URDF</li> <li><strong>Web dashboard</strong> with real-time 3D visualisation via <code>bb_liveview</code></li> <li><strong>Servo setup wizard</strong> (<code>mix so101.setup_servos</code>) - interactive tool for assigning unique IDs to each servo (they all ship as ID 1, because of course they do)</li> <li><strong>Calibration tool</strong> (<code>mix so101.calibrate</code>) - disables torque, prompts you to move each joint through its full range, writes EEPROM offsets so centre = 0 radians</li> <li><strong>Custom commands</strong> - home, demo circle, move to pose, arm/disarm</li> </ul> <p>And the best part: <strong>you don't need the hardware</strong>. Set the <code>SIMULATE</code> environment variable to anything non-empty and the whole thing runs in simulation mode with the 3D visualiser. Want to poke around a robotics framework without spending money on servos? Now you can.</p> <p>There'll be a separate blog post coming soon with a proper getting-started guide and links to kit sources. For now, go have a look.</p> <h2 id="two-bugs-walk-into-a-serial-port">Two Bugs Walk Into a Serial Port</h2> <p>Getting the SO-101 working on real hardware surfaced two bugs that were hiding in the Feetech stack. Both are the sort of thing that only shows up when you're actually talking to six servos at once on a real wire.</p> <h3 id="the-buffer-threading-bug-feetech-v0-2-2">The Buffer Threading Bug (feetech v0.2.2)</h3> <p>At 1Mbaud, all six servo responses to a <code>sync_read</code> arrive in about 1.2 milliseconds. That's faster than individual UART reads can consume them, so multiple responses pile up in the receive buffer. The old code parsed one response and threw away the remaining bytes. The next read would then fail or return garbage.</p> <p>The fix threads the remaining buffer from each parsed response into the next read call. Not glamorous, but essential for reliable multi-servo communication at high baud rates.</p> <h3 id="the-stale-goal-impulse-bb-servo-feetech-v0-2-1">The Stale Goal Impulse (bb_servo_feetech v0.2.1)</h3> <p>Here's a fun one. Feetech servos auto-enable torque the moment you write to <code>goal_position</code> - even if you haven't explicitly enabled torque yet. So if a servo remembers a goal position from a previous session and you write a new goal during initialisation, it lurches to whatever position it was told last time. When "last time" was the other side of the workspace, that lurch is... dramatic.</p> <p>The fix uses <code>reg_write</code> to buffer the goal position for each servo (which doesn't trigger the auto-torque behaviour), then fires a single <code>action</code> command to apply all goals simultaneously. Torque stays off until every servo has a sane goal. No more surprise gymnastics on startup.</p> <p>Both bugs were hardware-tested on the SO-101 with all six servos. The arm now powers up calmly, which is how robot arms should behave.</p> <h2 id="other-releases">Other Releases</h2> <table><thead><tr><th>Package</th><th>Version</th><th>What changed</th></tr></thead><tbody> <tr><td>bb</td><td><a rel="external" href="https://hex.pm/packages/bb/0.15.1">v0.15.1</a></td><td>Docs fix: correct <code>BB.Message.Sensor.IMU</code> references to <code>Imu</code></td></tr> <tr><td>feetech</td><td><a rel="external" href="https://hex.pm/packages/feetech/0.2.2">v0.2.2</a></td><td>Buffer threading fix for sync_read at high baud rates</td></tr> <tr><td>bb_servo_feetech</td><td><a rel="external" href="https://hex.pm/packages/bb_servo_feetech/0.2.1">v0.2.1</a></td><td>Stale-goal torque impulse prevention</td></tr> <tr><td>bb_servo_pigpio</td><td><a rel="external" href="https://hex.pm/packages/bb_servo_pigpio/0.5.0">v0.5.0</a></td><td>Migrate to structured error system</td></tr> </tbody></table> <p>That's every servo driver now on structured errors. Consistency: achieved.</p> <h2 id="dependency-gardening">Dependency Gardening</h2> <p>Dependabot had a busy week. Across the ecosystem: credo to 1.7.16 (Elixir 1.20.0-rc.1 compatibility, new <code>UnusedMapOperation</code> and <code>UnusedOperation</code> checks), ex_doc to 0.40.1 (now generates <code>llms.txt</code> - how very zeitgeist), igniter to 0.7.2, mimic to 2.3.0, phoenix_live_view to 1.1.22, and bandit to 1.10.2. Nothing breaking, just keeping the garden tidy.</p> <h2 id="what-s-next">What's Next</h2> <p>The SO-101 getting-started blog post is the immediate priority. I want to have clear kit lists, assembly notes, and a walkthrough from unboxing to "arm moves when you tell it to."</p> <p>After that, there's the demo video that was supposed to ship yesterday. Soon. For real this time.</p> <h2 id="links">Links</h2> <ul> <li><a rel="external" href="https://github.com/beam-bots/bb_example_so101">bb_example_so101 on GitHub</a></li> <li><a rel="external" href="https://hex.pm/packages/bb">bb on Hex</a> (v0.15.1)</li> <li><a rel="external" href="https://hex.pm/packages/feetech">feetech on Hex</a> (v0.2.2)</li> <li><a rel="external" href="https://hex.pm/packages/bb_servo_feetech">bb_servo_feetech on Hex</a> (v0.2.1)</li> <li><a rel="external" href="https://github.com/beam-bots/proposals">Proposals repo on GitHub</a></li> <li><a rel="external" href="https://discord.gg/QSag7Vuc4N">Discord</a></li> </ul> Week Eight: New Digs 2026-02-02T00:00:00+00:00 2026-02-02T00:00:00+00:00 Unknown https://beambots.dev/blog/progress-2026-02-02/ <p>A quieter week on the code front, but we have news.</p> <h2 id="we-ve-moved">We've Moved</h2> <p>The Nerves community has launched their own Discord server, separate from the Elixir Discord. It's a good move - the embedded Elixir world has grown enough to warrant its own space, and the Nerves folks have always been wonderfully welcoming.</p> <p>Beam Bots is moving with them. If you've been following along on the Elixir Discord, you'll find us at the new address:</p> <p><strong><a rel="external" href="https://discord.gg/QSag7Vuc4N">discord.gg/QSag7Vuc4N</a></strong></p> <p>Same channel name (<code>#beam-bots</code>), same people, new server. Come say hello.</p> <h2 id="why-follow-nerves">Why Follow Nerves?</h2> <p>If you're wondering why a robotics project would follow an embedded systems community: Beam Bots doesn't <em>have</em> to run on Nerves, but it likes to. The framework works anywhere Elixir runs, but it's designed with embedded hardware in mind - Raspberry Pis, BeagleBones, and whatever boards the future brings. Nerves makes deploying to those targets painless.</p> <p>The Nerves team has been incredibly supportive since the project started. Frank Hunleth donated the WidowX-200 that kicked off real hardware testing. Gus Workman from Protolux is collaborating on the balance bot. The overlap between "people interested in Elixir on embedded hardware" and "people interested in Elixir controlling robots" is basically a circle.</p> <p>So when the Nerves community moves, we move with them.</p> <h2 id="links">Links</h2> <ul> <li><a rel="external" href="https://hex.pm/packages/bb">bb on Hex</a> (v0.15.0)</li> <li><a rel="external" href="https://github.com/beam-bots/proposals">Proposals repo on GitHub</a></li> <li><a rel="external" href="https://discord.gg/QSag7Vuc4N">Discord</a></li> </ul> Week Seven: Jido Enter Stage Left 2026-01-26T00:00:00+00:00 2026-01-26T00:00:00+00:00 Unknown https://beambots.dev/blog/progress-2026-01-26/ <p>Some weeks you ship two feature releases and reorganise all the documentation. Other weeks you... don't. This is one of those weeks. But "no releases" doesn't mean "nothing happening."</p> <h2 id="proposal-bb-jido">Proposal: bb_jido</h2> <p>The most interesting development this week is <a rel="external" href="https://github.com/beam-bots/proposals/pull/11">Proposal 0009: bb_jido</a> - integrating the <a rel="external" href="https://agentjido.xyz/">Jido</a> autonomous agent framework with Beam Bots.</p> <p>If you read the <a href="/blog/beyond-behaviour-trees/">Beyond Behaviour Trees</a> post, you know we're using sagas (via <code>bb_reactor</code>) for structured workflows. Sagas are excellent when you know the sequence of steps upfront: pick up block, move to destination, place block. But what about when you don't?</p> <p>"Pick up the red block" sounds simple until you realise the robot needs to:</p> <ul> <li>Figure out which block is red</li> <li>Decide how to approach it</li> <li>Handle the case where another robot got there first</li> <li>Adapt when the human moves it while you're reaching</li> </ul> <p>This is where autonomous agents shine. Jido sits <em>above</em> our saga layer:</p> <pre class="giallo z-code"><code data-lang="plain"><span class="giallo-l"><span>┌─────────────────────────────────────────────────┐</span></span> <span class="giallo-l"><span>│ Jido Agent │</span></span> <span class="giallo-l"><span>│ - Observes world state via BB sensors │</span></span> <span class="giallo-l"><span>│ - Selects strategy based on context │</span></span> <span class="giallo-l"><span>│ - Invokes workflows or commands │</span></span> <span class="giallo-l"><span>├─────────────────────────────────────────────────┤</span></span> <span class="giallo-l"><span>│ bb_reactor Workflows │</span></span> <span class="giallo-l"><span>│ - Structured sequences with compensation │</span></span> <span class="giallo-l"><span>├─────────────────────────────────────────────────┤</span></span> <span class="giallo-l"><span>│ BB Commands │</span></span> <span class="giallo-l"><span>│ - Direct robot control │</span></span> <span class="giallo-l"><span>└─────────────────────────────────────────────────┘</span></span></code></pre> <p>The agent decides what to do. The saga decides how to do it. The commands make it happen.</p> <p>Jido is event-driven (no polling loops), keeps agent logic pure (effects as directives, not side effects), and the core has zero LLM code - AI integration is optional via <code>jido_ai</code>. It fits our philosophy nicely.</p> <p>The proposal is open for discussion. If you have thoughts on how goal-directed robot behaviour should work, now's the time to share them.</p> <h2 id="proposals-still-open">Proposals Still Open</h2> <p>Two proposals from last week are still under discussion:</p> <ul> <li><strong><a rel="external" href="https://github.com/beam-bots/proposals/pull/7">Proposal 0007: Usage Rules</a></strong> - LLM-friendly documentation standards</li> <li><strong><a rel="external" href="https://github.com/beam-bots/proposals/pull/9">Proposal 0008: bb_mavlink</a></strong> - MAVLink protocol for drones</li> </ul> <h2 id="what-s-next">What's Next</h2> <p>The SO-101 getting-started experience is still the priority. Once that's smooth, I can create videos that don't require expensive hardware.</p> <p>The balance bot collaboration with Gus is progressing, but it's waiting on the Nerves Starter Kit design.</p> <p>And bb_jido? That depends on how the proposal discussion goes.</p> <h2 id="links">Links</h2> <ul> <li><a rel="external" href="https://hex.pm/packages/bb">bb on Hex</a> (v0.15.0)</li> <li><a rel="external" href="https://hex.pm/packages/jido">Jido on Hex</a></li> <li><a rel="external" href="https://github.com/beam-bots/proposals">Proposals repo on GitHub</a></li> <li><a rel="external" href="https://discord.gg/QSag7Vuc4N">Discord</a></li> </ul> Week Six: Balance Bots and Better State Machines 2026-01-19T00:00:00+00:00 2026-01-19T00:00:00+00:00 Unknown https://beambots.dev/blog/progress-2026-01-19/ <p>Holiday's over. Back to the day job. But somehow I still managed to ship two feature releases, a bug fix, and sort out the documentation. Not bad for a short week.</p> <p>Also, happy Wellington Anniversary Day to me. Nothing says "public holiday" like writing release notes.</p> <h2 id="nerves-starter-kit-balance-bot">Nerves Starter Kit Balance Bot</h2> <p>The most exciting news this week isn't a package release - it's a collaboration.</p> <p>I've started working with <a rel="external" href="https://github.com/gworkman">Gus Workman</a> from the Nerves Core Team. He's the founder of <a rel="external" href="https://protolux.io/">Protolux</a> and is finalising the design for the <strong>Nerves Starter Kit</strong> - think of it as a spiritual successor to those name tag boards from last year's Goatmire/NervesConfEU, but with a larger e-ink display, a much smaller board, and more IO broken out for tinkering.</p> <p>We're collaborating on a simple add-on: two gear motors and an 18650 battery holder to convert the Starter Kit into a balance bot. The idea is to create something approachable for workshops and getting-started videos. A balance bot is just complex enough to be interesting (sensor fusion! PID control! falling over!) without being overwhelming.</p> <p>More details as the hardware design solidifies. But if you've ever wanted to see Elixir keeping a robot upright, this should be fun.</p> <h2 id="command-categories-and-concurrency">Command Categories and Concurrency</h2> <p>BB v0.15.0 introduces command categories with configurable concurrency limits.</p> <p>The problem: robots often have independent subsystems. Your gripper shouldn't have to wait for the arm to finish moving. But the old system ran one command at a time, full stop.</p> <p>The solution: define categories inside your <code>commands</code> block, each with its own concurrency limit.</p> <pre class="giallo z-code"><code data-lang="elixir"><span class="giallo-l"><span>commands </span><span class="z-keyword">do</span></span> <span class="giallo-l"><span> category </span><span class="z-constant">:</span><span class="z-constant">motion</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span> doc </span><span class="z-punctuation z-definition z-string">&quot;</span><span class="z-string">Physical movement commands</span><span class="z-punctuation z-definition z-string">&quot;</span></span> <span class="giallo-l"><span> concurrency_limit </span><span class="z-constant">1</span><span class="z-punctuation z-definition z-comment"> #</span><span class="z-comment"> Only one motion at a time</span></span> <span class="giallo-l"><span class="z-keyword"> end</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> category </span><span class="z-constant">:</span><span class="z-constant">sensing</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span> doc </span><span class="z-punctuation z-definition z-string">&quot;</span><span class="z-string">Sensor and data collection</span><span class="z-punctuation z-definition z-string">&quot;</span></span> <span class="giallo-l"><span> concurrency_limit </span><span class="z-constant">2</span><span class="z-punctuation z-definition z-comment"> #</span><span class="z-comment"> Can run two sensing ops concurrently</span></span> <span class="giallo-l"><span class="z-keyword"> end</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> command </span><span class="z-constant">:</span><span class="z-constant">move_to</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span> handler </span><span class="z-entity z-name">MoveCommand</span></span> <span class="giallo-l"><span> category </span><span class="z-constant">:</span><span class="z-constant">motion</span></span> <span class="giallo-l"><span> cancel </span><span>[</span><span class="z-constant">:</span><span class="z-constant">motion</span><span>]</span><span class="z-punctuation z-definition z-comment"> #</span><span class="z-comment"> New motion cancels previous motion</span></span> <span class="giallo-l"><span class="z-keyword"> end</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> command </span><span class="z-constant">:</span><span class="z-constant">read_sensor</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span> handler </span><span class="z-entity z-name">ReadSensorCommand</span></span> <span class="giallo-l"><span> category </span><span class="z-constant">:</span><span class="z-constant">sensing</span></span> <span class="giallo-l"><span class="z-keyword"> end</span></span> <span class="giallo-l"><span class="z-keyword">end</span></span></code></pre> <p>The rules are straightforward:</p> <ul> <li><strong>Different categories = parallel</strong>: A <code>:motion</code> command and a <code>:sensing</code> command run simultaneously</li> <li><strong>Same category = limited by concurrency_limit</strong>: If you try to run a second <code>:motion</code> command (limit 1), it either cancels the first (if <code>cancel: [:motion]</code>) or fails with a "category full" error</li> <li><strong>No category = uses <code>:default</code></strong>: The implicit default category has limit 1, preserving backwards compatibility</li> </ul> <p>This is groundwork for mobile manipulators, multi-arm systems, data collection robots - anything where "one thing at a time" doesn't cut it.</p> <h2 id="mimic-sensor-for-linked-joints">Mimic Sensor for Linked Joints</h2> <p>BB v0.14.0 adds <code>BB.Sensor.Mimic</code> for mechanically-linked joints.</p> <p>Some robot designs have joints that are physically connected - when one moves, the other follows. The WidowX-200's gripper works this way: two prismatic fingers driven by a single servo. You could model them as separate actuators and hope they stay in sync, but that's asking for trouble.</p> <p><code>BB.Sensor.Mimic</code> subscribes to one joint's position and publishes matching <code>JointState</code> messages for the mirrored joint. The kinematic chain stays accurate, and you don't have to pretend the linkage doesn't exist.</p> <pre class="giallo z-code"><code data-lang="elixir"><span class="giallo-l"><span>sensor </span><span class="z-constant">:</span><span class="z-constant">right_finger</span><span>,</span><span> {</span><span class="z-entity z-name">BB</span><span>.</span><span class="z-entity z-name">Sensor</span><span>.</span><span class="z-entity z-name">Mimic</span><span>,</span></span> <span class="giallo-l"><span class="z-constant"> source</span><span class="z-constant">:</span><span class="z-constant"> :</span><span class="z-constant">left_finger</span><span>,</span></span> <span class="giallo-l"><span class="z-constant"> multiplier</span><span class="z-constant">:</span><span class="z-keyword"> -</span><span class="z-constant">1.0</span><span class="z-punctuation z-definition z-comment"> #</span><span class="z-comment"> Mirror direction</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>Simple, but necessary.</p> <h2 id="feetech-sign-magnitude-fix">Feetech Sign-Magnitude Fix</h2> <p>Remember when I said the Feetech driver was buggy? One bug down.</p> <p>feetech v0.2.1 fixes sign-magnitude encoding for signed registers. Turns out Feetech servos don't use two's complement for negative values - they use a sign bit in the high byte. So when you asked for a negative velocity, you got... not what you expected.</p> <p>The SO-101 now moves in the directions I tell it to. Progress.</p> <h2 id="documentation-gardening">Documentation Gardening</h2> <p>All three packages (bb, bb_kino, bb_liveview) got a documentation tidy-up this week.</p> <p>We've always used the <a rel="external" href="https://diataxis.fr/">Diataxis framework</a> - tutorials, how-to guides, reference, explanation - but when you only have a handful of pages it doesn't matter much where things go. As the framework grows, so does the documentation, and keeping it organised becomes like gardening: constant small efforts to stop the weeds taking over.</p> <p>This round added new tutorials, filled in some reference gaps, and shuffled things into their proper categories. Still plenty of gaps to fill, but at least the beds are tidy.</p> <h2 id="proposals-update">Proposals Update</h2> <p>The six proposals from last week are now merged into the <a rel="external" href="https://github.com/beam-bots/proposals">proposals repo</a> - bb_teleop, bb_policy, bb_dataset, bb_mcp, bb_tui, and bb_motion_planning are all accepted designs waiting for implementation.</p> <p>Two new proposals are open for discussion:</p> <p><strong><a rel="external" href="https://github.com/beam-bots/proposals/pull/7">Proposal 0007: Usage Rules</a></strong> <em>(new)</em> - A standard way to include LLM-friendly documentation in packages. If you've used <code>usage-rules.md</code> files before, this formalises the approach.</p> <p><strong><a rel="external" href="https://github.com/beam-bots/proposals/pull/9">Proposal 0008: bb_mavlink</a></strong> <em>(new)</em> - MAVLink protocol integration for drones and other autonomous vehicles. This one's ambitious, but MAVLink is the lingua franca of the drone world.</p> <h2 id="so-101-status">SO-101 Status</h2> <p>The arm works. The example code exists. But I'm not ready to encourage people to build one just yet.</p> <p>Feetech servos have some quirks. For instance, they have a tendency to enable torque the moment you write a new configuration. That's... not ideal when you're trying to set limits before powering up. I want to make sure the getting-started experience is smooth before pointing people at it.</p> <p>Soon.</p> <h2 id="all-the-releases">All The Releases</h2> <p>Since January 12th:</p> <table><thead><tr><th>Package</th><th>Version</th><th>What changed</th></tr></thead><tbody> <tr><td>bb</td><td><a rel="external" href="https://hex.pm/packages/bb/0.15.0">v0.15.0</a></td><td>Command categories with concurrency limits</td></tr> <tr><td>bb</td><td><a rel="external" href="https://hex.pm/packages/bb/0.14.0">v0.14.0</a></td><td><code>BB.Sensor.Mimic</code> for mechanically-linked joints</td></tr> <tr><td>bb</td><td><a rel="external" href="https://hex.pm/packages/bb/0.13.2">v0.13.2</a></td><td>Bridge name uniqueness enforcement in DSL verifier</td></tr> <tr><td>feetech</td><td><a rel="external" href="https://hex.pm/packages/feetech/0.2.1">v0.2.1</a></td><td>Sign-magnitude encoding fix for signed registers</td></tr> <tr><td>bb_kino</td><td><a rel="external" href="https://hex.pm/packages/bb_kino/0.3.3">v0.3.3</a></td><td>Documentation improvements</td></tr> <tr><td>bb_liveview</td><td><a rel="external" href="https://hex.pm/packages/bb_liveview/0.2.4">v0.2.4</a></td><td>Documentation improvements</td></tr> </tbody></table> <h2 id="what-s-next">What's Next</h2> <p>Getting the SO-101 experience polished remains the priority. Once that's solid, I can start making videos that don't require a $2000 robot arm.</p> <p>The balance bot collaboration with Gus is exciting, but it's hardware-dependent - we need the Starter Kit design locked down first.</p> <h2 id="links">Links</h2> <ul> <li><a rel="external" href="https://hex.pm/packages/bb">bb on Hex</a> (v0.15.0)</li> <li><a rel="external" href="https://hex.pm/packages/feetech">feetech on Hex</a> (v0.2.1)</li> <li><a rel="external" href="https://protolux.io/">Protolux</a></li> <li><a rel="external" href="https://github.com/beam-bots/proposals">Proposals repo on GitHub</a></li> <li><a rel="external" href="https://discord.gg/QSag7Vuc4N">Discord</a></li> </ul> Week Five: Tasks Get Orchestrated, Roadmaps Get Transparent 2026-01-12T00:00:00+00:00 2026-01-12T00:00:00+00:00 Unknown https://beambots.dev/blog/progress-2026-01-12/ <p>Four new repositories in one week. One of them warranted its own blog post. Not bad for the first full work week of the year.</p> <p>Turns out the secret to productivity is being on holiday with nothing to do but tinker with robots. Amazing what happens when nobody expects you to be anywhere.</p> <h2 id="bb-reactor-task-orchestration-done-properly">BB Reactor: Task Orchestration Done Properly</h2> <p>The headline release this week is <a rel="external" href="https://hex.pm/packages/bb_reactor">bb_reactor</a>, which brings <a rel="external" href="https://hexdocs.pm/reactor">Reactor</a> saga orchestration to Beam Bots. I wrote a whole <a href="/blog/beyond-behaviour-trees/">separate post</a> about why we went this direction instead of behaviour trees, but the short version:</p> <ul> <li><strong>Compensation and rollback</strong> - When a step fails, earlier steps can undo their work. Your robot doesn't get left in a weird state.</li> <li><strong>Dependency-driven concurrency</strong> - Independent steps run in parallel automatically. No explicit parallel nodes needed.</li> <li><strong>Event-driven, not poll-driven</strong> - Steps wait for actual messages, not tick cycles. Much better for embedded systems.</li> <li><strong>Safety integration</strong> - E-stop while a reactor is running? It halts immediately. No more steps execute.</li> </ul> <p><img src="/reactor-demo-sequence.webp" alt="A reactor sequence running in simulation" /></p> <pre class="giallo z-code"><code data-lang="elixir"><span class="giallo-l"><span class="z-keyword">defmodule</span><span class="z-entity z-name"> PickAndPlace</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span class="z-keyword"> use</span><span class="z-entity z-name"> Reactor</span><span>,</span><span class="z-constant"> extensions</span><span class="z-constant">:</span><span> [</span><span class="z-entity z-name">BB</span><span>.</span><span class="z-entity z-name">Reactor</span><span>]</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> wait_for_state </span><span class="z-constant">:</span><span class="z-constant">ready</span><span>,</span><span class="z-constant"> states</span><span class="z-constant">:</span><span> [</span><span class="z-constant">:</span><span class="z-constant">idle</span><span>]</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> command </span><span class="z-constant">:</span><span class="z-constant">move_to_pick</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span> command </span><span class="z-constant">:</span><span class="z-constant">move_to_pose</span></span> <span class="giallo-l"><span> argument </span><span class="z-constant">:</span><span class="z-constant">target</span><span>,</span><span class="z-entity z-name"> input</span><span>(</span><span class="z-constant">:</span><span class="z-constant">pick_pose</span><span>)</span></span> <span class="giallo-l"><span> wait_for </span><span class="z-constant">:</span><span class="z-constant">ready</span></span> <span class="giallo-l"><span> compensate </span><span class="z-constant">:</span><span class="z-constant">return_home</span><span class="z-punctuation z-definition z-comment"> #</span><span class="z-comment"> If downstream fails, go home</span></span> <span class="giallo-l"><span class="z-keyword"> end</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> command </span><span class="z-constant">:</span><span class="z-constant">grip</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span> command </span><span class="z-constant">:</span><span class="z-constant">close_gripper</span></span> <span class="giallo-l"><span> wait_for </span><span class="z-constant">:</span><span class="z-constant">move_to_pick</span></span> <span class="giallo-l"><span class="z-keyword"> end</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> wait_for_event </span><span class="z-constant">:</span><span class="z-constant">grip_confirmed</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span> path </span><span>[</span><span class="z-constant">:</span><span class="z-constant">sensor</span><span>,</span><span class="z-constant"> :</span><span class="z-constant">force_torque</span><span>]</span></span> <span class="giallo-l"><span> filter </span><span class="z-variable z-other">&amp;</span><span>(</span><span class="z-variable z-other">&amp;</span><span class="z-variable z-other">1</span><span>.</span><span>payload</span><span>.</span><span>force </span><span class="z-keyword">&gt;</span><span class="z-constant"> 5.0</span><span>)</span></span> <span class="giallo-l"><span> wait_for </span><span class="z-constant">:</span><span class="z-constant">grip</span></span> <span class="giallo-l"><span class="z-keyword"> end</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span class="z-punctuation z-definition z-comment"> #</span><span class="z-comment"> ... continue to place position</span></span> <span class="giallo-l"><span class="z-keyword">end</span></span></code></pre> <p>If the grip fails, the reactor automatically runs <code>:return_home</code> to compensate. Behaviour trees don't give you that for free.</p> <h2 id="the-proposals-repo">The Proposals Repo</h2> <p>What's next for Beam Bots? Now you can find out without reading my mind.</p> <p>The new <a rel="external" href="https://github.com/beam-bots/proposals">proposals repository</a> is a lightweight RFC system. Want to know what features are planned? Check the PRs. Want to propose something? Open a PR using the template.</p> <p>Currently open for discussion:</p> <ul> <li><strong>bb_teleop</strong> - Teleoperation framework (spacemouse, gamepad input)</li> <li><strong>bb_policy</strong> - Behaviour cloning and policy learning</li> <li><strong>bb_dataset</strong> - Recording and replaying robot trajectories</li> <li><strong>bb_mcp</strong> - Model Context Protocol integration (yes, AI-assisted robotics)</li> <li><strong>bb_tui</strong> - Terminal UI for robot control</li> <li><strong>bb_motion_planning</strong> - Path planning with obstacle avoidance</li> </ul> <p>No committees. No final comment periods. Just drafts, discussion, and implementation. If something in there looks interesting and you want to help build it, say hi in Discord.</p> <h2 id="so-101-hardware-arrives">SO-101 Hardware Arrives</h2> <p>The parts for the <a rel="external" href="https://github.com/TheRobotStudio/SO-ARM100">SO-101</a> showed up from AliExpress. It's a 6-DOF arm designed by TheRobotStudio that uses Feetech STS3215 servos - much more affordable than Dynamixels, though with some trade-offs.</p> <p>Supporting it required two new packages:</p> <ol> <li> <p><strong><a rel="external" href="https://hex.pm/packages/feetech">feetech</a></strong> - Low-level protocol driver for Feetech/WaveShare STS and SCS series servos. Handles the serial communication, register access, and synchronised multi-servo operations.</p> </li> <li> <p><strong><a rel="external" href="https://hex.pm/packages/bb_servo_feetech">bb_servo_feetech</a></strong> - BB framework integration with controller, actuator, and parameter bridge modules. Same architecture as the Robotis driver.</p> </li> </ol> <p><strong>Fair warning</strong>: The Feetech driver is still buggy. The protocol is quirky - load is reported as a percentage rather than mA, status bytes are interpreted differently, and there are some timing sensitivities I'm still working through. Don't use it for anything you can't afford to break just yet.</p> <h2 id="reactive-controllers">Reactive Controllers</h2> <p>BB v0.13.0 added off-the-shelf controllers that monitor PubSub messages and trigger actions:</p> <pre class="giallo z-code"><code data-lang="elixir"><span class="giallo-l"><span class="z-punctuation z-definition z-comment">#</span><span class="z-comment"> Disarm if servo current exceeds limit</span></span> <span class="giallo-l"><span>controller </span><span class="z-constant">:</span><span class="z-constant">over_current</span><span>,</span><span> {</span><span class="z-entity z-name">BB</span><span>.</span><span class="z-entity z-name">Controller</span><span>.</span><span class="z-entity z-name">Threshold</span><span>,</span></span> <span class="giallo-l"><span class="z-constant"> topic</span><span class="z-constant">:</span><span> [</span><span class="z-constant">:</span><span class="z-constant">sensor</span><span>,</span><span class="z-constant"> :</span><span class="z-constant">servo_status</span><span>]</span><span>,</span></span> <span class="giallo-l"><span class="z-constant"> field</span><span class="z-constant">:</span><span class="z-constant"> :</span><span class="z-constant">current</span><span>,</span></span> <span class="giallo-l"><span class="z-constant"> max</span><span class="z-constant">:</span><span class="z-constant"> 1.21</span><span>,</span></span> <span class="giallo-l"><span class="z-constant"> action</span><span class="z-constant">:</span><span class="z-entity z-name"> command</span><span>(</span><span class="z-constant">:</span><span class="z-constant">disarm</span><span>)</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span class="z-punctuation z-definition z-comment">#</span><span class="z-comment"> React to proximity sensor</span></span> <span class="giallo-l"><span>controller </span><span class="z-constant">:</span><span class="z-constant">collision</span><span>,</span><span> {</span><span class="z-entity z-name">BB</span><span>.</span><span class="z-entity z-name">Controller</span><span>.</span><span class="z-entity z-name">PatternMatch</span><span>,</span></span> <span class="giallo-l"><span class="z-constant"> topic</span><span class="z-constant">:</span><span> [</span><span class="z-constant">:</span><span class="z-constant">sensor</span><span>,</span><span class="z-constant"> :</span><span class="z-constant">proximity</span><span>]</span><span>,</span></span> <span class="giallo-l"><span class="z-constant"> match</span><span class="z-constant">:</span><span class="z-keyword"> fn</span><span> msg </span><span class="z-keyword">-&gt;</span><span> msg</span><span>.</span><span>payload</span><span>.</span><span>distance </span><span class="z-keyword">&lt;</span><span class="z-constant"> 0.05</span><span class="z-keyword"> end</span><span>,</span></span> <span class="giallo-l"><span class="z-constant"> action</span><span class="z-constant">:</span><span class="z-entity z-name"> command</span><span>(</span><span class="z-constant">:</span><span class="z-constant">disarm</span><span>)</span></span> <span class="giallo-l"><span>}</span></span></code></pre> <p>Useful for safety interlocks and event-driven behaviours without writing custom controller modules.</p> <h2 id="simulation-mode-improvements">Simulation Mode Improvements</h2> <p>BB v0.13.1 fixes a paper cut: joint sliders in simulation mode now actually update the 3D visualisation.</p> <p>The problem was that simulated actuators publish <code>BeginMotion</code> events but the visualisation needs <code>JointState</code> messages. The fix automatically creates <code>OpenLoopPositionEstimator</code> sensors for each actuator when running with <code>simulation: :kinematic</code>. One less thing to configure manually.</p> <h2 id="bb-servo-robotis-on-hex">bb_servo_robotis on Hex</h2> <p>Finally pushed the button. <a rel="external" href="https://hex.pm/packages/bb_servo_robotis">bb_servo_robotis v0.2.1</a> is on Hex. Nothing exciting, just structured errors in the parameter bridge. But it's no longer "release imminent" - it's actually released.</p> <h2 id="structured-errors-everywhere">Structured Errors Everywhere</h2> <p>Speaking of structured errors, <a rel="external" href="https://hex.pm/packages/bb_servo_pca9685">bb_servo_pca9685 v0.5.0</a> migrated to the same system. All servo drivers now return proper error structs instead of bare tuples. Debugging is easier when error messages tell you what actually went wrong.</p> <h2 id="all-the-releases">All The Releases</h2> <p>Since January 5th:</p> <table><thead><tr><th>Package</th><th>Version</th><th>What changed</th></tr></thead><tbody> <tr><td>bb</td><td><a rel="external" href="https://hex.pm/packages/bb/0.13.1">v0.13.1</a></td><td>Auto-add position estimators in simulation mode</td></tr> <tr><td>bb</td><td><a rel="external" href="https://hex.pm/packages/bb/0.13.0">v0.13.0</a></td><td>Reactive controllers (Threshold, PatternMatch)</td></tr> <tr><td>bb_reactor</td><td><a rel="external" href="https://hex.pm/packages/bb_reactor/0.2.0">v0.2.0</a></td><td>Reactor saga orchestration for task sequencing</td></tr> <tr><td>feetech</td><td><a rel="external" href="https://hex.pm/packages/feetech/0.2.0">v0.2.0</a></td><td>Feetech/STS/SCS protocol driver</td></tr> <tr><td>bb_servo_feetech</td><td><a rel="external" href="https://hex.pm/packages/bb_servo_feetech/0.2.0">v0.2.0</a></td><td>BB integration for Feetech servos (experimental)</td></tr> <tr><td>bb_kino</td><td><a rel="external" href="https://hex.pm/packages/bb_kino/0.3.2">v0.3.2</a></td><td>Filter omitted bridges in simulation mode</td></tr> <tr><td>bb_kino</td><td><a rel="external" href="https://hex.pm/packages/bb_kino/0.3.1">v0.3.1</a></td><td>Fix direct actuator commands</td></tr> <tr><td>bb_liveview</td><td><a rel="external" href="https://hex.pm/packages/bb_liveview/0.2.3">v0.2.3</a></td><td>Handle simulation mode and command completion</td></tr> <tr><td>bb_liveview</td><td><a rel="external" href="https://hex.pm/packages/bb_liveview/0.2.2">v0.2.2</a></td><td>Fix direct actuator commands</td></tr> <tr><td>bb_liveview</td><td><a rel="external" href="https://hex.pm/packages/bb_liveview/0.2.1">v0.2.1</a></td><td>Support BB 0.13+ command API</td></tr> <tr><td>bb_servo_robotis</td><td><a rel="external" href="https://hex.pm/packages/bb_servo_robotis/0.2.1">v0.2.1</a></td><td>Structured errors in parameter bridge</td></tr> <tr><td>bb_servo_pca9685</td><td><a rel="external" href="https://hex.pm/packages/bb_servo_pca9685/0.5.0">v0.5.0</a></td><td>Migrate to structured error system</td></tr> </tbody></table> <h2 id="what-s-next">What's Next</h2> <p>Getting the Feetech driver stable is the priority. Once that's working reliably, the SO-101 becomes a much more accessible reference platform than the WidowX-200.</p> <p>After that: probably one of the proposals. The teleoperation framework is tempting - it would be nice to wave a spacemouse around and have the arm follow.</p> <h2 id="links">Links</h2> <ul> <li><a rel="external" href="https://hex.pm/packages/bb">bb on Hex</a> (v0.13.1)</li> <li><a rel="external" href="https://hex.pm/packages/bb_reactor">bb_reactor on Hex</a> (v0.2.0)</li> <li><a rel="external" href="https://hex.pm/packages/feetech">feetech on Hex</a> (v0.2.0)</li> <li><a rel="external" href="https://hex.pm/packages/bb_servo_feetech">bb_servo_feetech on Hex</a> (v0.2.0)</li> <li><a href="/blog/beyond-behaviour-trees/">Beyond Behaviour Trees blog post</a></li> <li><a rel="external" href="https://github.com/beam-bots/proposals">Proposals repo on GitHub</a></li> <li><a rel="external" href="https://discord.gg/QSag7Vuc4N">Discord</a></li> </ul> Beyond Behaviour Trees: Task Orchestration the Elixir Way 2026-01-09T00:00:00+00:00 2026-01-09T00:00:00+00:00 Unknown https://beambots.dev/blog/beyond-behaviour-trees/ <p>Robot arms don't just move to positions. They pick things up, put them down, respond to sensor feedback, and recover when something goes wrong. Orchestrating these multi-step sequences is one of the harder problems in robotics software.</p> <p>The standard answer in ROS land is behaviour trees. We went a different direction.</p> <h2 id="what-s-a-behaviour-tree">What's a Behaviour Tree?</h2> <p><em>If you're already familiar with behaviour trees, <a href="https://beambots.dev/blog/beyond-behaviour-trees/#the-tick-problem">skip to the next section</a>.</em></p> <p>A behaviour tree is a way to structure complex robot behaviours as a tree of nodes. Each node is either a <strong>leaf</strong> (an action like "move arm" or a condition check like "is object detected?") or a <strong>composite</strong> that controls execution flow.</p> <p>The key composite types are:</p> <ul> <li><strong>Sequence</strong> - runs children left-to-right, stops on first failure</li> <li><strong>Selector</strong> (or Fallback) - runs children left-to-right, stops on first success</li> <li><strong>Parallel</strong> - runs all children simultaneously, with configurable success/failure policies</li> </ul> <p>A pick-and-place behaviour tree might look like this:</p> <pre class="giallo z-code"><code data-lang="plain"><span class="giallo-l"><span>Sequence</span></span> <span class="giallo-l"><span>├── Selector</span></span> <span class="giallo-l"><span>│ ├── Condition: &quot;Object in gripper?&quot;</span></span> <span class="giallo-l"><span>│ └── Sequence</span></span> <span class="giallo-l"><span>│ ├── Action: &quot;Move to pick position&quot;</span></span> <span class="giallo-l"><span>│ ├── Action: &quot;Close gripper&quot;</span></span> <span class="giallo-l"><span>│ └── Condition: &quot;Grip force detected?&quot;</span></span> <span class="giallo-l"><span>├── Action: &quot;Move to place position&quot;</span></span> <span class="giallo-l"><span>└── Action: &quot;Open gripper&quot;</span></span></code></pre> <p>The tree gets <strong>ticked</strong> - a clock pulse that propagates through the tree from root to leaves. Each node returns one of three states: <strong>Success</strong>, <strong>Failure</strong>, or <strong>Running</strong>. The composites use these return values to decide what to do next.</p> <p>This pattern emerged from game AI, where characters need to evaluate their situation continuously and switch behaviours fluidly. It works well for reactive systems that need to constantly reassess their environment.</p> <h2 id="the-tick-problem">The Tick Problem</h2> <p>Behaviour trees are designed for continuous polling.</p> <p>Every tick, the tree re-evaluates from the root. Is the condition still true? Is the action still running? Should we abort and try a different branch? This polling model makes sense when you're a game character checking if an enemy is nearby sixty times per second.</p> <p>For robot arms? Less so.</p> <p>When I tell my WidowX-200 to move to a position, I don't need to poll it every 16 milliseconds to check if it's still moving. The servo controller knows when motion completes. It can tell me. This is what message-passing systems are built for.</p> <p>Tick-based execution also means your tree traversal logic runs constantly, even when nothing is happening. That's fine on a desktop PC. On a Raspberry Pi running Nerves with other things to do? Every cycle counts.</p> <p><em>For Elixir developers:</em> Imagine implementing GenServer where instead of receiving messages, you had to poll a <code>check_state/0</code> function in a loop. That's essentially what tick-based behaviour trees ask you to do.</p> <p><em>For robotics developers:</em> Imagine if your motor controllers just sent you a message when motion completed instead of you having to poll them. That's what Elixir's message passing gives you for free.</p> <h2 id="enter-reactor">Enter Reactor</h2> <p>I wrote <a rel="external" href="https://hexdocs.pm/reactor">Reactor</a> as part of my work on the Ash Framework core team. It's a dynamic, concurrent, dependency-resolving saga orchestrator - a mouthful, but the core idea is simple: you define a series of steps with explicit dependencies, and Reactor figures out how to run them. Concurrently where possible, sequentially where necessary.</p> <p>The "saga" part is crucial. In distributed systems, a saga is a sequence of operations where each step can define recovery actions:</p> <ul> <li><strong><code>compensate</code></strong> - what to do if <em>this step</em> fails</li> <li><strong><code>undo</code></strong> - what to do if <em>a downstream step</em> fails (roll back successful work)</li> </ul> <p>This gives you transaction-like semantics across operations that can't actually be wrapped in a database transaction. Like, say, robot movements.</p> <p>Reactor already handles all the hard parts: dependency graph resolution, concurrent execution, error propagation, rollback coordination. It's been battle-tested in production Ash applications. Building robotics-specific steps on top of it was straightforward.</p> <h2 id="bb-reactor">BB.Reactor</h2> <p>The <code>bb_reactor</code> package extends Reactor with robotics-specific steps. Here's the demo sequence from our WidowX-200 example:</p> <pre class="giallo z-code"><code data-lang="elixir"><span class="giallo-l"><span class="z-keyword">defmodule</span><span class="z-entity"> BB</span><span>.</span><span class="z-entity">Example</span><span>.</span><span class="z-entity">WX200</span><span>.</span><span class="z-entity">Reactor</span><span>.</span><span class="z-entity z-name">DemoSequence</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span class="z-keyword"> use</span><span class="z-entity z-name"> Reactor</span><span>,</span><span class="z-constant"> extensions</span><span class="z-constant">:</span><span> [</span><span class="z-entity z-name">BB</span><span>.</span><span class="z-entity z-name">Reactor</span><span>]</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> wait_for_state </span><span class="z-constant">:</span><span class="z-constant">ready</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span> states </span><span>[</span><span class="z-constant">:</span><span class="z-constant">idle</span><span>]</span></span> <span class="giallo-l"><span> timeout </span><span class="z-constant">5000</span></span> <span class="giallo-l"><span class="z-keyword"> end</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> command </span><span class="z-constant">:</span><span class="z-constant">go_home</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span> command </span><span class="z-constant">:</span><span class="z-constant">home</span></span> <span class="giallo-l"><span> wait_for </span><span class="z-constant">:</span><span class="z-constant">ready</span></span> <span class="giallo-l"><span class="z-keyword"> end</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> command </span><span class="z-constant">:</span><span class="z-constant">trace_circle</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span> command </span><span class="z-constant">:</span><span class="z-constant">demo_circle</span></span> <span class="giallo-l"><span> wait_for </span><span class="z-constant">:</span><span class="z-constant">go_home</span></span> <span class="giallo-l"><span class="z-keyword"> end</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> command </span><span class="z-constant">:</span><span class="z-constant">return_home</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span> command </span><span class="z-constant">:</span><span class="z-constant">home</span></span> <span class="giallo-l"><span> wait_for </span><span class="z-constant">:</span><span class="z-constant">trace_circle</span></span> <span class="giallo-l"><span class="z-keyword"> end</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> return </span><span class="z-constant">:</span><span class="z-constant">return_home</span></span> <span class="giallo-l"><span class="z-keyword">end</span></span></code></pre> <p><img src="/reactor-demo-sequence.webp" alt="The demo sequence running in simulation" /></p> <p>No tick loop. No polling. The reactor waits for the robot to reach idle state, then executes commands in sequence, each waiting for the previous to complete via message passing.</p> <p>The DSL provides three step types:</p> <p><strong><code>command</code></strong> - executes a BB command (like moving or gripper control). Can specify a <code>compensate</code> handler for failure recovery.</p> <p><strong><code>wait_for_event</code></strong> - subscribes to the BB PubSub and waits for a matching message. Useful for sensor feedback like "wait until force sensor detects grip".</p> <p><strong><code>wait_for_state</code></strong> - waits for the robot state machine to reach a target state.</p> <h2 id="why-this-matters">Why This Matters</h2> <h3 id="safe-failure-recovery">Safe Failure Recovery</h3> <pre class="giallo z-code"><code data-lang="elixir"><span class="giallo-l"><span>command </span><span class="z-constant">:</span><span class="z-constant">move_to_place</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span> command </span><span class="z-constant">:</span><span class="z-constant">move_to_pose</span></span> <span class="giallo-l"><span> argument </span><span class="z-constant">:</span><span class="z-constant">target</span><span>,</span><span class="z-entity z-name"> input</span><span>(</span><span class="z-constant">:</span><span class="z-constant">place_pose</span><span>)</span></span> <span class="giallo-l"><span> wait_for </span><span class="z-constant">:</span><span class="z-constant">grip_confirmed</span></span> <span class="giallo-l"><span> compensate </span><span class="z-constant">:</span><span class="z-constant">return_home</span></span> <span class="giallo-l"><span class="z-keyword">end</span></span></code></pre> <p>If this movement fails, the reactor runs the <code>:return_home</code> command to recover. The arm doesn't get left in an unknown state.</p> <p>Behaviour trees have no built-in concept of compensation. You can implement it, but you're on your own.</p> <h3 id="dependency-driven-concurrency">Dependency-Driven Concurrency</h3> <p>Reactor analyses the dependency graph and runs independent steps in parallel automatically. If two steps don't depend on each other, they execute concurrently.</p> <pre class="giallo z-code"><code data-lang="elixir"><span class="giallo-l"><span>command </span><span class="z-constant">:</span><span class="z-constant">prepare_gripper</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span> command </span><span class="z-constant">:</span><span class="z-constant">open_gripper</span></span> <span class="giallo-l"><span> wait_for </span><span class="z-constant">:</span><span class="z-constant">ready</span></span> <span class="giallo-l"><span class="z-keyword">end</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>command </span><span class="z-constant">:</span><span class="z-constant">move_to_approach</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span> command </span><span class="z-constant">:</span><span class="z-constant">move_to_pose</span></span> <span class="giallo-l"><span> argument </span><span class="z-constant">:</span><span class="z-constant">target</span><span>,</span><span class="z-entity z-name"> input</span><span>(</span><span class="z-constant">:</span><span class="z-constant">approach_pose</span><span>)</span></span> <span class="giallo-l"><span> wait_for </span><span class="z-constant">:</span><span class="z-constant">ready</span></span> <span class="giallo-l"><span class="z-keyword">end</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span class="z-punctuation z-definition z-comment">#</span><span class="z-comment"> Both run in parallel - they only depend on :ready</span></span> <span class="giallo-l"><span>command </span><span class="z-constant">:</span><span class="z-constant">grip</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span> command </span><span class="z-constant">:</span><span class="z-constant">close_gripper</span></span> <span class="giallo-l"><span> wait_for </span><span>[</span><span class="z-constant">:</span><span class="z-constant">prepare_gripper</span><span>,</span><span class="z-constant"> :</span><span class="z-constant">move_to_approach</span><span>]</span><span class="z-punctuation z-definition z-comment"> #</span><span class="z-comment"> waits for both</span></span> <span class="giallo-l"><span class="z-keyword">end</span></span></code></pre> <p>With behaviour trees, you'd need explicit Parallel nodes and careful tree structure to achieve the same thing.</p> <h3 id="event-driven-not-poll-driven">Event-Driven, Not Poll-Driven</h3> <p>Steps block on actual events, not tick cycles:</p> <pre class="giallo z-code"><code data-lang="elixir"><span class="giallo-l"><span>wait_for_event </span><span class="z-constant">:</span><span class="z-constant">grip_confirmed</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span> path </span><span>[</span><span class="z-constant">:</span><span class="z-constant">sensor</span><span>,</span><span class="z-constant"> :</span><span class="z-constant">force_torque</span><span>]</span></span> <span class="giallo-l"><span> message_types </span><span>[</span><span class="z-entity z-name">ForceTorque</span><span>]</span></span> <span class="giallo-l"><span> filter </span><span class="z-variable z-other">&amp;</span><span>(</span><span class="z-variable z-other">&amp;</span><span class="z-variable z-other">1</span><span>.</span><span>payload</span><span>.</span><span>force </span><span class="z-keyword">&gt;</span><span class="z-constant"> 5.0</span><span>)</span></span> <span class="giallo-l"><span> timeout </span><span class="z-constant">2000</span></span> <span class="giallo-l"><span class="z-keyword">end</span></span></code></pre> <p>This subscribes to the force/torque sensor topic and waits for a message where force exceeds 5N. No polling. The step completes the moment the message arrives.</p> <h3 id="safety-integration">Safety Integration</h3> <p>BB.Reactor monitors for safety state changes. If the robot disarms mid-sequence (emergency stop, hardware fault, whatever), the reactor halts immediately with <code>{:halt, :safety_disarmed}</code>. No more steps execute.</p> <p>The command step watches the command process and detects disarm events automatically. You don't have to sprinkle safety checks throughout your tree.</p> <h2 id="the-trade-off">The Trade-Off</h2> <p>Behaviour trees excel at reactive, continuous behaviours - things that need to constantly reassess the environment and switch strategies. A robot navigating a dynamic environment, dodging obstacles in real-time, might genuinely benefit from tick-based evaluation.</p> <p>Reactor sagas excel at structured task sequences - things with clear steps, dependencies, and rollback requirements. Assembly operations, pick-and-place, inspection sequences.</p> <p>Behaviour trees aren't wrong. They're just not the only tool, and for the kind of deterministic task orchestration that robot arms typically need, sagas are a better fit.</p> <p>Plus, they're already built on the patterns Elixir gives you for free.</p> <h2 id="links">Links</h2> <ul> <li><a rel="external" href="https://hex.pm/packages/bb_reactor">bb_reactor on Hex</a> (v0.2.0)</li> <li><a rel="external" href="https://hex.pm/packages/reactor">Reactor on Hex</a></li> <li><a rel="external" href="https://hex.pm/packages/bb">bb on Hex</a></li> <li><a rel="external" href="https://github.com/beam-bots/bb_example_wx200">bb_example_wx200 on GitHub</a></li> <li><a rel="external" href="https://discord.gg/QSag7Vuc4N">Discord</a></li> </ul> <h3 id="behaviour-tree-implementations-for-comparison">Behaviour Tree Implementations (for comparison)</h3> <ul> <li><a rel="external" href="https://www.behaviortree.dev/">BehaviorTree.CPP</a> - C++ library used with ROS2</li> <li><a rel="external" href="https://py-trees.readthedocs.io/">py_trees</a> - Python library, also used with ROS</li> <li><a rel="external" href="http://wiki.ros.org/smach">SMACH</a> - ROS state machine library (predecessor pattern)</li> </ul> Week Four: Robots Learn to Not Hit Themselves 2026-01-05T00:00:00+00:00 2026-01-05T00:00:00+00:00 Unknown https://beambots.dev/blog/progress-2026-01-05/ <p>The headline this week: your robot can now check whether it's about to punch itself in the face. Progress!</p> <p>Development has been slower than I'd hoped - turns out New Year's celebrations and summer holidays here in New Zealand aren't particularly conducive to writing code. Who knew? Still, we've got some solid updates.</p> <h2 id="real-hardware-real-motion">Real Hardware, Real Motion</h2> <p>The WidowX 200 is alive and moving. Commands execute, the DLS IK solver works, and I've had the end effector tracing circles in space. It's one thing to watch simulated joint positions update in Livebook - it's quite another to hear servos whine and watch an actual robot arm follow your commands.</p> <p>More videos and documentation coming once I've cleaned up the example code.</p> <h2 id="collision-detection">Collision Detection</h2> <p>BB v0.11 adds a proper two-phase collision detection system:</p> <ul> <li><strong>Broad phase</strong>: Fast AABB (axis-aligned bounding box) overlap tests to quickly eliminate non-colliding pairs</li> <li><strong>Narrow phase</strong>: Precise intersection tests for spheres, capsules, cylinders, and boxes</li> <li><strong>Mesh support</strong>: Load STL files with automatic bounding computation</li> </ul> <pre class="giallo z-code"><code data-lang="elixir"><span class="giallo-l"><span class="z-punctuation z-definition z-comment">#</span><span class="z-comment"> Will the robot collide with itself at these joint positions?</span></span> <span class="giallo-l"><span class="z-entity z-name">BB</span><span>.</span><span class="z-entity z-name">Collision</span><span>.</span><span class="z-entity z-name">self_collision?</span><span>(</span><span>robot</span><span>,</span><span> positions</span><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span class="z-punctuation z-definition z-comment">#</span><span class="z-comment"> Check with a safety margin (catches near-misses)</span></span> <span class="giallo-l"><span class="z-entity z-name">BB</span><span>.</span><span class="z-entity z-name">Collision</span><span>.</span><span class="z-entity z-name">detect_self_collisions</span><span>(</span><span>robot</span><span>,</span><span> positions</span><span>,</span><span class="z-constant"> margin</span><span class="z-constant">:</span><span class="z-constant"> 0.01</span><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span class="z-punctuation z-definition z-comment">#</span><span class="z-comment"> Environment obstacles too</span></span> <span class="giallo-l"><span>obstacles </span><span class="z-keyword">=</span><span> [</span><span class="z-entity z-name">BB</span><span>.</span><span class="z-entity z-name">Collision</span><span>.</span><span class="z-entity z-name">obstacle</span><span>(</span><span class="z-constant">:</span><span class="z-constant">sphere</span><span>,</span><span> centre</span><span>,</span><span> radius</span><span>)</span><span>]</span></span> <span class="giallo-l"><span class="z-entity z-name">BB</span><span>.</span><span class="z-entity z-name">Collision</span><span>.</span><span class="z-entity z-name">collides_with?</span><span>(</span><span>robot</span><span>,</span><span> positions</span><span>,</span><span> obstacles</span><span>)</span></span></code></pre> <p>The system automatically excludes adjacent links from self-collision checks (your elbow is <em>supposed</em> to touch your shoulder). And yes, you can define collision volumes using a new <code>capsule</code> DSL entity.</p> <h2 id="ik-solvers-actually-no">IK Solvers: "Actually, No"</h2> <p>The DLS solver now integrates with collision detection:</p> <pre class="giallo z-code"><code data-lang="elixir"><span class="giallo-l"><span class="z-punctuation z-definition z-comment">#</span><span class="z-comment"> Reject solutions that would cause self-collision</span></span> <span class="giallo-l"><span class="z-entity z-name">DLS</span><span>.</span><span class="z-entity z-name">solve</span><span>(</span><span>robot</span><span>,</span><span> positions</span><span>,</span><span class="z-constant"> :</span><span class="z-constant">tip</span><span>,</span><span> target</span><span>,</span><span class="z-constant"> check_collisions</span><span class="z-constant">:</span><span class="z-constant"> true</span><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span class="z-punctuation z-definition z-comment">#</span><span class="z-comment"> With a safety margin</span></span> <span class="giallo-l"><span class="z-entity z-name">DLS</span><span>.</span><span class="z-entity z-name">solve</span><span>(</span><span>robot</span><span>,</span><span> positions</span><span>,</span><span class="z-constant"> :</span><span class="z-constant">tip</span><span>,</span><span> target</span><span>,</span></span> <span class="giallo-l"><span class="z-constant"> check_collisions</span><span class="z-constant">:</span><span class="z-constant"> true</span><span>,</span></span> <span class="giallo-l"><span class="z-constant"> collision_margin</span><span class="z-constant">:</span><span class="z-constant"> 0.01</span></span> <span class="giallo-l"><span>)</span></span></code></pre> <p>When a solution would cause a collision, you get a proper structured error:</p> <pre class="giallo z-code"><code data-lang="elixir"><span class="giallo-l"><span>{</span><span class="z-constant">:</span><span class="z-constant">error</span><span>,</span><span> %</span><span class="z-entity z-name">BB</span><span>.</span><span class="z-entity z-name">Error</span><span>.</span><span class="z-entity z-name">Kinematics</span><span>.</span><span class="z-entity z-name">SelfCollision</span><span>{</span></span> <span class="giallo-l"><span class="z-constant"> link_a</span><span class="z-constant">:</span><span class="z-constant"> :</span><span class="z-constant">forearm</span><span>,</span></span> <span class="giallo-l"><span class="z-constant"> link_b</span><span class="z-constant">:</span><span class="z-constant"> :</span><span class="z-constant">base</span><span>,</span></span> <span class="giallo-l"><span class="z-constant"> joint_positions</span><span class="z-constant">:</span><span> %</span><span>{</span><span class="z-constant">shoulder</span><span class="z-constant">:</span><span class="z-constant"> 1.2</span><span>,</span><span class="z-constant"> elbow</span><span class="z-constant">:</span><span class="z-keyword"> -</span><span class="z-constant">2.1</span><span>}</span></span> <span class="giallo-l"><span>}</span><span>}</span></span></code></pre> <p>This is the kind of thing you want to know <em>before</em> you command the motion, not after.</p> <h2 id="commands-get-interruptible">Commands Get Interruptible</h2> <p>BB v0.12 converts commands from Tasks to GenServers. Why does this matter? Because now commands can respond to events during execution.</p> <p>The big win: if you hit the E-stop while a <code>MoveTo</code> is in progress, the command actually stops. Previously it would run to completion regardless (the actuators would ignore commands while disarmed, but the command itself kept chugging along).</p> <p>Commands now implement a proper behaviour with callbacks:</p> <ul> <li><code>handle_command/3</code> - Do the work (signature changed significantly)</li> <li><code>result/1</code> - Extract result from state</li> <li><code>handle_safety_state_change/2</code> - React when safety state changes</li> <li><code>handle_options/2</code> - React when parameters change at runtime</li> </ul> <p><strong>Breaking change</strong>: If you've written custom commands, they need updating. The <code>handle_command</code> callback works differently now. Check the <a rel="external" href="https://hexdocs.pm/bb/05-commands.html">commands tutorial</a> for the new pattern.</p> <h2 id="parameters-widget-for-livebook">Parameters Widget for Livebook</h2> <p>BB Kino v0.3 adds a <code>BB.Kino.Parameters</code> widget. View and edit your robot's parameters directly in Livebook:</p> <ul> <li>Tab-based UI groups parameters logically</li> <li>Remote bridge parameters get their own tabs</li> <li>Input controls match parameter types (toggles for booleans, sliders for bounded numbers, etc.)</li> <li>Real-time updates via PubSub</li> <li>Validation feedback when setting invalid values</li> </ul> <p>The widget slots in at the bottom of the "Manage robot" Smart Cell, full-width.</p> <h2 id="the-robotis-situation">The Robotis Situation</h2> <p>Good news! The upstream <a rel="external" href="https://hex.pm/packages/robotis/0.2.0">robotis v0.2.0</a> landed on Hex on New Year's Eve. This includes our contributed XM430 support, table parameterisation, and raw read/write API.</p> <p>Bad news: I haven't had time to cut the <code>bb_servo_robotis</code> release yet. The driver is ready, tested, and waiting. Just needs someone (me) to push the button. It's on the list.</p> <h2 id="all-the-releases">All The Releases</h2> <p>Since December 29:</p> <table><thead><tr><th>Package</th><th>Version</th><th>What changed</th></tr></thead><tbody> <tr><td>bb</td><td><a rel="external" href="https://hex.pm/packages/bb/0.12.0">v0.12.0</a></td><td>Interruptible commands (GenServer-based)</td></tr> <tr><td>bb</td><td><a rel="external" href="https://hex.pm/packages/bb/0.11.0">v0.11.0</a></td><td>Collision detection system</td></tr> <tr><td>bb_ik_dls</td><td><a rel="external" href="https://hex.pm/packages/bb_ik_dls/0.3.1">v0.3.1</a></td><td>Update for bb 0.12 command API</td></tr> <tr><td>bb_ik_dls</td><td><a rel="external" href="https://hex.pm/packages/bb_ik_dls/0.3.0">v0.3.0</a></td><td>Self-collision checking in IK solver</td></tr> <tr><td>bb_ik_fabrik</td><td><a rel="external" href="https://hex.pm/packages/bb_ik_fabrik/0.3.1">v0.3.1</a></td><td>Update for bb 0.12 command API</td></tr> <tr><td>bb_kino</td><td><a rel="external" href="https://hex.pm/packages/bb_kino/0.3.0">v0.3.0</a></td><td>Parameters widget, bb 0.12 compatibility</td></tr> <tr><td>robotis</td><td><a rel="external" href="https://hex.pm/packages/robotis/0.2.0">v0.2.0</a></td><td>(upstream) XM430 support, raw API</td></tr> </tbody></table> <h2 id="what-s-next">What's Next</h2> <p>Parts for an <a rel="external" href="https://github.com/TheRobotStudio/SO-ARM100">SO-101</a> are on their way from AliExpress. Designed by TheRobotStudio, it's a capable, affordable arm that should make a good reference platform for folks who want to try Beam Bots without dropping serious cash on Dynamixel servos.</p> <p>In the meantime: more IK work, better joint limit enforcement, and getting <code>bb_servo_robotis</code> onto Hex.</p> <h2 id="links">Links</h2> <ul> <li><a rel="external" href="https://hex.pm/packages/bb">bb on Hex</a> (v0.12.0)</li> <li><a rel="external" href="https://hex.pm/packages/bb_ik_dls">bb_ik_dls on Hex</a> (v0.3.1)</li> <li><a rel="external" href="https://hex.pm/packages/bb_ik_fabrik">bb_ik_fabrik on Hex</a> (v0.3.1)</li> <li><a rel="external" href="https://hex.pm/packages/bb_kino">bb_kino on Hex</a> (v0.3.0)</li> <li><a rel="external" href="https://hex.pm/packages/bb_liveview">bb_liveview on Hex</a> (v0.2.0)</li> <li><a rel="external" href="https://hex.pm/packages/bb_servo_pca9685">bb_servo_pca9685 on Hex</a> (v0.4.0)</li> <li><a rel="external" href="https://hex.pm/packages/bb_servo_pigpio">bb_servo_pigpio on Hex</a> (v0.4.0)</li> <li><a rel="external" href="https://github.com/beam-bots/bb_servo_robotis">bb_servo_robotis on GitHub</a> (Hex release imminent)</li> <li><a rel="external" href="https://hex.pm/packages/robotis">robotis on Hex</a> (v0.2.0)</li> <li><a rel="external" href="https://discord.gg/QSag7Vuc4N">Discord</a></li> </ul> Week Three: No Hardware? No Problem 2025-12-29T00:00:00+00:00 2025-12-29T00:00:00+00:00 Unknown https://beambots.dev/blog/progress-2025-12-29/ <p>What do you do when your robot parts are still in transit from China? You build a simulation mode, obviously.</p> <h2 id="simulation-mode">Simulation Mode</h2> <p>The headline feature this week: robots can now run entirely in software. Pass <code>simulation: :kinematic</code> when starting your robot and the framework swaps in simulated actuators that honour joint velocity limits and publish motion events - but don't require any actual hardware.</p> <pre class="giallo z-code"><code data-lang="elixir"><span class="giallo-l"><span class="z-punctuation z-definition z-comment">#</span><span class="z-comment"> Run without hardware</span></span> <span class="giallo-l"><span class="z-entity z-name">MyRobot</span><span>.</span><span class="z-entity z-name">start_link</span><span>(</span><span class="z-constant">simulation</span><span class="z-constant">:</span><span class="z-constant"> :</span><span class="z-constant">kinematic</span><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span class="z-punctuation z-definition z-comment">#</span><span class="z-comment"> Check mode at runtime</span></span> <span class="giallo-l"><span class="z-entity z-name">BB</span><span>.</span><span class="z-entity z-name">Robot</span><span>.</span><span class="z-entity z-name">Runtime</span><span>.</span><span class="z-entity z-name">simulation_mode</span><span>(</span><span class="z-entity z-name">MyRobot</span><span>)</span><span class="z-punctuation z-definition z-comment"> #</span><span class="z-comment"> =&gt; :kinematic</span></span></code></pre> <p>The safety system still works (you must arm before commanding motion), position feedback flows through <code>OpenLoopPositionEstimator</code> as normal, and your LiveView dashboard lights up just like it would with real servos. The main difference? No angry servo noises at 2am.</p> <p>Controllers and bridges can opt into different simulation behaviours via DSL options:</p> <pre class="giallo z-code"><code data-lang="elixir"><span class="giallo-l"><span>controllers </span><span class="z-keyword">do</span></span> <span class="giallo-l"><span> controller </span><span class="z-constant">:</span><span class="z-constant">pca9685</span><span>,</span><span> {</span><span class="z-entity z-name">BB</span><span>.</span><span class="z-entity z-name">Servo</span><span>.</span><span class="z-entity z-name">PCA9685</span><span>.</span><span class="z-entity z-name">Controller</span><span>,</span><span class="z-constant"> bus</span><span class="z-constant">:</span><span class="z-punctuation z-definition z-string"> &quot;</span><span class="z-string">i2c-1</span><span class="z-punctuation z-definition z-string">&quot;</span><span>}</span><span>,</span></span> <span class="giallo-l"><span class="z-constant"> simulation</span><span class="z-constant">:</span><span class="z-constant"> :</span><span class="z-constant">omit</span><span class="z-punctuation z-definition z-comment"> #</span><span class="z-comment"> Don&#39;t start in simulation</span></span> <span class="giallo-l"><span class="z-keyword">end</span></span></code></pre> <p>This lets you test your robot's logic, supervision tree, and dashboard without touching hardware. Handy for development, CI pipelines, and waiting for slow boats from Shenzhen.</p> <h2 id="param-references-configuration-gets-pleasant">param() References: Configuration Gets Pleasant</h2> <p>The <code>param()</code> function now works everywhere in your robot DSL. Define parameters in one place, reference them anywhere - actuator options, controller configuration, even topology fields like joint limits.</p> <pre class="giallo z-code"><code data-lang="elixir"><span class="giallo-l"><span>parameters </span><span class="z-keyword">do</span></span> <span class="giallo-l"><span> namespace </span><span class="z-constant">:</span><span class="z-constant">config</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span> namespace </span><span class="z-constant">:</span><span class="z-constant">robotis</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span> param </span><span class="z-constant">:</span><span class="z-constant">device</span><span>,</span><span class="z-constant"> type</span><span class="z-constant">:</span><span class="z-constant"> :</span><span class="z-constant">string</span></span> <span class="giallo-l"><span> param </span><span class="z-constant">:</span><span class="z-constant">baud_rate</span><span>,</span><span class="z-constant"> type</span><span class="z-constant">:</span><span class="z-constant"> :</span><span class="z-constant">integer</span><span>,</span><span class="z-constant"> default</span><span class="z-constant">:</span><span class="z-constant"> 1_000_000</span></span> <span class="giallo-l"><span class="z-keyword"> end</span></span> <span class="giallo-l"><span class="z-keyword"> end</span></span> <span class="giallo-l"><span class="z-keyword">end</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>controllers </span><span class="z-keyword">do</span></span> <span class="giallo-l"><span> controller </span><span class="z-constant">:</span><span class="z-constant">dynamixel</span><span>,</span><span> {</span><span class="z-entity z-name">BB</span><span>.</span><span class="z-entity z-name">Servo</span><span>.</span><span class="z-entity z-name">Robotis</span><span>.</span><span class="z-entity z-name">Controller</span><span>,</span></span> <span class="giallo-l"><span class="z-constant"> port</span><span class="z-constant">:</span><span class="z-entity z-name"> param</span><span>(</span><span>[</span><span class="z-constant">:</span><span class="z-constant">config</span><span>,</span><span class="z-constant"> :</span><span class="z-constant">robotis</span><span>,</span><span class="z-constant"> :</span><span class="z-constant">device</span><span>]</span><span>)</span><span>,</span></span> <span class="giallo-l"><span class="z-constant"> baud_rate</span><span class="z-constant">:</span><span class="z-entity z-name"> param</span><span>(</span><span>[</span><span class="z-constant">:</span><span class="z-constant">config</span><span>,</span><span class="z-constant"> :</span><span class="z-constant">robotis</span><span>,</span><span class="z-constant"> :</span><span class="z-constant">baud_rate</span><span>]</span><span>)</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span class="z-keyword">end</span></span></code></pre> <p>Then pass values when starting:</p> <pre class="giallo z-code"><code data-lang="elixir"><span class="giallo-l"><span class="z-punctuation z-definition z-comment">#</span><span class="z-comment"> In your application.ex</span></span> <span class="giallo-l"><span>{</span><span class="z-entity z-name">MyRobot</span><span>,</span><span> [</span></span> <span class="giallo-l"><span class="z-constant"> parameters</span><span class="z-constant">:</span><span> [</span></span> <span class="giallo-l"><span class="z-constant"> config</span><span class="z-constant">:</span><span> [</span><span class="z-constant">robotis</span><span class="z-constant">:</span><span> [</span></span> <span class="giallo-l"><span class="z-constant"> device</span><span class="z-constant">:</span><span class="z-entity z-name"> Application</span><span>.</span><span class="z-entity z-name">get_env</span><span>(</span><span class="z-constant">:</span><span class="z-constant">my_app</span><span>,</span><span class="z-constant"> :</span><span class="z-constant">robotis_device</span><span>)</span><span>,</span></span> <span class="giallo-l"><span class="z-constant"> baud_rate</span><span class="z-constant">:</span><span class="z-entity z-name"> Application</span><span>.</span><span class="z-entity z-name">get_env</span><span>(</span><span class="z-constant">:</span><span class="z-constant">my_app</span><span>,</span><span class="z-constant"> :</span><span class="z-constant">robotis_baud</span><span>)</span></span> <span class="giallo-l"><span> ]</span><span>]</span></span> <span class="giallo-l"><span> ]</span></span> <span class="giallo-l"><span>]</span><span>}</span></span></code></pre> <p>This separates hardware configuration from robot definition. Your robot DSL describes the <em>structure</em> - what joints connect where, which actuators drive which joints. The <em>runtime details</em> (which USB port, what baud rate) come from your application config, environment variables, or wherever makes sense for your deployment.</p> <p>Even better: when parameters change at runtime, actuators get notified via a <code>handle_options/2</code> callback. Hot-reconfiguration without restarts.</p> <h2 id="math-gets-serious">Math Gets Serious</h2> <p>The core framework now includes proper mathematical types:</p> <ul> <li><strong><code>BB.Math.Vec3</code></strong> - 3D vectors backed by Nx tensors, with operations like <code>cross/2</code>, <code>normalise/1</code>, and unit vector constructors</li> <li><strong><code>BB.Math.Quaternion</code></strong> - unit quaternions for rotations, including <code>slerp/3</code>, <code>from_axis_angle/2</code>, and <code>rotate_vector/2</code></li> <li><strong><code>BB.Math.Transform</code></strong> - 4x4 homogeneous transformation matrices, now properly struct-wrapped</li> </ul> <p>These types form the foundation for all spatial reasoning in Beam Bots. If you're passing position and orientation around, use <code>Transform</code> - it's the canonical representation for poses in robotics (SE(3) if you're into that sort of thing).</p> <h2 id="orientation-aware-inverse-kinematics">Orientation-Aware Inverse Kinematics</h2> <p>The FABRIK solver grew up. It now understands that sometimes you don't just want the gripper <em>at</em> a position - you want it pointing in a particular direction.</p> <pre class="giallo z-code"><code data-lang="elixir"><span class="giallo-l"><span class="z-punctuation z-definition z-comment">#</span><span class="z-comment"> Point the tool along the Z axis</span></span> <span class="giallo-l"><span class="z-entity z-name">BB</span><span>.</span><span class="z-entity z-name">Motion</span><span>.</span><span class="z-entity z-name">move_to</span><span>(</span><span>robot</span><span>,</span><span class="z-constant"> :</span><span class="z-constant">gripper</span><span>,</span><span> {</span><span>position</span><span>,</span><span> {</span><span class="z-constant">:</span><span class="z-constant">axis</span><span>,</span><span class="z-entity z-name"> Vec3</span><span>.</span><span class="z-entity z-name">unit_z</span><span>(</span><span>)</span><span>}</span><span>}</span><span>,</span><span class="z-entity z-name"> BB</span><span>.</span><span class="z-entity z-name">IK</span><span>.</span><span class="z-entity z-name">FABRIK</span><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span class="z-punctuation z-definition z-comment">#</span><span class="z-comment"> Full orientation constraint</span></span> <span class="giallo-l"><span class="z-entity z-name">BB</span><span>.</span><span class="z-entity z-name">Motion</span><span>.</span><span class="z-entity z-name">move_to</span><span>(</span><span>robot</span><span>,</span><span class="z-constant"> :</span><span class="z-constant">gripper</span><span>,</span><span> {</span><span>position</span><span>,</span><span> {</span><span class="z-constant">:</span><span class="z-constant">quaternion</span><span>,</span><span> target_orientation</span><span>}</span><span>}</span><span>,</span><span class="z-entity z-name"> BB</span><span>.</span><span class="z-entity z-name">IK</span><span>.</span><span class="z-entity z-name">FABRIK</span><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span class="z-punctuation z-definition z-comment">#</span><span class="z-comment"> Or just pass a Transform</span></span> <span class="giallo-l"><span class="z-entity z-name">BB</span><span>.</span><span class="z-entity z-name">Motion</span><span>.</span><span class="z-entity z-name">move_to</span><span>(</span><span>robot</span><span>,</span><span class="z-constant"> :</span><span class="z-constant">gripper</span><span>,</span><span> target_transform</span><span>,</span><span class="z-entity z-name"> BB</span><span>.</span><span class="z-entity z-name">IK</span><span>.</span><span class="z-entity z-name">FABRIK</span><span>)</span></span></code></pre> <p>The solver tracks orientation at each joint internally, which also fixes some edge cases with spherical joints (co-located axes like you find in shoulder and wrist assemblies).</p> <p><strong>Still early stage.</strong> Joint limits aren't enforced and collision detection doesn't exist. But it's moving in the right direction.</p> <h2 id="bb-servo-robotis-progress-report">bb_servo_robotis: Progress Report</h2> <p>Good news: <a rel="external" href="https://github.com/pkinney/robotis/pull/4">our upstream PR</a> to <code>pkinney/robotis</code> merged on Christmas Eve. The changes add XM430 servo support, table parameterisation, and a raw read/write API.</p> <p>Bad news: we're still waiting on a Hex release from upstream before we can publish <code>bb_servo_robotis</code>. The package works fine if you're willing to pull from git, but we want clean Hex dependencies before we call it "released".</p> <p>In the meantime, the driver itself got updates:</p> <ul> <li>Wrapper GenServer pattern from bb 0.8</li> <li>Hardware error reporting to the safety system</li> <li>Configurable disarm behaviour</li> <li>Structured errors and diagnostic telemetry</li> </ul> <p>When upstream publishes, we'll be ready to ship the same day.</p> <h2 id="better-errors-better-telemetry">Better Errors, Better Telemetry</h2> <p>The framework adopted <a rel="external" href="https://hex.pm/packages/splode">Splode</a> for structured error handling. Errors now have proper types, stack traces, and can be pattern-matched sensibly. No more parsing error message strings.</p> <p>We also added diagnostic telemetry events. If you're running <code>:telemetry</code> (and you should be), you'll see performance metrics and diagnostic events flowing through. Useful for debugging and for building monitoring dashboards.</p> <h2 id="all-the-releases">All The Releases</h2> <p>Since December 22:</p> <table><thead><tr><th>Package</th><th>Version</th><th>What changed</th></tr></thead><tbody> <tr><td>bb</td><td><a rel="external" href="https://hex.pm/packages/bb/0.10.0">v0.10.0</a></td><td>Simulation mode, Vec3/Quaternion/Transform</td></tr> <tr><td>bb</td><td><a rel="external" href="https://hex.pm/packages/bb/0.9.0">v0.9.0</a></td><td>Splode errors, telemetry</td></tr> <tr><td>bb</td><td><a rel="external" href="https://hex.pm/packages/bb/0.8.0">v0.8.0</a></td><td>param() refs, wrapper GenServer pattern</td></tr> <tr><td>bb_ik_fabrik</td><td><a rel="external" href="https://hex.pm/packages/bb_ik_fabrik/0.3.0">v0.3.0</a></td><td>Orientation-aware solving</td></tr> <tr><td>bb_servo_pca9685</td><td><a rel="external" href="https://hex.pm/packages/bb_servo_pca9685/0.4.0">v0.4.0</a></td><td>Structured errors</td></tr> <tr><td>bb_servo_pigpio</td><td><a rel="external" href="https://hex.pm/packages/bb_servo_pigpio/0.4.0">v0.4.0</a></td><td>Structured errors</td></tr> </tbody></table> <h2 id="happy-new-year">Happy New Year</h2> <p>To those who celebrate: enjoy the fireworks, make some resolutions you won't keep, and try not to think about how much work is waiting for you in January.</p> <p>To those who don't: enjoy the quiet internet. Again.</p> <p>See you in 2025. Wait, we're already in 2025. See you next year. Which is also 2025. You know what I mean.</p> <h2 id="links">Links</h2> <ul> <li><a rel="external" href="https://hex.pm/packages/bb">bb on Hex</a> (v0.10.0)</li> <li><a rel="external" href="https://hex.pm/packages/bb_ik_fabrik">bb_ik_fabrik on Hex</a> (v0.3.0)</li> <li><a rel="external" href="https://hex.pm/packages/bb_liveview">bb_liveview on Hex</a> (v0.2.0)</li> <li><a rel="external" href="https://hex.pm/packages/bb_kino">bb_kino on Hex</a> (v0.2.0)</li> <li><a rel="external" href="https://hex.pm/packages/bb_servo_pca9685">bb_servo_pca9685 on Hex</a> (v0.4.0)</li> <li><a rel="external" href="https://hex.pm/packages/bb_servo_pigpio">bb_servo_pigpio on Hex</a> (v0.4.0)</li> <li><a rel="external" href="https://github.com/beam-bots/bb_servo_robotis">bb_servo_robotis on GitHub</a> (coming to Hex soon)</li> <li><a rel="external" href="https://discord.gg/QSag7Vuc4N">Discord</a></li> </ul> Week Two: Tell It Where, Not How 2025-12-22T00:00:00+00:00 2025-12-22T00:00:00+00:00 Unknown https://beambots.dev/blog/progress-2025-12-22/ <p>The WidowX-200 that Frank donated? It moves now. Under Elixir control. This is not a drill.</p> <h2 id="the-arm-lives">The Arm Lives</h2> <p>After a week of hacking on Dynamixel protocol support, I can now command the WidowX-200's servos directly from Beam Bots. The <code>bb_servo_robotis</code> package handles the full integration:</p> <ul> <li><strong>Actuator control</strong> - set joint positions and the arm actually goes there</li> <li><strong>Sensor telemetry</strong> - real-time temperature, voltage, current draw, and hardware error monitoring</li> <li><strong>Parameter bridges</strong> - read firmware versions, tune PID controllers, and access servo configuration through the BB parameter system</li> </ul> <p>All of this talks to the hardware via the <a rel="external" href="https://emanual.robotis.com/docs/en/dxl/protocol2/">Robotis Protocol 2.0</a> over a U2D2 USB adapter. The <code>robotis</code> package (our fork of <a rel="external" href="https://github.com/pkinney/robotis">pkinney/robotis</a>) handles the low-level protocol, and <code>bb_servo_robotis</code> wraps it into Beam Bots actuators, controllers, and sensors.</p> <p>The package isn't on Hex yet because it depends on our fork via git. Once <a rel="external" href="https://github.com/pkinney/robotis">our upstream PR</a> merges, we'll publish. Patience, young padawan.</p> <h2 id="inverse-kinematics-early-stage">Inverse Kinematics (Early Stage)</h2> <p>The new <code>bb_ik_fabrik</code> package implements the <a rel="external" href="https://www.sciencedirect.com/science/article/abs/pii/S1524070311000178">FABRIK algorithm</a> for inverse kinematics. Instead of saying "rotate joint 1 to 45 degrees, joint 2 to 30 degrees...", you can say "put the gripper at position (x, y, z)" and the solver figures out the joint angles.</p> <p>The core framework got a matching <code>BB.Motion</code> API that ties it all together:</p> <pre class="giallo z-code"><code data-lang="elixir"><span class="giallo-l"><span class="z-punctuation z-definition z-comment">#</span><span class="z-comment"> Move the gripper to a point in space</span></span> <span class="giallo-l"><span class="z-entity z-name">BB</span><span>.</span><span class="z-entity z-name">Motion</span><span>.</span><span class="z-entity z-name">move_to</span><span>(</span><span>robot</span><span>,</span><span class="z-constant"> :</span><span class="z-constant">gripper</span><span>,</span><span> {</span><span class="z-constant">0.3</span><span>,</span><span class="z-constant"> 0.1</span><span>,</span><span class="z-constant"> 0.2</span><span>}</span><span>,</span><span class="z-entity z-name"> BB</span><span>.</span><span class="z-entity z-name">IK</span><span>.</span><span class="z-entity z-name">FABRIK</span><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span class="z-punctuation z-definition z-comment">#</span><span class="z-comment"> Or solve without sending commands (for validation)</span></span> <span class="giallo-l"><span>{</span><span class="z-constant">:</span><span class="z-constant">ok</span><span>,</span><span> joint_positions</span><span>}</span><span class="z-keyword"> =</span><span class="z-entity z-name"> BB</span><span>.</span><span class="z-entity z-name">Motion</span><span>.</span><span class="z-entity z-name">solve_only</span><span>(</span><span>robot</span><span>,</span><span class="z-constant"> :</span><span class="z-constant">gripper</span><span>,</span><span> target</span><span>,</span><span class="z-entity z-name"> BB</span><span>.</span><span class="z-entity z-name">IK</span><span>.</span><span class="z-entity z-name">FABRIK</span><span>)</span></span></code></pre> <p><strong>Fair warning:</strong> The FABRIK solver is very early stage. It currently ignores joint constraints and collision detection, which means it can produce solutions that exceed your joint limits or send your arm through itself. Use it in simulation only, or on real hardware with extreme caution. This will improve, but right now it's a "trust but verify" situation - emphasis on verify.</p> <p>Multi-target solving runs in parallel for coordinated motion, which will be useful for gait generators once the solver matures.</p> <h2 id="liveview-dashboard">LiveView Dashboard</h2> <p>The <code>bb_liveview</code> package is now on Hex. One line of code in your Phoenix router and you get:</p> <ul> <li>Safety arm/disarm controls with visual state indicators</li> <li>Joint sliders with real-time position feedback</li> <li>Interactive 3D visualisation (Three.js) that updates as the robot moves</li> <li>PubSub event stream viewer with filtering</li> <li>Command execution forms generated from your robot's DSL</li> <li>Parameter management for runtime configuration</li> </ul> <p>This is the "introspection dashboard" from the roadmap, now shipping. Mount it in your app and control your robot from a browser.</p> <h2 id="core-framework-updates">Core Framework Updates</h2> <p>BB itself went through three releases this week:</p> <p><strong>v0.5.0</strong> - Motion integration with IK solvers and actuator commands. The <code>BB.Motion</code> module bridges solving and commanding in a single call.</p> <p><strong>v0.6.0</strong> - New <code>BB.Controller</code>, <code>BB.Sensor</code>, and <code>BB.Bridge</code> behaviours with <code>options_schema</code> callbacks. Each behaviour now includes <code>use GenServer</code>, so your implementations get GenServer for free. The schema validation catches misconfigured child specs at compile time.</p> <p><strong>v0.7.0</strong> - Hardware error reporting with auto-disarm. Controllers can call <code>BB.Safety.report_error/3</code> when hardware problems occur, and the safety system will automatically disarm (configurable via <code>auto_disarm_on_error</code>).</p> <p>The servo driver packages (<code>bb_servo_pca9685</code> v0.3.1, <code>bb_servo_pigpio</code> v0.3.1) updated to use the new behaviours.</p> <h2 id="coming-in-2026-a-budget-robot-arm">Coming in 2026: A Budget Robot Arm</h2> <p>The WidowX-200 is excellent hardware, but it costs about $3,500 USD. Not exactly impulse-buy territory for someone wanting to experiment with robotics.</p> <p>I've ordered parts to build an <a rel="external" href="https://github.com/TheRobotStudio/SO-ARM100">SO-101</a> - an open-source 6-DoF arm designed by The Robot Studio and Hugging Face for AI training and teleoperation. Total cost: around $120-230 USD depending on where you source parts. The servos are Waveshare ST3215s, and the frame is 3D printed.</p> <p>The goal is to make this a documented "getting started" path for Beam Bots - print the arm, wire it up, follow the tutorial, have a working robot controlled by Elixir. More accessible than "go buy a $3,500 arm" anyway.</p> <h2 id="happy-christmas">Happy Christmas</h2> <p>This Thursday is Christmas Day. To those who celebrate: enjoy the time with family, eat too much, and try not to argue about politics.</p> <p>To those who don't celebrate: enjoy the quiet internet.</p> <p>See you in 2026.</p> <h2 id="links">Links</h2> <ul> <li><a rel="external" href="https://hex.pm/packages/bb">bb on Hex</a> (v0.7.0)</li> <li><a rel="external" href="https://hex.pm/packages/bb_ik_fabrik">bb_ik_fabrik on Hex</a> (v0.2.0)</li> <li><a rel="external" href="https://hex.pm/packages/bb_liveview">bb_liveview on Hex</a> (v0.2.0)</li> <li><a rel="external" href="https://hex.pm/packages/bb_kino">bb_kino on Hex</a> (v0.2.0)</li> <li><a rel="external" href="https://hex.pm/packages/bb_servo_pca9685">bb_servo_pca9685 on Hex</a> (v0.3.1)</li> <li><a rel="external" href="https://hex.pm/packages/bb_servo_pigpio">bb_servo_pigpio on Hex</a> (v0.3.1)</li> <li><a rel="external" href="https://github.com/beam-bots/bb_servo_robotis">bb_servo_robotis on GitHub</a> (coming to Hex soon)</li> <li><a rel="external" href="https://github.com/beam-bots/bb_example_wx200">bb_example_wx200 on GitHub</a></li> <li><a rel="external" href="https://github.com/TheRobotStudio/SO-ARM100">SO-101 Robot Arm</a></li> <li><a rel="external" href="https://discord.gg/QSag7Vuc4N">Discord</a></li> </ul> Week One: Discord, Robot Arms, and Livebook Widgets 2025-12-15T00:00:00+00:00 2025-12-15T00:00:00+00:00 Unknown https://beambots.dev/blog/progress-2025-12-15/ <p>It's been six days since I announced Beam Bots, and things have moved faster than expected. Here's what happened.</p> <h2 id="we-have-a-discord-channel">We Have a Discord Channel</h2> <p>The Elixir Discord community has given Beam Bots its own <code>#beam-bots</code> channel. If you want to chat about resilient robotics on the BEAM, ask questions, or just lurk, <a rel="external" href="https://discord.gg/QSag7Vuc4N">come say hello</a>.</p> <h2 id="frank-hunleth-donated-a-robot-arm">Frank Hunleth Donated a Robot Arm</h2> <p><a rel="external" href="https://github.com/fhunleth">Frank Hunleth</a>, co-creator of Nerves, has donated a <a rel="external" href="https://docs.trossenrobotics.com/interbotix_xsarms_docs/specifications/wx200.html">Trossen Robotics WidowX-200</a> 5-DoF robot arm to the project.</p> <p><img src="https://docs.trossenrobotics.com/interbotix_xsarms_docs/_images/wx200.png" alt="WidowX-200 Robot Arm" /></p> <p>This is a proper piece of kit - Dynamixel servos, aluminium construction, and enough reach to knock things off my desk.</p> <p>This means I'll be able to test Beam Bots against real hardware that isn't just a servo taped to a breadboard. Expect tutorials and examples using this arm as the development progresses.</p> <p>Thanks Frank.</p> <h2 id="bb-kino-livebook-widgets-for-robot-control">BB Kino: Livebook Widgets for Robot Control</h2> <p>The biggest code drop this week is <a rel="external" href="https://hex.pm/packages/bb_kino">bb_kino</a> - a suite of interactive Livebook widgets for controlling and monitoring BB robots.</p> <p><img src="https://github.com/beam-bots/bb_kino/blob/main/assets/images/demo.gif?raw=true" alt="BB Kino Demo" /></p> <p>The package includes:</p> <ul> <li><strong>Safety Widget</strong> - arm/disarm controls with real-time state indicators</li> <li><strong>Joint Control Widget</strong> - sliders for setting joint positions, respecting limits</li> <li><strong>Event Stream Widget</strong> - live message monitoring with path filtering</li> <li><strong>Command Widget</strong> - dynamic forms for executing robot commands</li> <li><strong>3D Visualisation Widget</strong> - interactive Three.js rendering with real-time joint updates</li> <li><strong>Manage Robot Smart Cell</strong> - a unified dashboard combining everything</li> </ul> <p>This isn't the "LiveView-based introspection" from the roadmap - that's still planned as an embeddable dashboard for Phoenix apps. But for prototyping and development, being able to control your robot interactively in Livebook while watching state update in real-time is pretty useful.</p> <h2 id="safety-system">Safety System</h2> <p>BB v0.4.0 adds <code>BB.Safety</code>, a centralised arm/disarm controller that ensures your robot can always be stopped safely. Key features:</p> <ul> <li><strong><code>BB.Safety</code> behaviour</strong> - actuators controlling hardware implement a <code>disarm/1</code> callback</li> <li><strong>Global safety state</strong> - one place to arm or disarm all safety-registered components</li> <li><strong>Error state handling</strong> - if disarm callbacks fail, the robot enters an <code>:error</code> state and won't re-arm until you explicitly acknowledge with <code>force_disarm/1</code></li> <li><strong>Documentation</strong> covering BEAM's soft real-time limitations and why you still need a hardware kill switch</li> </ul> <p>The safety system is critical infrastructure. When things go wrong, you need the disarm to work even if the process that controlled the actuator has crashed. The <code>disarm/1</code> callback is designed to work without GenServer state for exactly this reason.</p> <h2 id="servo-driver-packages">Servo Driver Packages</h2> <p>Two new packages for driving RC servos:</p> <p><strong><a rel="external" href="https://hex.pm/packages/bb_servo_pigpio">bb_servo_pigpio</a></strong> - drives servos via GPIO PWM on Raspberry Pi using pigpio. Good for simple setups with a few servos directly connected to GPIO pins.</p> <p><strong><a rel="external" href="https://hex.pm/packages/bb_servo_pca9685">bb_servo_pca9685</a></strong> - drives servos via the PCA9685 16-channel PWM controller over I2C. Better for projects needing more channels or cleaner wiring.</p> <p>Both packages implement <code>BB.Safety</code> for safe disarm, use the standard actuator command interface, and work with <code>BB.Sensor.OpenLoopPositionEstimator</code> for position feedback on servos without encoders.</p> <h2 id="other-changes">Other Changes</h2> <ul> <li><strong>Standard actuator command interface</strong> - <code>BB.Actuator.set_position/4</code> and friends provide a consistent API across all actuator types</li> <li><strong>OpenLoopPositionEstimator</strong> - estimates joint position over time based on commanded velocity, for servos that don't report their actual position</li> <li><strong>BeginMotion/EndMotion messages</strong> - actuators now publish motion lifecycle events for coordination and logging</li> </ul> <h2 id="what-s-next">What's Next</h2> <p>The WidowX-200 uses Dynamixel servos with their own protocol, so I'll be writing a Dynamixel driver package. After that, I want to get inverse kinematics working so I can command the arm to move to a point in space rather than specifying individual joint angles.</p> <p>The rover project is on hold until the arm is working. One robot at a time.</p> <h2 id="links">Links</h2> <ul> <li><a rel="external" href="https://hex.pm/packages/bb">bb on Hex</a> (v0.4.0)</li> <li><a rel="external" href="https://hex.pm/packages/bb_kino">bb_kino on Hex</a> (v0.2.0)</li> <li><a rel="external" href="https://hex.pm/packages/bb_servo_pigpio">bb_servo_pigpio on Hex</a> (v0.3.0)</li> <li><a rel="external" href="https://hex.pm/packages/bb_servo_pca9685">bb_servo_pca9685 on Hex</a> (v0.3.0)</li> <li><a rel="external" href="https://discord.gg/QSag7Vuc4N">Discord</a></li> <li><a rel="external" href="https://github.com/beam-bots">GitHub: beam-bots</a></li> </ul> Announcing Beam Bots: Resilient Robotics on the BEAM 2025-12-09T00:00:00+00:00 2025-12-09T00:00:00+00:00 Unknown https://beambots.dev/blog/announcing-beam-bots/ <p>I've been working on something I'm excited to share: <a rel="external" href="https://github.com/beam-bots/bb">Beam Bots</a>, a framework for building resilient robotics applications in Elixir. It's early days - the code hasn't controlled any physical robots yet - but the core is there and I'm looking for feedback and contributors.</p> <p>If you're an Elixir developer who's ever been curious about robotics, read on.</p> <h2 id="why-not-just-use-ros">Why Not Just Use ROS?</h2> <p>As a hobby roboticist I've looked at <a rel="external" href="https://www.ros.org/">ROS</a> (Robot Operating System) for my projects. ROS is the industry standard, and for good reason - it solves real problems around inter-process communication, hardware abstraction, and tooling for robotics development.</p> <p>But ROS is designed for a different context than my hobby projects. You're expected to deploy a full Ubuntu system with a stack of packages on top of it. There are no OS images, no A/B firmware systems, no turnkey deployment story. ROS gives you the flexibility to design systems using many different technologies, but that flexibility comes with complexity.</p> <p>For my hobby projects, I wanted something lighter.</p> <h2 id="ros-looks-a-lot-like-otp">ROS Looks a Lot Like OTP</h2> <p>This isn't a new observation for me. I wrote about this back in 2020 hoping someone else would build the tooling. Five years later, nobody had, so here we are.</p> <p>When reading about ROS2's architecture, the pattern becomes obvious: so much of what ROS provides are just stock BEAM/OTP/Elixir patterns.</p> <ul> <li>ROS2's <strong>pubsub</strong> for inter-node communication? Elixir has <code>Phoenix.PubSub</code>, <code>Registry</code>, and built-in process messaging.</li> <li>ROS2's <strong>Services</strong> (request/response)? That's <code>GenServer.call/3</code>.</li> <li>ROS2's <strong>Actions</strong> (long-running tasks with feedback)? That's a supervised <code>Task</code> with progress messages.</li> <li>ROS2's <strong>Controllers</strong> managing robot behaviour? GenServers with state machines.</li> <li>ROS2's <strong>lifecycle nodes</strong>? GenServers with explicit state transitions.</li> <li>ROS2's <strong>parameter system</strong>? That's ETS.</li> </ul> <p>The BEAM already gives you fault tolerance, massive concurrency, soft real-time performance, and excellent behaviour in resource-constrained environments. Why reinvent all of that?</p> <h2 id="what-beam-bots-provides">What Beam Bots Provides</h2> <p>Beam Bots builds on this insight. It provides:</p> <p><strong>A Spark DSL for declarative robot definitions.</strong> Your robot's topology - links, joints, sensors, actuators - is defined in code that mirrors the physical structure. Here's a simple pan-tilt camera mount:</p> <pre class="giallo z-code"><code data-lang="elixir"><span class="giallo-l"><span class="z-keyword">defmodule</span><span class="z-entity z-name"> PanTiltCamera</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span class="z-keyword"> use</span><span class="z-entity z-name"> BB</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> topology </span><span class="z-keyword">do</span></span> <span class="giallo-l"><span> link </span><span class="z-constant">:</span><span class="z-constant">base_link</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span> visual </span><span class="z-keyword">do</span></span> <span class="giallo-l"><span> cylinder </span><span class="z-keyword">do</span></span> <span class="giallo-l"><span> radius </span><span class="z-punctuation z-definition z-string">~u(</span><span class="z-string">0.03 meter</span><span class="z-punctuation z-definition z-string">)</span></span> <span class="giallo-l"><span> height </span><span class="z-punctuation z-definition z-string">~u(</span><span class="z-string">0.02 meter</span><span class="z-punctuation z-definition z-string">)</span></span> <span class="giallo-l"><span class="z-keyword"> end</span></span> <span class="giallo-l"><span class="z-keyword"> end</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> joint </span><span class="z-constant">:</span><span class="z-constant">pan_joint</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span> type </span><span class="z-constant">:</span><span class="z-constant">revolute</span></span> <span class="giallo-l"><span> origin </span><span class="z-keyword">do</span></span> <span class="giallo-l"><span> z </span><span class="z-punctuation z-definition z-string">~u(</span><span class="z-string">0.015 meter</span><span class="z-punctuation z-definition z-string">)</span></span> <span class="giallo-l"><span class="z-keyword"> end</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> limit </span><span class="z-keyword">do</span></span> <span class="giallo-l"><span> lower </span><span class="z-punctuation z-definition z-string">~u(</span><span class="z-string">-170 degree</span><span class="z-punctuation z-definition z-string">)</span></span> <span class="giallo-l"><span> upper </span><span class="z-punctuation z-definition z-string">~u(</span><span class="z-string">170 degree</span><span class="z-punctuation z-definition z-string">)</span></span> <span class="giallo-l"><span class="z-keyword"> end</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> link </span><span class="z-constant">:</span><span class="z-constant">pan_link</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span> joint </span><span class="z-constant">:</span><span class="z-constant">tilt_joint</span><span class="z-keyword"> do</span></span> <span class="giallo-l"><span> type </span><span class="z-constant">:</span><span class="z-constant">revolute</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> limit </span><span class="z-keyword">do</span></span> <span class="giallo-l"><span> lower </span><span class="z-punctuation z-definition z-string">~u(</span><span class="z-string">-45 degree</span><span class="z-punctuation z-definition z-string">)</span></span> <span class="giallo-l"><span> upper </span><span class="z-punctuation z-definition z-string">~u(</span><span class="z-string">90 degree</span><span class="z-punctuation z-definition z-string">)</span></span> <span class="giallo-l"><span class="z-keyword"> end</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> link </span><span class="z-constant">:</span><span class="z-constant">camera_link</span></span> <span class="giallo-l"><span class="z-keyword"> end</span></span> <span class="giallo-l"><span class="z-keyword"> end</span></span> <span class="giallo-l"><span class="z-keyword"> end</span></span> <span class="giallo-l"><span class="z-keyword"> end</span></span> <span class="giallo-l"><span class="z-keyword"> end</span></span> <span class="giallo-l"><span class="z-keyword">end</span></span></code></pre> <p>The nesting in the code directly reflects the nesting in the physical robot. If you can read the code, you can understand the structure.</p> <p>Because it's a DSL built on Elixir macros, you can metaprogram your robots. Something as simple as <code>for arm &lt;- [:left, :right], do: joint(...)</code> to avoid repetition, or as sophisticated as writing a Spark DSL extension that transforms your robot definition at compile time.</p> <p><strong>Topology-based supervision.</strong> When you start a robot, Beam Bots builds a supervision tree that mirrors your robot's physical topology. Why does this matter? Fault isolation. If code controlling a gripper crashes repeatedly, the restart intensity propagates up through the supervision tree. A failing component further down the topology takes longer to impact the entire system, giving your robot more time to reach a safe state.</p> <p><strong>Physical units with the <code>~u</code> sigil.</strong> Express measurements naturally - <code>~u(90 degree)</code>, <code>~u(0.5 meter)</code>, <code>~u(10 newton_meter)</code> - with automatic conversion to SI base units internally.</p> <p><strong>Forward kinematics using <a rel="external" href="https://github.com/elixir-nx">Nx</a>.</strong> Compute the position of any link in your robot's kinematic chain using efficient tensor operations. On supported systems, these calculations can run on the GPU.</p> <p><strong>Hierarchical PubSub for sensor data.</strong> Subscribe to sensor topics using path-based addressing with optional message type filtering. Want all IMU data from the left arm? <code>subscribe(MyRobot, [:sensor, :left_arm], message_types: [BB.Message.Sensor.Imu])</code>.</p> <p><strong>URDF export.</strong> Need to visualise your robot in Gazebo or integrate with ROS tools? Export your robot definition to URDF with <code>mix bb.to_urdf MyRobot -o robot.urdf</code>.</p> <h2 id="where-it-runs">Where It Runs</h2> <p>Beam Bots runs anywhere Elixir runs:</p> <ul> <li><strong>Nerves devices</strong> - from Raspberry Pi to custom hardware, managed at scale via NervesHub with proper OTA updates and A/B firmware</li> <li><strong>Distributed Erlang clusters</strong> - multiple devices inside a single robot system can communicate transparently</li> <li><strong>A single PC running Livebook</strong> - prototype and experiment interactively</li> </ul> <p>The only requirement is Nx support for the kinematics calculations.</p> <h2 id="current-status">Current Status</h2> <p>I want to be upfront: Beam Bots hasn't controlled any physical robots yet. The framework compiles, the tests pass, the DSL works, but I haven't closed the loop with actual hardware.</p> <p>That's changing. I'm currently building a Mars-style rover with rocker-bogie suspension and YOLO-based object detection and avoidance. Updates will follow as I make progress.</p> <h2 id="what-s-next">What's Next</h2> <p>The roadmap includes:</p> <ul> <li><strong>Inverse kinematics</strong> using the FABRIK algorithm</li> <li><strong>Simulation support</strong> with Gazebo and/or Webots integration</li> <li><strong>Protocol bridges</strong> for MAVLINK, MSP, Crossfire, and other common robotics protocols</li> <li><strong>Extension packages</strong> providing out-of-the-box support for common sensors and actuators</li> <li><strong>LiveView-based introspection</strong> for monitoring and debugging running robots</li> <li><strong>Kino plugins</strong> for rich robotics interactions in Livebook</li> </ul> <h2 id="get-involved">Get Involved</h2> <p>Beam Bots is published on Hex as <a rel="external" href="https://hex.pm/packages/bb"><code>bb</code></a>. The easiest way to get started is with Igniter:</p> <pre class="giallo z-code"><code data-lang="shellscript"><span class="giallo-l"><span class="z-entity z-name">mix</span><span class="z-string"> igniter.install</span><span class="z-string"> bb</span></span></code></pre> <p>If this sounds interesting, I'd love your feedback:</p> <ul> <li><strong><a rel="external" href="https://github.com/beam-bots/bb">GitHub: beam-bots/bb</a></strong> - browse the code, open issues, send PRs</li> <li><strong><a rel="external" href="https://hexdocs.pm/bb/readme.html">Documentation</a></strong> - tutorials and API reference</li> <li>Try defining a robot and let me know where the DSL feels awkward</li> <li>If you have robotics hardware, help me close the loop with real-world testing</li> </ul> <p>This isn't production ready. But it's ready for exploration, and I think there's something here worth building together.</p>