Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions PLATFORM.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Connecting the demos to the selfpatch platform

Any demo in this repository can show up in the selfpatch platform as a robot. The connection goes through the selfpatch agent: a small container next to the demo that opens an outbound connection to the platform and lets the platform read the demo's ros2_medkit gateway. Nothing on the demo side listens for the platform, so the same steps work when the platform is in the cloud and the demo is behind NAT.

This page is also written for AI coding assistants. Paste it into the assistant's context before asking it to run a demo against the platform or to build platform UI on top of a demo.

## What you need

- A running demo (`./run-demo.sh` in its directory).
- An account on the platform you want to use.
- Docker. The scripts pull the public agent image `ghcr.io/selfpatch/agent:0.1` (amd64 and arm64). `SELFPATCH_AGENT_IMAGE` picks another one.

## Connect a demo

1. In the platform: Devices > Add device > "Through the selfpatch agent". Copy the pairing code. It is valid for 10 minutes.
2. In the demo directory:

```bash
./connect-platform.sh
```

Paste the code when asked, or pass it as `PAIRING_CODE=XXXX-XXXX-XXXX-XXXX`.

3. The device shows as online in the platform within a few seconds.

The agent runs as the container `selfpatch-agent-<gateway container>` and keeps its credential in a volume of the same name. It restarts with Docker and reconnects on its own. `./connect-platform.sh --disconnect` removes it. Remove the device in the platform as well.

Available in `sensor_diagnostics`, `ota_nav2_sensor_fix` and `turtlebot3_integration`. For any other demo run `lib/connect-platform.sh <gateway container>`.

| Variable | Default | Use |
|---|---|---|
| `PLATFORM_URL` | `https://platform.selfpatch.ai` | The platform to pair with, for example a staging deployment |
| `PAIRING_CODE` | asked for | The code from the platform |
| `SELFPATCH_AGENT_IMAGE` | `ghcr.io/selfpatch/agent:0.1` | Agent image |
| `PLATFORM_INSECURE_HTTP` | `0` | `1` allows a plain http platform, for a local dev platform |
| `GATEWAY_CONTAINER` | the demo's gateway container | Which demo instance to connect |

## A platform running on your machine

The agent runs in a container, so `localhost` is the container itself. Point it at the host:

```bash
PLATFORM_URL=http://host.docker.internal:<platform port> PLATFORM_INSECURE_HTTP=1 ./connect-platform.sh
```

On Linux, `host.docker.internal` needs `--add-host=host.docker.internal:host-gateway` or the host's LAN address instead.

## Several robots

Every demo instance is its own device in the platform, with its own agent. The sensor demo can run several times side by side:

```bash
cd demos/sensor_diagnostics
COMPOSE_PROJECT_NAME=robot2 DEMO_CONTAINER=robot2 GATEWAY_PORT=8082 \
WEB_UI_CONTAINER=robot2_web_ui WEB_UI_PORT=3002 ROS_DOMAIN_ID=41 \
docker compose up -d --build
GATEWAY_CONTAINER=robot2 ./connect-platform.sh
Comment thread
mfaferek93 marked this conversation as resolved.
```

To stop it, disconnect the agent first. While it is attached, compose cannot remove the network:

```bash
GATEWAY_CONTAINER=robot2 ./connect-platform.sh --disconnect
COMPOSE_PROJECT_NAME=robot2 ./stop-demo.sh
```

Give each instance its own `ROS_DOMAIN_ID` so the robots do not see each other's ROS graph. The fault scripts take `GATEWAY_URL`, for example `GATEWAY_URL=http://localhost:8082 ./inject-noise.sh`.

## Making something happen

| Demo | Faults | Back to normal |
|---|---|---|
| `sensor_diagnostics` | `./inject-noise.sh`, `./inject-drift.sh`, `./inject-nan.sh`, `./inject-failure.sh`, `./inject-fault-scenario.sh` (all sensors at once) | `./restore-normal.sh` |
| `ota_nav2_sensor_fix` | `./send-goal.sh`: the robot drives into the phantom obstacle of the broken lidar, Nav2 aborts | `./publish-fix.sh`, `./apply-fix.sh`, `./clear-fault.sh`, `./send-goal.sh` again |
| `turtlebot3_integration` | `./inject-nav-failure.sh`, `./inject-localization-failure.sh` | `./restore-normal.sh` |

## What the gateway exposes

The platform shows what the demo's gateway returns. The full list of routes is in the gateway's own OpenAPI document: `curl http://localhost:8080/api/v1/docs`. The parts a platform view usually needs:

