Use Cyberwave Edge with the CLI
SSH to the edge device where you want to install Edge Core, then install the Cyberwave CLI and run the installer:cyberwave pair is the recommended top-level alias for cyberwave edge install. Both run the same first-time setup flow.Prerequisites by platform (stub):
- Linux: Docker is auto-installed via
apt-getwhen missing (Ubuntu / Debian / Raspbian). - macOS: Docker Desktop must be installed and running before
cyberwave edge install(the installer aborts early with a hint if not). The Rust toolchain is auto-installed viarustup(with confirmation) when missing — it is needed to compile the USB/IP server used for USB device passthrough.
| Subcommand | Description |
|---|---|
install | Install cyberwave-edge-core and register systemd service. On macOS, also sets up the camera stream bridge and prompts for camera selection. |
install --reconfigure-camera | Re-select camera and restart the stream and edge-core without a full reinstall |
start | Start the edge node |
stop | Stop the edge node |
restart | Restart the edge node (systemd or process) |
status | Check if the edge node is running |
logs | Show edge node logs |
driver | Manage edge driver containers (subgroup) |
Host telemetry
Everyedge_health heartbeat carries the host’s dynamic resource pressure alongside the per-stream health it has always carried:
host_memory_percent,host_memory_available_mbcpu_temp_cconsecutive_critical— reconcile cycles where memory or CPU sat above the critical threshold; resets when pressure clearswatchdog_layers— e.g.["systemd"]on a developer laptop or["systemd", "hardware"]on a Raspberry Pi with/dev/watchdog
/proc, not the host’s, and omit these fields. Only the bootstrap publisher running on the edge host populates them.
Static hardware identity — total RAM, CPU model, kernel, thermal source, hardware-watchdog availability — is uploaded via POST /api/v1/edges/discover and stored on Edge.metadata.host_facts. The first POST runs synchronously at startup; a background daemon then re-POSTs every ~30 s so the backend can tell “edge daemon alive” apart from “edge daemon dead” even when no twin MQTT traffic is flowing. The Edge Devices tab renders both signals: a colour-coded pressure pill from the live heartbeat plus a “what hardware is this” row from the REST identity.
The REST keepalive powers a three-state liveness pill — Online (twin MQTT activity), Idle (edge alive, no twin work; liveness_state="standby" on the wire), Offline (neither signal within window). Idle is what shows up on a freshly-paired edge that hasn’t had any twins bound to it yet: previously such an edge would render as “Offline” even though edge-core was running fine. The Edge Devices tab sorts rows by liveness — online → idle → offline → unresolved binding — so working edges land at the top and broken bindings sink to the bottom.
Static facts work on both Linux and macOS: Linux reads /proc/{meminfo,cpuinfo} and /sys/class/thermal; Darwin shells out to sysctl for RAM, CPU model and (logical) core count. Dynamic host pressure (memory %, CPU temp) remains Linux-only for now — the dashboard hides the pressure pill on macOS edges and shows only the static identity row, and thermal_source is omitted on macOS so it never claims a sensor that isn’t being sampled.
Per-stream configuration
Each entry understreams[id] in an edge_health heartbeat carries an optional typed stream_config block describing the runtime-negotiated config for that stream. The block is a discriminated union keyed by kind:
EdgeHealthCheck.register_stream_config(stream_id, config)for static configs known at startup; the publisher keeps the snapshot and merges it into every heartbeat. The Go2lidar_bridge.pyandpointcloud_bridge.pyuse this path because the scan rate is wired from a ROS parameter at node bootstrap.EdgeHealthCheck(..., stream_config_provider=callable)for dynamic configs where some fields are only known after streamer startup (post-V4L2actual_fps, post-handshake codec, …). The callable is invoked on every heartbeat and its values override registered statics on key collision.MicrophoneAudioStreamerandMultimediaStreameruse this path so re-opening a mic at a different sample rate is reflected on the very next heartbeat without re-registration.MultimediaStreamerkeys its audio block under"audio"rather than the legacy"stream"placeholder so a future videoget_stream_confighook can slot in next to it as"video"without a wire-breaking rename.
register_stream_config enforces a per-kind required-field contract: kind: "camera" must carry source, resolution, fps; kind: "audio" must carry sample_rate_hz and channels (and may optionally carry codec and source); kind: "lidar" requires source and scan_rate_hz (points_per_second is optional and intentionally omitted by both Go2 bridges because the only available value is the post-downsample cap, not measured throughput); kind: "imu" requires source and rate_hz. Unknown kinds pass through additively. URL-shaped source values must have user:pass@ stripped before they reach the wire — the SDK’s CV2CameraStreamer does this automatically for RTSP and HTTP sources. kind: "audio" deliberately treats source as optional: across the rest of the SDK source is a device path / URL / ROS topic, and a WebRTC microphone has no equivalent (publishing the host ALSA / CoreAudio device path would leak operator-local filesystem state).
Liveness — frame_count vs mark_alive. Camera and lidar publishers call EdgeHealthCheck.update_frame_count() per emitted frame so the wire-side fps / frames_sent numbers carry meaning to operators (frames per second is the standard quality signal for both). Audio publishers — and future IMU / encoder / GPS publishers — call EdgeHealthCheck.mark_alive() instead, which advances the staleness clock without bumping the counter. Reporting fps: 50.0 for a microphone emitting 20 ms Opus packets would be terminology-correct but operationally noise; the dashboard already hides those fields for audio rows, but raw MQTT subscribers and the analytics table see the cleaner shape too. mark_alive still flips ice_connection_state to connected on first call so audio rows render connected from the first heartbeat onward.
A top-level camera_config slot remains populated for one deprecation release for out-of-tree consumers that still read the legacy single-camera shape. New consumers must read streams[id].stream_config instead — the legacy slot only ever covers one camera and is silent for audio / lidar / IMU streams. When more than one camera-kind stream_config is registered, the legacy slot deterministically reflects the lexicographically-smallest stream_id so multi-camera devices don’t flap identities heartbeat-to-heartbeat.
An idle publisher that has registered no stream_config and has never seen a frame emits an empty streams: {} map, stream_count: 0, and camera_config: null. This is the wire shape the edge-core bootstrap publisher uses in the gap before a driver container starts — the dashboard’s Edge details renders nothing for streams in that case, rather than a phantom "stream" row that flickers away the moment a real driver takes over. Once a driver starts publishing (either by registering a stream_config or by ticking update_frame_count()), the entry appears on the next heartbeat. The dashboard’s per-row renderer falls back to the asset’s declared parameters.{width, height, update_rate} for camera streams when neither the wire stream_config nor the legacy camera_config carries that data, so paired cameras still show their target resolution and fps before the live stream catches up.
Upgrading
Edge Core ships the orchestrator binary and a systemd unit file. From the high-availability watchdog release onward the unit usesType=notify and the binary issues a READY=1 notification at startup — the two must match.
When upgrading on an existing device, re-run sudo cyberwave edge install after the package upgrade to regenerate the systemd unit. Skipping this step results in one of two states:
- Old binary, new unit (
Type=notify): systemd waits the fullTimeoutStartSec(900 s) for aREADY=1that never arrives and marks the service failed. - New binary, old unit (
Type=simple): the service runs, butWatchdogSec/OOMScoreAdjust/TimeoutStartSecare not in effect — no watchdog protection.
pip install cyberwave-edge-core then cyberwave edge install) require the manual step.
Related
Live Teleoperation
Control robots in real time through Edge Core
Quick Start
Connect real hardware step by step
Edge Drivers
Write and manage hardware drivers