Skip to content

Advanced Subsystem Packages

The cleaner tutorial now runs robot subsystem packages as managed Librux packages. run_sim_world.py only starts the provided simulator and browser frontend. Early tutorial steps use librux launch run to start one subsystem package at a time; the final application step uses librux deploy up to reconcile the full robot.

This page explains the package boundary using the same cleaner subsystem package you launched in Step 3.

1. Package Boundary

A Librux package wraps one reusable subsystem.

subsystem package root
  package.yaml
  subsystem.yaml
  subsystem implementation
  config/
  spec/
  frontend/        optional static UI

For the cleaner tutorial, each subsystem folder is also a package root.

tutorials/subsystems/
  component.mobile/
    package.yaml
    subsystem.yaml
    mobile_controller.py
    run.py

Reasonable subsystem packages include these examples.

Subsystem package Role
gateway.device_gateway simulator-backed gateway subsystem package
component.mobile mobile-base component subsystem package
component.manipulation manipulator component subsystem package
component.gripper gripper component subsystem package
component.perception perception component subsystem package
app.cleaner.teleop operator application package
app.cleaner.radar perception-driven application package

Deployment decides which installed package instance fills each robot slot.

2. Inspect A Managed Subsystem Package

Open the mobile subsystem package manifest.

nano tutorials/subsystems/component.mobile/package.yaml

The package manifest declares the following.

Field Meaning
name reusable package identity
entrypoint command, arguments, environment, and working directory
parameters launch-time values allowed by the package
resources CPU, network, device, CAN, or serial claims
frontend optional static package UI served by the control backend
contracts package-local spec roots

Packages can include static browser UI. If the package also runs a local UI backend, it must not open an unmanaged network listener. Declare resources.sandbox.network: brokered, expose browser assets as static files, and open any declared frontend.context listener through the resource broker.

resources:
  sandbox:
    network: brokered
frontend:
  type: static
  root: frontend/dist
  index: index.html
  context:
    api: http://127.0.0.1:{{param.gui_port}}/
    ws: ws://127.0.0.1:{{param.gui_port}}/ws

The Web Console serves the static files at /api/v1/packages/<package-id>/ui/. Runtime opens a specific execution instance.

Build static package assets with relative URLs. This keeps scripts, styles, and images below the package-scoped /api/v1 route without hardcoding a package id.

/api/v1/packages/<package-id>/ui/?instance=<instance-id>&context=/api/v1/packages/<package-id>/instances/<instance-id>/context

Frontend code should read the context endpoint and use the returned same-origin api and ws proxy paths. It must not hardcode host ports. App code should call the platform through Librux bindings and should not claim NIC, CAN, serial, or device resources directly. Component packages may claim CPU and brokered package-frontend networking, but hardware and fieldbus resources still belong behind gateway packages.

Open the subsystem manifest next to it.

nano tutorials/subsystems/component.mobile/subsystem.yaml

The package entrypoint is the package-local run.py wrapper. The runtime looks for subsystem.yaml by default, so most one-subsystem packages do not need a subsystem_manifest field in package.yaml. Use subsystem_manifest only when a package intentionally keeps the subsystem manifest at another relative path. There is no separate packages/ wrapper directory in the cleaner tutorial.

3. Validate The Package

Use installed runtime tools.

. /opt/librux/env.sh
librux package validate tutorials/subsystems/component.mobile

Validation checks manifest schema, package name safety, entrypoint paths, optional static frontend paths, contract spec roots, and resource claim format.

4. Build A Runnable Package Directory

librux package build tutorials/subsystems/component.mobile \
  --out /tmp/librux-robot-mobile.libruxpkg \
  --force

The current build step validates and copies an already runnable package directory. It does not compile application source, vendor dependencies, sign artifacts, or create a system installer.

To upload through the Web Console, archive the package directory.

tar -czf /tmp/librux-robot-mobile.libruxpkg.tar.gz \
  -C /tmp librux-robot-mobile.libruxpkg

5. Launch Through Librux

The installed resource service normally runs as a system service. If you are using a source-built runtime, start a local resource service first. With an installed release, launch the package.