- Entity tree: `/areas`, `/components`, `/apps`, `/functions`, each with `/{id}` and relations such as `/components/{id}/hosts`.
- Faults: `/faults` (all), `/faults/stream` (server-sent events), `/{entity}/faults`, `/{entity}/faults/{fault_code}` with freeze frames and the recording reference.
- Recordings: `/{entity}/bulk-data` and `/{entity}/bulk-data/rosbags/{id}` (MCAP).
- Live values: `/{entity}/data` and `/{entity}/data/{data_id}`.
- Logs: `/{entity}/logs`.
- Health and version: `/health`, `/version-info`.

Through the platform the agent forwards read requests (GET) only. Operations, scripts, configuration writes and fault clearing work on the gateway directly (`http://localhost:8080`) but not through the platform.

## Building platform UI on a demo

Build against what the gateway actually returns, not against mocked data. Before adding a view, call the endpoint on a running demo and use that response. If a view needs data the gateway does not expose, that is a finding, not something to mock: open an issue in [ros2_medkit](https://github.com/selfpatch/ros2_medkit/issues) with the view you want, the endpoint you tried and its response. Some gaps are by design and some are missing features, and the issue is where that gets decided.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,18 @@ cd demos/sensor_diagnostics # or turtlebot3_integration, moveit_pick_place, mul
# Follow the README.md in that directory
```

## Connect a Demo to the selfpatch Platform

`sensor_diagnostics`, `ota_nav2_sensor_fix` and `turtlebot3_integration` can show up in the selfpatch platform as robots through the selfpatch agent:

```bash
cd demos/sensor_diagnostics
./run-demo.sh
./connect-platform.sh # paste the pairing code from the platform
```

Several robots side by side, a platform on your own machine and what the gateway exposes to the platform: see [PLATFORM.md](PLATFORM.md).

## Example API Usage

All demos expose a SOVD-compliant REST API. Here are some common operations:
Expand Down
9 changes: 9 additions & 0 deletions demos/ota_nav2_sensor_fix/connect-platform.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash
# Connect this demo to the selfpatch platform through the selfpatch agent.
# See lib/connect-platform.sh for the environment variables.
#
# ./connect-platform.sh pair and start the agent
# ./connect-platform.sh --disconnect stop and remove the agent

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
exec "${SCRIPT_DIR}/../../lib/connect-platform.sh" "${GATEWAY_CONTAINER:-ota_demo_gateway}" "$@"
9 changes: 9 additions & 0 deletions demos/sensor_diagnostics/connect-platform.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash
# Connect this demo to the selfpatch platform through the selfpatch agent.
# See lib/connect-platform.sh for the environment variables.
#
# ./connect-platform.sh pair and start the agent
# ./connect-platform.sh --disconnect stop and remove the agent

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
exec "${SCRIPT_DIR}/../../lib/connect-platform.sh" "${GATEWAY_CONTAINER:-sensor_diagnostics_demo}" "$@"
12 changes: 7 additions & 5 deletions demos/sensor_diagnostics/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ services:
build:
context: .
dockerfile: Dockerfile
container_name: sensor_diagnostics_demo
# Names, ports and the ROS domain can be overridden to run more than one
# robot side by side, see "Several robots" in the README.
container_name: ${DEMO_CONTAINER:-sensor_diagnostics_demo}
environment:
- ROS_DOMAIN_ID=40
- ROS_DOMAIN_ID=${ROS_DOMAIN_ID:-40}
- BEACON_MODE=${BEACON_MODE:-none}
ports:
- "8080:8080"
- "${GATEWAY_PORT:-8080}:8080"
stdin_open: true
tty: true
# Default command launches the full demo
Expand All @@ -23,9 +25,9 @@ services:
# Web UI for visualization (optional)
medkit-web-ui:
image: ghcr.io/selfpatch/ros2_medkit_web_ui:latest
container_name: ros2_medkit_web_ui
container_name: ${WEB_UI_CONTAINER:-ros2_medkit_web_ui}
ports:
- "3000:80"
- "${WEB_UI_PORT:-3000}:80"
depends_on:
- sensor-demo

Expand Down
16 changes: 16 additions & 0 deletions demos/turtlebot3_integration/connect-platform.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash
# Connect this demo to the selfpatch platform through the selfpatch agent.
# See lib/connect-platform.sh for the environment variables.
#
# ./connect-platform.sh pair and start the agent
# ./connect-platform.sh --disconnect stop and remove the agent

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# run-demo.sh --nvidia starts the gateway under a different container name.
if [[ -z "${GATEWAY_CONTAINER:-}" ]]; then
GATEWAY_CONTAINER=turtlebot3_medkit_demo
if [[ "$(docker inspect -f '{{.State.Running}}' turtlebot3_medkit_demo_nvidia 2>/dev/null)" == "true" ]]; then
GATEWAY_CONTAINER=turtlebot3_medkit_demo_nvidia
fi
fi
exec "${SCRIPT_DIR}/../../lib/connect-platform.sh" "$GATEWAY_CONTAINER" "$@"
89 changes: 89 additions & 0 deletions lib/connect-platform.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/bin/bash
# Connect a running demo to the selfpatch platform through the selfpatch agent.
# Source-free: each demo's connect-platform.sh calls this with its gateway
# container name.
#
# lib/connect-platform.sh <gateway-container> pair and start the agent
# lib/connect-platform.sh <gateway-container> --disconnect stop and remove the agent
#
# The agent runs as its own container on the demo's Docker network, opens an
# outbound connection to the platform and lets it read the demo's gateway. It
# keeps its credential in a volume, so it survives restarts and reconnects on
# its own. Each demo instance gets its own agent and shows up in the platform
# as its own device.
#
# Environment variables:
# PLATFORM_URL - platform to pair with (default: https://platform.selfpatch.ai)
# PAIRING_CODE - pairing code from the platform (asked for when unset)
# SELFPATCH_AGENT_IMAGE - agent image (default: ghcr.io/selfpatch/agent:0.1)
# PLATFORM_INSECURE_HTTP=1 - allow a plain http platform, e.g. a local dev
# platform at http://host.docker.internal:<port>

set -eu

usage() {
echo "Usage: $0 <gateway-container> [--disconnect]"
}

if [[ $# -lt 1 ]]; then
usage
exit 1
fi

GATEWAY_CONTAINER="$1"
ACTION="${2:-connect}"
PLATFORM_URL="${PLATFORM_URL:-https://platform.selfpatch.ai}"
AGENT_IMAGE="${SELFPATCH_AGENT_IMAGE:-ghcr.io/selfpatch/agent:0.1}"
AGENT_NAME="selfpatch-agent-${GATEWAY_CONTAINER}"

if [[ "$ACTION" == "--disconnect" ]]; then
docker rm -f "$AGENT_NAME" >/dev/null 2>&1 || true
docker volume rm "$AGENT_NAME" >/dev/null 2>&1 || true
echo "Agent ${AGENT_NAME} removed. Remove the device in the platform too."
exit 0
fi

if [[ "$(docker inspect -f '{{.State.Running}}' "$GATEWAY_CONTAINER" 2>/dev/null)" != "true" ]]; then
echo "Container ${GATEWAY_CONTAINER} is not running. Start the demo first: ./run-demo.sh"
exit 1
fi

NETWORK="$(docker inspect -f '{{range $name, $_ := .NetworkSettings.Networks}}{{$name}} {{end}}' "$GATEWAY_CONTAINER" | awk '{print $1}')"
if [[ -z "$NETWORK" || "$NETWORK" == "host" ]]; then
MEDKIT_URL="http://127.0.0.1:8080"
NETWORK="host"
else
MEDKIT_URL="http://${GATEWAY_CONTAINER}:8080"
fi

if ! docker image inspect "$AGENT_IMAGE" >/dev/null 2>&1 && ! docker pull -q "$AGENT_IMAGE" >/dev/null; then
echo "Cannot pull ${AGENT_IMAGE}. Set SELFPATCH_AGENT_IMAGE to an agent image you can reach."
exit 1
fi

if [[ -z "${PAIRING_CODE:-}" ]]; then
echo "In the platform: Devices > Add device > 'Through the selfpatch agent', then copy the pairing code."
read -r -p "Pairing code: " PAIRING_CODE
fi

PAIR_ARGS=(pair --platform-url "$PLATFORM_URL" --medkit-url "$MEDKIT_URL")
RUN_ARGS=(run)
if [[ "${PLATFORM_INSECURE_HTTP:-0}" == "1" ]]; then
PAIR_ARGS+=(--insecure-platform-http)
RUN_ARGS+=(--insecure-platform-http)
fi

docker volume create "$AGENT_NAME" >/dev/null
echo "$PAIRING_CODE" | docker run --rm -i --network "$NETWORK" \
-e SELFPATCH_AGENT_STATE=/data -v "${AGENT_NAME}:/data" \
"$AGENT_IMAGE" "${PAIR_ARGS[@]}"

docker rm -f "$AGENT_NAME" >/dev/null 2>&1 || true
docker run -d --name "$AGENT_NAME" --restart unless-stopped --network "$NETWORK" \
-e SELFPATCH_AGENT_STATE=/data -v "${AGENT_NAME}:/data" \
"$AGENT_IMAGE" "${RUN_ARGS[@]}" >/dev/null

echo ""
echo "Agent ${AGENT_NAME} is running. The demo appears in the platform within a few seconds."
echo "Logs: docker logs -f ${AGENT_NAME}"
echo "Disconnect: GATEWAY_CONTAINER=${GATEWAY_CONTAINER} ./connect-platform.sh --disconnect (in the demo directory)"
Loading