Engine
The Bobulation Engine
The Bobulation Engine is the real-time decisioning layer that fuses inputs, resolves conflicts, prioritizes signals, and routes outputs to the right actuators — in under 8 milliseconds.
Hardware is commoditized. Software is not. Anyone can solder three radios to a dual-core SoC; what makes the Wireless Bobulator a product rather than a dev board is the Bobulation Engine — the real-time decisioning layer that fuses inputs, resolves conflicts, prioritizes signals, and routes outputs to exactly the right actuators, deterministically, in under 8 milliseconds.1 This page is the engine, opened up.
The rule engine
Behaviour is defined in a small declarative YAML DSL. Rules are
when/then blocks: a set of conditions over the normalized signal space,
and a set of actions to fire when they all hold. You push rules to a single
device or to the whole fleet over the REST API; they hot-reload without a
reboot.
rule "gym-entrance-soft-open":
priority: 40
when:
- sensor.door.reed == CLOSED
- time_of_day() in "06:00-09:00"
then:
- light.zone.2.fade_to(30%, over=5s)
- audio.zone.2.play("morning-warmup.m3u")
- hvac.zone.2.setpoint = 68
- log.info("auto-warmup-triggered", room="main-floor")
Conditions can reference any normalized channel, a handful of built-in
functions (time_of_day(), zone(), user(), rolling_avg()), and the
output of the optional ML model. Actions address outputs by a stable
domain.zone.id path, so a rule written against light.zone.2 keeps
working when you swap the underlying fixture.
Conflict resolution
In any real deployment, two rules eventually fire at the same instant and demand opposite things — one says dim the lights, another says full brightness, motion detected. A naive system picks whichever evaluated last. The Bobulator refuses to be non-deterministic about it. Conflicts are resolved by a fixed three-key sort:
- Explicit priority — the integer
priority:on each rule wins first. - Signal freshness — if priorities tie, the rule driven by the most recently updated input wins. A 200 ms-old motion event beats a 5-second-old schedule.
- Specificity — if still tied, the rule with more matched conditions (the more specific one) wins.
Every resolution is written to the audit log with both contenders and the key that decided it. There is no hidden tiebreak and no race.
On-device ML (optional)
For decisions that don’t reduce to clean rules — is this vibration pattern
the bearing starting to fail? — the engine can run a quantized TFLite
model directly on the Cortex-M7. Models are small by design (typically
under 256 KB), inference is sub-millisecond, and the output is just another
input the rule layer can reference: when: ml.anomaly_score > 0.8. Nothing
leaves the device; the model runs at the edge, offline, every cycle.
Context-aware routing
The same physical event should do different things depending on where, when, and who. The routing table keys on zone, time-of-day, and user identity, so one rule set adapts to context instead of multiplying into dozens of near-duplicates.
| Context key | Example | Effect |
|---|---|---|
| Zone | zone() == "court-3" | route to court-3 actuators only |
| Time-of-day | time_of_day() in "22:00-06:00" | quiet-hours output profile |
| User identity | user().role == "operator" | unlock manual override panel |
Audit log format
Every decision the engine makes is appended to a structured, tamper-evident log — newline-delimited JSON, one record per decision, with a rolling hash chain so a deleted line is detectable.
{"ts":"2026-06-29T07:14:02.318Z","rule":"gym-entrance-soft-open",
"trigger":"sensor.door.reed","conflict_with":null,
"actions":4,"latency_ms":2.1,"hash":"a91f…"}
The log streams to the dashboard live and syncs to the cloud when a hybrid topology is configured. For regulated environments it is the difference between we think the interlock fired and here is the exact 2.1 ms in which it fired and why.
Worked example: a multi-sensor conflict
A factory cell has three active rules. At 07:14:02.318, a worker crosses a
light curtain at the same moment a scheduled production ramp begins:
- R1
safety-interlock(priority: 100): light curtain broken → halt conveyor. - R2
production-ramp(priority: 50): schedule hit 07:14 → conveyor to full speed. - R3
energy-saver(priority: 10): no motion 5 min → idle conveyor.
R1 and R2 both fire on the same cycle and contradict each other. The engine
sorts by explicit priority: R1 wins, 100 > 50, conveyor halts in 2.1 ms.
R3 never even contends — its motion precondition is false. The audit log
records R1 as the winner, R2 as conflict_with, and the priority key as the
reason. Deterministic, logged, and fast enough to matter.
See how the engine sits in the broader five-layer architecture, or read the full specifications.