Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.lagerdata.com/llms.txt

Use this file to discover all available pages before exploring further.

An overview of how the Lager platform components fit together — from CLI commands on your laptop to instruments connected to your DUT.

High-Level Overview

USER COMPUTER                                  LAGERBOX (Cybergeek Mini PC)
+---------------------------+                  +----------------------------------------------+
|                           |                  |                                              |
|  $ lager supply psu1      |                  |   Docker Container ("lager")                 |
|         voltage 3.3       |   Tailscale VPN  |  +----------------------------------------+  |
|         --yes             |   (WireGuard)    |  |  Flask/WebSocket Server                |  |
|                           | ───────────────> |  |       Python execution service         |  |
|  $ lager python           |   SSH + HTTP     |  |       Debug service (GDB)              |  |
|         my_test.py        |   over encrypted |  |                                        |  |
|                           |   tunnel         |  |  Hardware Service                      |  |
+---------------------------+                  |  |     |                                  |  |
                                               |  |     v                                  |  |
                                               |  |  Dispatchers --> Drivers               |  |
 GITHUB ACTIONS RUNNER                         |  |     |              |                   |  |
+---------------------------+                  |  +-----|--------------|-------------------+  |
|                           |                  |        |              |                      |
|  $ lager python           |   Tailscale VPN  |  USB / Serial / VISA / LAN                   |
|    tests/ci/test.py       | ───────────────> +--------|--------------|----------------------+
|    --box $BOX_IP          |   (ephemeral key)         |              |
|                           |                  +--------|--------------|----------+
+---------------------------+                  |  INSTRUMENTS                     |
                                               |                                  |
                                               |  Power Supply    Oscilloscope    |
                                               |  LabJack T7      Debug Probe     |
                                               |  Battery Sim     USB Hub         |
                                               |  E-Load          Thermocouple    |
                                               +--------|-------------------------+
                                                        |
                                                        | Wires / probes / pins
                                                        v
                                               +------------------+
                                               |                  |
                                               |   DUT (Device    |
                                               |   Under Test)    |
                                               |                  |
                                               +------------------+
All three entry points — developer CLI commands, developer custom scripts, and CI runners — share the same execution infrastructure. They all reach the Lagerbox through Tailscale VPN and hit Flask on port 5000, which executes the uploaded script or impl module inside the Docker container with full access to lager.* hardware libraries.

Terminology

TermDefinition
CLIThe lager-cli Python package (installed via pip install lager-cli). A Click-based command-line tool that runs on the developer’s laptop.
Tailscale VPNA WireGuard-based mesh VPN that creates an encrypted tunnel between the developer’s machine and the Lagerbox.
LagerboxA Cybergeek mini PC running Linux, physically co-located with the test instruments. Runs a Docker container hosting the Flask/WebSocket server and hardware drivers.
NetA logical name (e.g., psu1, uart0) that maps to a specific instrument + channel + address. Stored in /etc/lager/saved_nets.json on the box.
DUTDevice Under Test — the embedded board or product being tested.
InstrumentA piece of test equipment (power supply, oscilloscope, LabJack, debug probe, etc.) connected to the box via USB, serial, or LAN.
lager pythonCLI command that uploads a user-written Python script to the box for execution with full access to lager.* hardware libraries.

Lagerbox Internals

LAGERBOX HOST (Cybergeek Mini PC, Linux)
+-----------------------------------------------------------------------+
|                                                                       |
|  /etc/lager/                       ~/third_party/                     |
|    saved_nets.json                   JLink_Linux_*/  (optional)       |
|    available_instruments.json        customer-binaries/  (optional)   |
|    box_id                                                             |
|                                                                       |
|  Docker Container "lager"  (--restart always)                         |
| +-------------------------------------------------------------------+ |
| |                                                                   | |
| |  Port 9000 -- Flask + SocketIO -- Main Box API                   | |
| |                  |     (UART, supply, battery, instruments,      | |
| |                  |      nets, lock)                               | |
| |                  v                                                | |
| |  Port 5000 -- HTTP Server -- Python Execution Service             | |
| |                  |                                                | |
| |                  |  receives impl script + LAGER_COMMAND_DATA     | |
| |                  |  env var (JSON), executes it, streams results  | |
| |                  v                                                | |
| |  Port 8765 -- WebSocket Server -- Debug Service (GDB, OpenOCD)    | |
| |                                                                   | |
| |  Port 8100 -- HTTP Server -- MCP Service (AI tool integration)    | |
| |                                                                   | |
| |  Port 8080 -- Hardware Service (internal only, not exposed)       | |
| |                  |                                                | |
| |                  v                                                | |
| |           +------------------+                                    | |
| |           |  NetsCache       |  Thread-safe singleton             | |
| |           |  (cache.py)      |  mtime-based invalidation          | |
| |           +--------+---------+  O(1) lookup by net name           | |
| |                    |                                              | |
| |                    v                                              | |
| |           +------------------+                                    | |
| |           |  Dispatchers     |  BaseDispatcher subclasses         | |
| |           |  (per domain)    |  driver caching, net resolution    | |
| |           +--------+---------+                                    | |
| |                    |                                              | |
| |                    v                                              | |
| |           +------------------+                                    | |
| |           |  Drivers         |  VISA/SCPI, pySerial, LJM,         | |
| |           |  (per instrument)|  pyOCD, aardvark_py, etc.          | |
| |           +--------+---------+                                    | |
| |                    |                                              | |
| +--------------------+----------------------------------------------+ |
|                      |                                                |
|           USB / Serial / VISA / LAN                                   |
+----------------------+------------------------------------------------+
                       |
                       v
                  INSTRUMENTS

