Packages

CubDB-backed parameter persistence for Beam Bots.

Current section

Files

Jump to

README.md

<!--
SPDX-FileCopyrightText: 2026 James Harton
SPDX-License-Identifier: Apache-2.0
-->
<img src="https://github.com/beam-bots/bb/blob/main/logos/beam_bots_logo.png?raw=true" alt="Beam Bots Logo" width="250" />
# BB.Parameter.Store.CubDB
[![CI](https://github.com/beam-bots/bb_parameter_store_cubdb/actions/workflows/ci.yml/badge.svg)](https://github.com/beam-bots/bb_parameter_store_cubdb/actions/workflows/ci.yml)
[![License: Apache 2.0](https://img.shields.io/badge/License-Apache--2.0-green.svg)](https://opensource.org/licenses/Apache-2.0)
[![Hex version badge](https://img.shields.io/hexpm/v/bb_parameter_store_cubdb.svg)](https://hex.pm/packages/bb_parameter_store_cubdb)
[![Hexdocs badge](https://img.shields.io/badge/docs-hexdocs-purple)](https://hexdocs.pm/bb_parameter_store_cubdb)
[![REUSE status](https://api.reuse.software/badge/github.com/beam-bots/bb_parameter_store_cubdb)](https://api.reuse.software/info/github.com/beam-bots/bb_parameter_store_cubdb)
[CubDB](https://hexdocs.pm/cubdb)-backed parameter persistence for the [Beam Bots](https://github.com/beam-bots/bb) robotics framework.
Tune a gain with `BB.Parameter.set/3`, and it's still there after a reboot.
## Why CubDB
`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.
## Installation
```bash
mix igniter.install bb_parameter_store_cubdb
```
That 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:
```elixir
def deps do
[
{:bb_parameter_store_cubdb, "~> 0.1.0"}
]
end
```
## Usage
Add the extension to your robot and say where the database lives:
```elixir
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
end
```
Then tune away, and the values will be there next boot:
```elixir
iex> BB.Parameter.set(MyRobot.Robot, [:balance, :kp], 200.0)
:ok
```
Declaring 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:
```elixir
settings do
parameter_store {BB.Parameter.Store.CubDB, data_dir: "/root/my_robot_params"}
end
```
## Options
| 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 |
## Where to put the data directory
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:
```elixir
parameter_store_cubdb do
data_dir Application.compile_env(:my_app, :params_dir)
end
```
Then 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:
```elixir
# 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`, not `get_env/2` or `runtime.exs`. The DSL is
compiled, so the value has to be available at compile time — a key set in
`config/runtime.exs` is read far too late to end up in the section. The payoff
is that `compile_env` also 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 `nil` path, 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.
## Bounds aren't rechecked on load
`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.
## Licence
Apache-2.0. See `LICENSE.txt`.