Skip to content

Architecture Overview

Defined Robotics is a capability-first platform for autonomous robots. It decouples robot description, task authoring, and runtime execution into three independent layers that compose at compile time and runtime.

Three Compilable Inputs

Every mission run requires three inputs:

Input Format Describes
RDF (Robot Description Framework) robot.rdf.yaml What the robot is: hardware modules, capabilities, physical geometry
Task tasks/patrol.task.yaml What the robot should do: a sequence of named verb steps
World World model (POIs in ~/.defined/state.yaml) Where it happens: named Points of Interest with coordinates

These inputs are independent — the same task can run on different robots (if capabilities match), and the same robot can run different tasks without recompilation.

Compile-Time vs Runtime Pipeline

graph TD
    subgraph COMPILE["Compile Time"]
        RDF["robot.rdf.yaml"] --> RDF_PKG["defined-rdf<br/><small>Parse YAML → Pydantic models<br/>Capability Registry</small>"]
        TASK["task.yaml"] --> COMP_PKG["defined-compiler<br/><small>1. Parse task<br/>2. Expand verbs<br/>3. Gate capabilities<br/>4. Render Jinja2</small>"]
        WORLD["world (POIs)"] --> RESOLVER["mission/resolver.py<br/><small>Expand $world.pois.X<br/>→ concrete (x, y)</small>"]
        RDF_PKG --> COMP_PKG
        RESOLVER --> COMP_PKG
        COMP_PKG --> XML["task.bt.xml"]
    end

    subgraph RUNTIME["Runtime"]
        CLI["defined-cli<br/><small>DefinedSession</small>"] -->|"WebSocket :9090"| BRIDGE["rosbridge"]
        CLI -->|"XML"| EXECUTOR["defined_runtime<br/><small>BT.CPP executor</small>"]
        EXECUTOR -->|"events"| CLI
        EXECUTOR --> NAV2["Nav2 + Gazebo/hardware"]
    end

    XML --> CLI

    style COMPILE fill:#1a2332,stroke:#2dd4bf,color:#e6edf3
    style RUNTIME fill:#1a2332,stroke:#3b82f6,color:#e6edf3

Key Compile-Time Rules

  • Capability gating is compile-time only (for v0.1.0). If a task requires differential_drive but the robot's RDF doesn't declare it, compilation fails immediately with a clear error.
  • $world.pois.dock references in task YAML are resolved to concrete (x, y) coordinates before BT XML is emitted. There are no runtime POI lookups.
  • The compiler output is a self-contained BT XML file — it has no dependency on the RDF or task YAML at runtime.

Package Responsibilities

defined-rdf

Owns the robot description domain. Pure Python, no ROS2.

  • Pydantic models for Robot, hardware modules, and capabilities
  • YAML parser (defined_rdf.parser.load)
  • CapabilityRegistry for querying declared capabilities
  • RobotConfig dataclass for extracting sim/hardware config (DR-022 Phase 1)

defined-compiler

Owns YAML-to-BT-XML translation. Pure Python, no ROS2.

  • Parses task YAML (steps: + {verb:, params:} schema)
  • Resolves verb definitions from a three-tier lookup: local verbs/ → built-in registry → future remote registry
  • Gates each verb against CapabilityRegistry
  • Renders Jinja2 templates to produce BehaviorTree.CPP XML

Depends on defined-rdf as a package dependency. Never imports ROS2 (DR-010).

defined-cli

Owns mission orchestration and the user interface. No ROS2 imports (DR-010).

defined-cli
├── CLI Layer (Click)
├── Mission Control
│   ├── DefinedSession     unified lifecycle API (connect, compile, deploy, monitor)
│   ├── Events             ConnectionStatus, MissionStatus, SessionEvent
│   └── POI Resolver       $world.pois.X expansion at compile time
├── State Layer
│   ├── StateStore         persists to ~/.defined/state.yaml
│   ├── Blackboard         dot-path KV store (3 layers)
│   └── Models             RobotState, WorldState
├── Compiler Wrapper
├── Protocols
│   ├── TargetBase         start/stop/status/resolve_xml_path
│   └── TransportBase      connect/disconnect/send_task/subscribe_status
├── Implementations
│   ├── SimTarget          manages Docker sim lifecycle
│   └── RosbridgeTransport roslibpy WebSocket client
└── TUI (Textual)
    ├── DefinedApp         shell + key bindings
    ├── CommandBar         command parser (no TUI dep)
    └── Panels: StatusBar, MissionPanel, WorldPanel, DiagnosticsPanel

defined_platform

The only component that contains ROS2 code.

  • defined_runtime: BT.CPP executor (C++) — receives task XML, drives Nav2 actions
  • defined_navigation: Nav2 lifecycle manager, costmap config, planner/controller params
  • defined_description: URDF/xacro, parametric sensor inclusion (DR-022)
  • defined_gazebo: Gazebo Ionic world files, sensor plugins
  • defined_bringup: Launch files, rosbridge node, TF bridge