Port Summary

PortServiceExposedPurpose
9000Flask + SocketIOYes (VPN only)Main box API: UART streaming, live supply/battery WebSockets, instrument discovery, net listing, box lock
5000HTTPYes (VPN only)Python execution service: receives CLI commands, runs impl scripts
8765WebSocketYes (VPN only)Debug sessions (GDB, flash, reset)
8100HTTPYes (VPN only)MCP service for AI tool integration
8080Hardware ServiceNo (container-internal)Instrument control via Device proxy
8081HTTPYes (if PicoScope present)Oscilloscope streaming UI
8082-8085TCP / WebSocketYes (if PicoScope present)Oscilloscope daemon (commands, browser streaming, database streaming, CLI WebSocket)
8086+HTTPYes (if webcams present)Webcam MJPEG streaming (one port per camera)
22SSHYesDirect SSH access for deployment and debugging

Optional Control Plane Integration

Lager boxes expose an authenticated SSH-key-sync endpoint (POST /authorize-key on port 9000) that an external control plane can use to provision access without a human typing SSH commands. If /etc/lager/control_plane.json is present with an authorize_token, requests carrying that token as a Bearer token can add public keys to /etc/lager/authorized_keys.d/, which the box’s startup script and systemd path unit merge into ~/.ssh/authorized_keys. Lager itself does not require or run a control plane — this is a hook, not a dependency. Leaving control_plane.json absent simply disables the endpoint. Commercial control planes that build on this hook — for fleets that need org/RBAC/SSO, audit logging, and scheduling on top of Lager — are listed on the Professional Services directory.

Net Abstraction

A Net is the central abstraction that decouples CLI commands from physical hardware details.
CLI command                     saved_nets.json entry              Physical hardware
+---------------------+       +----------------------------+      +---------------------+
|                     |       |  {                         |      |                     |
| lager supply psu1   | ----> |    "name": "psu1",         | ---> | Rigol DP832         |
|       voltage 3.3   |       |    "type": "power-supply", |      |   Channel 1         |
|                     |       |    "channel": 1,           |      |   VISA: USB0::...   |
+---------------------+       |    "instrument": {         |      +---------------------+
                              |      "name": "rigol-dp832",|
                              |      "address": "USB0::.."|
                              |    },                      |
                              |    "params": {             |
                              |      "voltage_limit": 5.0  |
                              |    }                       |
                              |  }                         |
                              +----------------------------+

Supported Net Types

Net TypeInstruments
power-supplyRigol DP800, Keithley 2200/2280, Keysight E36x00
batteryKeithley 2281S
eloadRigol DL3021
solarEA PSI / EL series
analogRigol MSO5000 (oscilloscope analog channel)
logicRigol MSO5000 (logic analyzer channel)
adcLabJack T7, USB-202
dacLabJack T7, USB-202
gpioLabJack T7, USB-202
thermocouplePhidget thermocouple
wattYocto-Watt, Joulescope JS220
debugJ-Link, CMSIS-DAP, ST-Link (via pyOCD)
uartUSB-to-serial adapters
i2cAardvark, LabJack T7, FT232H
spiLabJack T7, FT232H
armRotrics Dexarm
usb-hubAcroname, YKUSH

Execution Flows

CLI Command Execution

Step-by-step data path for lager supply psu1 voltage 3.3 --yes:
DEVELOPER LAPTOP                    NETWORK              LAGERBOX
================                    =======              ========

1. User runs:
   $ lager supply psu1
         voltage 3.3 --yes
         |
         v
2. CLI resolves box
   - reads .lager/config
   - finds box IP for "psu1"
         |
         v
3. CLI builds command JSON
         |
         v
4. CLI uploads impl script          5. SSH/HTTP over
   cli/impl/power/supply.py  ---------> Tailscale VPN -------->  6. Flask receives
   + sets env var                        (encrypted)                  request on :5000
   LAGER_COMMAND_DATA=<JSON>                                              |
                                                                          v
                                                                  7. Box executes
                                                                     supply.py in
                                                                     subprocess
                                                                          |
                                                                          v
                                                                  8. Dispatcher looks up
                                                                     "psu1" in NetsCache,
                                                                     selects driver
                                                                          |
                                                                          v
                                                                  9. Driver sends SCPI
                                                                     command to instrument
                                                                          |
                                                                          v