librux launch run /tmp/librux-robot-mobile.libruxpkg \
  --config /opt/librux/runtime/config.yaml \
  --instance component.mobile \
  --bind actuator_servo=gateway.device_gateway.actuator_servo

Expected result.

  • launcher output starts with Ran package or Launched package
  • the child process receives package environment variables
  • the child process receives a resource lease
  • package runtime files are written under the instance runtime directory
  • detached stdout/stderr logs are written under <instance-runtime-dir>/logs/stdout.log and <instance-runtime-dir>/logs/stderr.log

On an installed /opt/librux runtime, the default instance runtime directory is under /opt/librux/var/run/packages/<instance-id>. This keeps installed packages and tutorials read-only while still giving each launched instance a writable state directory. In a source checkout without LIBRUX_PREFIX, the fallback is package-local var/instances/<instance-id>.

If a detached process exits immediately, the launcher reports that early exit instead of returning a successful launch result. Check the stderr log path shown in the error for the package-side failure.

Add --json when you need the full machine-readable launch payload.

Launch a second instance with different parameters.

librux launch run /tmp/librux-robot-mobile.libruxpkg \
  --instance component.mobile.test \
  --set subsystem.name=component.mobile.test \
  --bind actuator_servo=gateway.device_gateway.actuator_servo \
  --detach

This is the same model a product cleaner robot would use to run multiple instances of a reusable controller package.

6. Web Console Management

The Web Console manages the same package contract.

  • upload a .tar.gz or .tgz package archive
  • validate and install it
  • launch one or more runtime instances of the installed package from Runtime
  • expose optional static package frontends under /api/v1/packages/<package-id>/ui/
  • open package frontends from concrete Runtime instances with instance context
  • stop or restart individual runtime instances from Runtime
  • uninstall managed packages when no runtime instances are running

Install registers the package once. Multiple execution instances are created at launch time by choosing distinct instance ids. A package must use {{instance.id}} or explicit parameters for runtime names if it is expected to run more than once.

The Packages page accepts packages for gateway, component, compound, and app subsystem roles. role: app still has the strictest policy: application subsystems must call compound capabilities rather than claiming NIC, CAN, serial, or device resources directly. Packages with browser UI should declare static assets in frontend. If the frontend needs a package-specific REST or WebSocket backend, declare frontend.context.api or frontend.context.ws as localhost endpoint templates. The control backend exposes them to the browser through instance-scoped same-origin proxy paths. The package backend must not call bind() on a new TCP socket directly. Open the declared loopback listener through the broker instead.

from librux.core import ResourceBrokerClient

listener = ResourceBrokerClient().open_frontend_tcp_listener(kind="api").socket
# pass listener to the HTTP server implementation for the app backend

Only packages launched through Librux receive leases, resource policy, runtime directories, and Web Console lifecycle management. Directly running a Python script is useful for development, but it is not a managed robot subsystem.

In practical terms.

python3 subsystem.py
  -> can register and communicate as a subsystem
  -> does not create a package lease or launcher-owned lifecycle

librux launch run package-dir
  -> validates the package
  -> starts through librux-resourced
  -> creates a lease, runtime directory, and lifecycle handle

7. Cleaner Product Shape

The tutorial world uses the following.

run_sim_world.py -> simulator + static frontend

Single-subsystem execution uses the following.

subsystem package root -> librux launch run -> managed lease

Full robot execution uses the following.

deployment manifest -> package instances -> managed launch -> resource policy

For the cleaner, deployment manifests bind package instances into public robot slots.

mobile_base_controller    -> installed mobile-base controller package instance
manipulator_controller    -> installed manipulator controller package instance
gripper_controller        -> installed gripper controller package instance
perception_controller     -> installed perception controller package instance
actuator_gateway      -> installed gateway package instance
digital_output_gateway -> installed gateway package instance
perception_gateway    -> installed gateway package instance
application           -> selected cleaner application package

Before packaging the cleaner as a product robot artifact, keep the same standard contract instances and rerun librux deploy validate.

Continue to Advanced Teleop App Frontend.