CubDB-backed parameter persistence for the Beam Bots robotics framework.
Tune a gain with BB.Parameter.set/3, and it's still there after a reboot.
bb ships BB.Parameter.Store.Dets, which is fine when the machine shuts down
politely. Robots don't always. CubDB stores its data in an append-only, immutable
B-tree, so an unexpected shutdown can't corrupt the database or leave it needing
a repair pass — which matters when the off switch is "it fell over". Keys and
values are arbitrary terms, so parameter paths and BB.Unit values are stored
as-is, and writes are flushed to disk as they happen.
mix igniter.install bb_parameter_store_cubdbThat adds the extension to your robot and declares a parameter_store_cubdb
section, pointing at /root on a Nerves project and the build directory
otherwise.
Or add it to mix.exs by hand:
def deps do
[
{:bb_parameter_store_cubdb, "~> 0.1.0"}
]
endAdd the extension to your robot and say where the database lives:
defmodule MyRobot.Robot do
use BB, extensions: [BB.Parameter.Store.CubDB.Dsl]
parameter_store_cubdb do
data_dir "/root/my_robot_params"
end
parameters do
group :balance do
param :kp, type: :float, default: 180.0, min: 0.0, max: 500.0
param :kd, type: :float, default: 4.0, min: 0.0, max: 50.0
end
end
topology do
link :base_link
end
endThen tune away, and the values will be there next boot:
iex> BB.Parameter.set(MyRobot.Robot, [:balance, :kp], 200.0)
:okDeclaring the section is all that's needed — it sets the robot's
parameter_store setting for you. If you'd rather configure the store directly
and skip the extension, that works too:
settings do
parameter_store {BB.Parameter.Store.CubDB, data_dir: "/root/my_robot_params"}
end| Option | Default | Description |
|---|---|---|
data_dir |
required | Directory to store the database in, created if it doesn't exist |
auto_compact |
true |
Compact automatically. true, false, or {min_write_operations, min_dirt_factor} |
auto_file_sync |
true |
Flush the disk buffer on every write. Turning this off trades durability for write throughput |
On Nerves, use a path under /root — the application data partition. The
firmware's root filesystem is read-only, and bb only logs a warning when a
store fails to open, so a robot pointed at an unwritable path will boot with
persistence quietly disabled.
Which is a problem if the same robot module has to run on your laptop and on
the device, because /root isn't writable on the one and _build doesn't exist
on the other. Take the directory from the application environment instead:
parameter_store_cubdb do
data_dir Application.compile_env(:my_app, :params_dir)
endThen give each environment and target its own value. In a Nerves project,
config/config.exs ends with import_config "#{Mix.target()}.exs", so the
per-target files are where the paths go:
# config/host.exs
config :my_app, params_dir: Path.expand("_build/params")
# config/rpi0_2.exs
config :my_app, params_dir: "/root/params"For a plain (non-Nerves) project the same trick works per Mix.env(), with
config/dev.exs and config/test.exs setting their own directories — handy for
keeping a test run from inheriting the gains you tuned in dev.
Two things to know about this:
- Use
Application.compile_env/2, notget_env/2orruntime.exs. The DSL is compiled, so the value has to be available at compile time — a key set inconfig/runtime.exsis read far too late to end up in the section. The payoff is thatcompile_envalso tracks the key, so changing it triggers a recompile rather than leaving a stale path baked into the BEAM file. - An unset key is a compile error, not a
nilpath, because the section's schema requires a string. You'll hear about a missing target config while you're building the firmware rather than after it's on the robot.
bb applies persisted values without revalidating them against each parameter's
min/max. A value that was in bounds when it was written will still be in
bounds when it's read, so this only bites if something edits the database
directly.
Apache-2.0. See LICENSE.txt.