14. CLI displays:  <--------------- result JSON <-----------  10. Result streamed back
    "Voltage set to 3.300V"          streamed back

Custom Script Execution (lager python)

The lager python command uploads a user-written Python script to the box for execution. It uses the same Flask :5000 execution path as CLI commands.
$ lager python my_test.py --box mybox --env VOLTAGE=3.3 --timeout 300
The script runs inside the Docker container with full access to lager.* hardware libraries, and output is streamed back in real time.

Physical Wiring

How instruments physically connect between the Lagerbox and the DUT:
                    LAGERBOX
              (Cybergeek Mini PC)
                  +----------+
                  |          |
            USB-A |  USB-A   | USB-A        LAN
            port  |  port    | port         port
              |   |    |     |   |            |
              |   +----+-----+   |            |
              |        |         |            |
    +---------+   +----+----+   +--------+   +------------+
    |             |         |   |        |   |            |
    v             v         v   v        v   v            |
+--------+  +---------+ +------+  +--------+ +--------+  |
| LabJack|  | Debug   | | USB  |  |Aardvark| | Phidget|  |
| T7     |  | Probe   | | Hub  |  | I2C/SPI| | Thermo |  |
+---+----+  +----+----+ +--+---+  +---+----+ +---+----+  |
    |            |          |          |          |       |
    |            v          v          |          |       |
    |       SWD/JTAG    USB to DUT    |          |       |
    |                                 |          |       |
    v                                 v          v       v
+----------------------------------------------------------------+
|   DUT (Device Under Test)                                      |
|   VCC, GND, SDA, SCL, SWD, TX, RX, GPIO, TEMP, USB            |
+----------------------------------------------------------------+
                                                         |
+--------------------------------------------------------+
|  VISA-over-LAN Instruments (on same LAN)               |
|  Rigol DP832, Rigol MSO5074, Keithley 2281S            |
|  Connected to DUT via banana jacks / BNC / probes      |
+--------------------------------------------------------+

Connection Types

ConnectionUsed ForProtocol
USBLabJack, debug probes, serial, hubsVendor-specific, CDC-ACM
USB-VISARigol/Keithley/Keysight instrumentsUSBTMC (SCPI)
LAN-VISABench instruments on local networkVXI-11 / raw TCP (SCPI)
Serial (UART)DUT communicationRS-232 / TTL via USB adapter
SWD / JTAGFirmware flash, debug, resetARM debug (via probe)
I2C / SPIPeripheral communication with DUTI2C / SPI via Aardvark or LabJack

GitHub Actions CI Integration

A CI runner is an ephemeral GitHub Actions VM that joins Tailscale and runs lager commands exactly like a developer would — no special CI-specific infrastructure is needed.
GITHUB CLOUD                         TAILSCALE NETWORK           HARDWARE LAB
============                         =================           ============

+---------------------------+
| GitHub Actions Runner     |
| (ubuntu-latest)           |
|                           |
| 1. checkout code          |
| 2. pip install lager-cli  |
| 3. tailscale up       ----------->  Tailscale         +------------------+
|    (TAILSCALE_AUTHKEY)    |         Coordination  -->  | LAGERBOX         |
|                           |         Server             | 100.x.y.z       |
| 4. lager hello         --.-------->                    |                  |
|    --box $BOX_IP          |    encrypted tunnel        | Docker container |
|                           |    (WireGuard)             |   :5000 Flask    |
| 5. lager python        --.-------->                    |                  |
|    tests/ci/test.py       |                            +--------+---------+
|    --box $BOX_IP          |                                     |
|                           |                              USB / VISA / LAN
+---------------------------+                                     |
                                                           +------+------+
                                                           | Instruments |
                                                           +------+------+
                                                                  |
                                                           +------+------+
                                                           |     DUT     |
                                                           +-------------+

Required GitHub Secrets

SecretPurpose
TAILSCALE_AUTHKEYEphemeral auth key so the runner can join the VPN
LAGER_BOX_IPTailscale IP of the Lagerbox (e.g., 100.x.y.z)

Workflow Example

name: Hardware-in-the-Loop Test
on:
  workflow_dispatch:

concurrency:
  group: hardware-test
  cancel-in-progress: false

jobs:
  hardware-test:
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Install Lager CLI
        run: cd cli && pip install -q -e .

      - name: Connect to hardware lab via Tailscale
        uses: tailscale/github-action@v2
        with:
          authkey: ${{ secrets.TAILSCALE_AUTHKEY }}

      - name: Verify box connectivity
        run: lager hello --box ${{ secrets.LAGER_BOX_IP }}

      - name: Run hardware integration test
        run: |
          lager python tests/ci/demo_test.py \
            --box ${{ secrets.LAGER_BOX_IP }} \
            --add-file test/assets/firmware/nrf_blinky.hex \
            --env FIRMWARE_PATH=nrf_blinky.hex \
            --timeout 300

      - name: Emergency cleanup
        if: failure()
        run: |
          lager python tests/ci/cleanup.py \
            --box ${{ secrets.LAGER_BOX_IP }} || true