Exposed to the rest of the platform only through rosbridge WebSocket and volume-mounted BT XML (DR-010).

Visualization

The platform does not bundle its own visualization tool. Instead, the simulation stack exposes a Foxglove WebSocket bridge at ws://localhost:8765, and any compatible client can connect.

Foxglove Studio is the recommended viewer. It runs on macOS, Linux, and Windows with no ROS2 installation required. Connect to the WebSocket and you get live access to:

  • 3D view — robot model, laser scan, costmap, navigation path
  • Map — SLAM-generated occupancy grid with robot pose
  • Topics — raw message inspection for any ROS2 topic exposed via the bridge

Other options work too: Lichtblick (open-source fork), RViz (if you have a ROS2 install), or any rosbridge-compatible WebSocket client.

Note

The Foxglove bridge runs inside the Docker container alongside rosbridge. Port 8765 is for Foxglove (visualization), port 9090 is for rosbridge (CLI control). Both are WebSocket — different protocols, different purposes.

CLI-to-ROS2 Connection

The host machine (macOS or Linux) never imports ROS2. All communication goes through rosbridge WebSocket at ws://localhost:9090 (or a remote robot IP).

graph LR
    subgraph HOST["Host (macOS / Linux)"]
        TRANSPORT["RosbridgeTransport<br/><small>roslibpy.Ros()</small>"]
    end

    subgraph DOCKER["Docker / Raspberry Pi"]
        ROSBRIDGE["rosbridge_server<br/><small>ws://localhost:9090</small>"]
        RUNTIME["defined_runtime"]
        NAV2["Nav2"]
        CAPS["capability_reporter<br/><small>(Phase 2)</small>"]
    end

    TRANSPORT -->|"/task_file_path"| RUNTIME
    RUNTIME -->|"/mission_status"| TRANSPORT
    NAV2 -->|"/odom"| TRANSPORT
    CAPS -->|"/robot_capabilities"| TRANSPORT
    TRANSPORT <-->|"WebSocket"| ROSBRIDGE

    style HOST fill:#1a2332,stroke:#2dd4bf,color:#e6edf3
    style DOCKER fill:#1a2332,stroke:#f59e0b,color:#e6edf3

roslibpy (Python) handles all serialization. The host never needs rclpy, colcon, or a ROS2 installation.

roslibpy threading

roslibpy 2.0 requires all operations to use callFromThread. Subscriptions and publishes issued outside the reactor thread will silently fail. RosbridgeTransport handles this internally.

State Layer

The state layer is in defined-cli with zero imports from Click, Rich, or Textual, so it can be extracted to a defined-core package in Milestone 2.

StateStore

Persists to ~/.defined/state.yaml on every state transition. Atomic writes (write-then-rename).

graph TD
    subgraph STATE["~/.defined/state.yaml"]
        ROBOT["robot:<br/>status: OFFLINE | IDLE | ON_MISSION | ERROR<br/>current_mission: task name | null"]
        WORLD["world:<br/>pois:<br/>  name: {center, radius, type}"]
        HISTORY["history: [MissionRecord, ...]"]
    end

    SESSION["DefinedSession"] -->|"atomic write"| STATE

    style STATE fill:#1a2332,stroke:#2dd4bf,color:#e6edf3

Blackboard (3-Layer)

The Blackboard is a dot-path key-value store with three namespaced layers:

Layer Namespace Lifetime Contents
Constant $const.* Permanent Dock position, fixed infrastructure POIs
Static $world.* Across missions Survey waypoints, named areas
Dynamic $mission.* Single mission Current target, scan results, ephemeral state

POIs are areas (center + radius), not points. They live in world.pois.*.

POI Resolver

mission/resolver.py walks the task YAML before compilation and substitutes $world.pois.<name> references with concrete {x, y} values from the Blackboard. This keeps the BT XML self-contained — no live POI lookups at runtime.

TUI Architecture

The TUI is built on Textual and launches when defined is run with no subcommand.

DefinedApp (tui/defined_app.py)
├── StatusBar          connection status, robot status, mission name
├── MissionPanel       step-by-step mission progress with icons
├── WorldPanel         POI list with coordinates and types
└── DiagnosticsPanel   3-level detail: messages → suggestions → full trace
    CommandBar         bottom bar: /run, /stop, /world add, etc.

Key bindings: 1/2/3 focus panels, d cycles diagnostics detail, / activates command bar, q quits.

CommandBar (tui/command_bar.py) is pure Python with no Textual imports — it parses command strings and returns structured command objects. This makes it testable without a running TUI.

The TUI connects to DefinedSession via an observer pattern. Session events (ConnectionStatus, MissionStatus, SessionEvent) are emitted from background threads and consumed by the TUI on the Textual event loop via app.call_from_thread.