blipwatch
v1.4.2
Published
Server-based interface for HALO-based radars.
Readme
BlipWatch
BlipWatch is an open-source Node.js server for receiving radar data from a Navico/B&G/Simrad HALO radar on a local Ethernet network and exposing runtime vessel-surrounding awareness through standard HTTP endpoints.
Current capabilities include HALO receive/decode/render, passive discovery, active wake/transmit control, replay APIs and dashboard controls, target overlays and management APIs, runtime diagnostics, and profiling helpers.
Project Intent
BlipWatch is not primarily a chartplotter replacement and is not limited to producing a radar picture.
The core goal is object-centric monitoring: detect and track nearby objects, summarize tangible risk around the vessel, and present concise safety-corridor information that can support real anchoring and close-quarters decisions.
This includes identifying patterns such as potential dragging of own vessel or nearby vessels, and building toward multi-sensor context (radar first, then AIS/camera and other inputs over time).
One specific direction is augmenting GPS-based understanding with radar-derived relative motion so BlipWatch can help estimate own-vessel position and movement behavior within an anchorage.
Safety Notice
BlipWatch is experimental situational-awareness software.
It is not a certified navigation, collision-avoidance, watchkeeping, or safety-of-life system. Do not rely on BlipWatch as the sole source of navigational information. Use certified marine instruments, visual watchkeeping, radar displays, AIS, charts, and seamanship appropriate to the vessel and conditions.
Roadmap and Validation
Planning, validation notes, release preparation status, and longer-form hardware test guidance now live in ROADMAP.md.
Requirements
- Node.js 22 or newer
- npm
- Docker, when building or running the container
- Direct network access to radar UDP traffic for live onboard use
Install on macOS with Homebrew
Install and run:
brew tap SternAnchor/tap
brew install blipwatch
BLIPWATCH_HEADLESS=true blipwatchOpen the dashboard at http://localhost:8080.
Use BLIPWATCH_HEADLESS=false blipwatch if you want BlipWatch to auto-open a browser window on startup.
Homebrew Maintainer Notes
BlipWatch publishes Homebrew formula updates through .github/workflows/homebrew-tap.yml.
Expected tap repository layout:
SternAnchor/homebrew-tap- Formula path:
Formula/blipwatch.rb
Required repository secret in this repository:
HOMEBREW_TAP_TOKEN: GitHub token with write access toSternAnchor/homebrew-tap
Release behavior:
- On published non-prerelease GitHub releases, the workflow downloads the npm tarball for that version, computes SHA256, renders the formula, and commits it to the tap.
- You can also run it manually with
workflow_dispatchand provide a specific version.
Local Development
Install dependencies:
npm installRun validation:
npm run typecheck
npm run lint
npm test
npm run build
npm pack --dry-runGitHub Actions runs validation on Ubuntu, macOS, and Windows for pull requests and commits to develop and main. Docker build validation runs on Ubuntu, and release jobs build multi-architecture Docker images for linux/amd64 and linux/arm64 when publishing is enabled.
Run the compiled server:
npm run build
npm startRun from an npm install:
npm install -g blipwatch
blipwatchRun with a desktop launcher window (no terminal management required):
npm run desktopThe launcher lets you start/stop the server and open the dashboard with one click.
Run in development mode:
npm run devRun with debug logging:
npm run debugDebug mode sets LOG_LEVEL=debug and logs configuration, UDP receive activity, decode results, render updates, replay capture, HTTP requests, startup, and shutdown.
Simulator
BlipWatch includes a small UDP simulator for development without radar hardware. It sends deterministic placeholder radar packets to the configured UDP host and port.
SIM_RADAR_HOST=127.0.0.1 \
SIM_RADAR_PORT=6678 \
SIM_PACKET_COUNT=5 \
SIM_PACKET_INTERVAL_MS=1000 \
npm run simulate:radarThe simulator logs JSON events for start, packet send, and finish. The placeholder packet format is for local development only and is not the real HALO wire format.
Target Intelligence and Raw Recordings
BlipWatch includes a foundation for target intelligence without claiming certified ARPA/MARPA behavior. Targets are normalized as RadarTarget records with ais, blipwatch-detected, halo-native, or manual sources, new, tracking, or lost status, confirmation state, name, bearing, range, confidence, and timestamps. Current production target ingestion is intentionally conservative: native HALO/MARPA target packet decoding is not implemented until MARPA-active captures prove the packet shape.
The dashboard includes a Targets panel with inline rename, confirm/unconfirm, delete, refresh, and a toggleable radar overlay. Target changes are also streamed over /api/radar/stream as reason: "target" with a targetEvent payload for created, updated, renamed, confirmed, unconfirmed, deleted, and lost events.
Target APIs:
| Method | Path | Purpose |
| --- | --- | --- |
| GET | /api/targets | List targets with target-manager status counters. |
| GET | /api/targets/:id | Inspect one target. |
| PATCH | /api/targets/:id | Rename a target with {"name":"..."} or clear it with {"name":null}. |
| POST | /api/targets/:id/confirm | Mark a target confirmed. |
| POST | /api/targets/:id/unconfirm | Clear confirmation. |
| DELETE | /api/targets/:id | Delete/dismiss a target. |
Raw decoded-spoke recordings use metadata.json plus spokes.ndjson under RAW_RECORDING_DIRECTORY (captures/recordings by default). Each spoke line stores bearing, range, sample count, max intensity, timestamps, and base64 intensity samples. Recordings are intended for decoder/render/replay development and should be reviewed before sharing because they can reveal local vessel/marina context.
Recording and replay APIs:
| Method | Path | Purpose |
| --- | --- | --- |
| GET | /api/recordings/status | Current recording store counters and active recording id. |
| POST | /api/recordings/start | Start writing decoded spokes. |
| POST | /api/recordings/stop | Stop the active recording. |
| GET | /api/recordings | List recording inspections. |
| GET | /api/recordings/:id | Inspect metadata and file health. |
| GET | /api/recordings/:id/download | Download spokes.ndjson. |
| DELETE | /api/recordings/:id | Delete a recording directory. |
| GET | /api/recordings/replay | Raw recording replay status. |
| POST | /api/recordings/:id/replay | Replay a recording through renderer/replay capture. Optional body: {"speed":2,"loop":false}. |
| POST | /api/recordings/replay/playback | Control raw replay with {"action":"pause"}, resume, or stop. |
/api/radar/status includes targets, recording, and rawReplay diagnostics alongside receiver, decoder, renderer, control, process, streaming, and replay status. The dashboard surfaces active recording and raw replay progress in the status grid.
The experimental BlipWatch-side detection scaffold in src/targets/detection-experiment.ts can threshold bright spoke samples and form coarse bearing/range clusters for future research. It is not connected to production target management yet, does not estimate motion, and must not be treated as collision-avoidance logic.
For native HALO target work, use the repeatable capture workflow in HALO Target and MARPA Packet Investigation. Capture baseline transmit, MARPA acquire, active tracking, target drop, standby, and transmit-resume windows, then compare packets.ndjson summaries with npm run inspect:packets.
Configuration
BlipWatch is configured through environment variables.
| Variable | Default | Description |
| --- | --- | --- |
| CALIBRATION_CAPTURE_ENABLED | false | Enables periodic calibration bundles for chartplotter/render comparison. |
| CALIBRATION_CAPTURE_DIRECTORY | captures/calibration | Directory where timestamped calibration bundles are written. CALIBRATION_CAPTURE_DIR is also accepted as a shorter alias. |
| CALIBRATION_CAPTURE_INTERVAL_MS | 10000 | Interval between calibration bundle captures when enabled. |
| CALIBRATION_CAPTURE_PACKET_LIMIT | 250 | Maximum number of recent raw UDP payloads to include in each calibration bundle. Set to 0 to disable packet payload capture. |
| HEADLESS | false | Disables desktop browser launch when true. BLIPWATCH_HEADLESS is also accepted. Docker sets this to true by default. |
| OPEN_BROWSER | true unless headless | Opens the dashboard in the local desktop browser after startup. BLIPWATCH_OPEN_BROWSER is also accepted. |
| PORT | 8080 | HTTP API port. |
| PORT_FALLBACK_ENABLED | true | When the configured HTTP port is busy, try sequential fallback ports. BLIPWATCH_PORT_FALLBACK_ENABLED is also accepted. |
| PORT_FALLBACK_MAX_ATTEMPTS | 5 | Number of sequential HTTP ports to try, so the default probes 8080 through 8084. BLIPWATCH_PORT_FALLBACK_MAX_ATTEMPTS is also accepted. |
| RADAR_DISCOVERY_ENABLED | true | Enables passive Navico/HALO report listening. |
| RADAR_BRIGHTNESS_SCALE | 100 | Percentage multiplier applied to radar return intensity before rendering. Increase for dim targets or decrease for saturated returns. |
| RADAR_DISPLAY_RANGE_METERS | auto | Render display range in meters. auto uses the decoded packet sweep range; set a value such as 463 to match a 1/4 NM chartplotter view. |
| RADAR_RENDER_PALETTE | chartplotter | Render color palette. Supported values are chartplotter, grayscale, and green. |
| RADAR_CONTROL_ENABLED | true | Enables active Navico/HALO wake or transmit commands. Set to false for receive-only operation. |
| RADAR_CONTROL_MODE | wake | Active control mode. Use wake to wake only or transmit to request transmit plus stay-alive. |
| RADAR_CONTROL_WAKE_HOST | 236.6.7.5 | IPv4 destination for the Navico wake command. |
| RADAR_CONTROL_WAKE_PORT | 6878 | UDP destination port for the Navico wake command. |
| RADAR_CONTROL_HOST | auto | IPv4 destination for transmit and stay-alive commands, or auto to use discovery before falling back. |
| RADAR_CONTROL_FALLBACK_HOST | 236.6.8.36 | Fallback IPv4 destination for transmit and stay-alive commands when RADAR_CONTROL_HOST=auto and no discovery command endpoint is available. |
| RADAR_CONTROL_PORT | 6516 | UDP destination port for transmit and stay-alive commands. |
| RADAR_CONTROL_STAY_ALIVE_INTERVAL_MS | 1000 | Interval between repeated control cycles while active control is enabled. |
| RADAR_INTERFACE | auto | Local interface address used for UDP radar packet binding. auto logs a preferred interface but binds to 0.0.0.0, joins multicast on active IPv4 interfaces, and periodically reconciles memberships so wired-to-Wi-Fi handoffs continue without restart. |
| RADAR_MULTICAST_GROUPS | 236.6.7.8 | Comma-separated IPv4 multicast groups for radar image/spoke reception. |
| RADAR_REPORT_MULTICAST_GROUP | 236.6.7.5 | IPv4 multicast group used for passive Navico/HALO report discovery. |
| RADAR_REPORT_UDP_PORT | 6878 | UDP port used for passive Navico/HALO report discovery. |
| RADAR_TARGET_EXPANSION | 100 | Percentage multiplier for rendered target footprint size. Increase to make returns easier to see on high-resolution displays. |
| RADAR_TARGET_PERSISTENCE_MS | 2000 | Time a radar return stays at full rendered intensity before fading begins. |
| RADAR_TARGET_FADE_MS | 4000 | Duration of the linear fade after the persistence window. |
| RADAR_TARGET_MAX_AGE_MS | 7000 | Maximum age for a rendered return before it is removed. |
| RADAR_UDP_PORT | 6678 | UDP port used for radar packet reception. |
| RAW_RECORDING_DIRECTORY | captures/recordings | Directory where raw decoded-spoke recordings are stored. RAW_RECORDING_DIR is also accepted as a shorter alias. |
| IMAGE_SIZE | 1024 | Width and height, in pixels, of the rendered radar image. |
| REPLAY_RETENTION_SECONDS | 300 | In-memory replay retention window. |
| REPLAY_FRAME_INTERVAL_MS | 1000 | Minimum interval between captured replay frames. |
| TARGET_TRACKING_ENABLED | true | Enables the target abstraction and lifecycle manager. Native target decoding and detection sources are added separately. |
| TARGET_LOST_TIMEOUT_SECONDS | 10 | Time since last observation before an active target is marked lost. |
| LOG_LEVEL | info | Logging verbosity. Use debug for packet, decode, render, replay, and request diagnostics. |
Example:
PORT=8080 \
RADAR_DISCOVERY_ENABLED=true \
RADAR_INTERFACE=auto \
RADAR_MULTICAST_GROUPS=236.6.7.8 \
RADAR_REPORT_MULTICAST_GROUP=236.6.7.5 \
RADAR_REPORT_UDP_PORT=6878 \
RADAR_RENDER_PALETTE=chartplotter \
RADAR_BRIGHTNESS_SCALE=100 \
RADAR_TARGET_EXPANSION=100 \
RADAR_TARGET_PERSISTENCE_MS=2000 \
RADAR_TARGET_FADE_MS=4000 \
RADAR_TARGET_MAX_AGE_MS=7000 \
RADAR_UDP_PORT=6678 \
IMAGE_SIZE=1024 \
REPLAY_RETENTION_SECONDS=300 \
REPLAY_FRAME_INTERVAL_MS=1000 \
LOG_LEVEL=info \
npm startHTTP API
GET /
Returns the browser dashboard with the current radar image, live diagnostics, packet counters, multicast groups, transmit/standby controls, clear screen control, advanced radar control inputs, replay controls, next actions, and raw /api/radar/status JSON. The dashboard refreshes status, replay metadata, and imagery automatically.
The replay panel supports returning to live mode, pausing on the newest replay frame, resuming replay playback state, scrubbing recent frames with the timeline, jumping to a timestamp, and selecting 1x, 2x, 5x, or 10x playback speed. In replay mode the main image uses /api/radar/replay/frame; in live mode it returns to /api/radar/latest.png.
The advanced controls panel exposes gain, sea clutter, rain clutter, and range controls through the same /api/radar/control/settings endpoint used by API clients. These controls are disabled if RADAR_CONTROL_ENABLED=false prevents the active command socket from starting. The dashboard range control shows unit and current range side-by-side, then steps through operator-friendly Imperial or Metric preset distances with plus/minus buttons that immediately send rangeMeters to the API. Standby, transmit, tuning, and range commands are active hardware commands. Clear Screen only resets BlipWatch's local rendered reflections.
GET /api/health
Returns service health and basic renderer/replay state.
{
"ok": true,
"service": "blipwatch"
}GET /api/radar/latest.png
Returns the latest rendered radar image.
- Content type:
image/png - Cache policy:
no-store
Before radar data arrives, this returns a valid empty image.
GET /api/radar/latest.json
Returns latest render metadata.
{
"imageSize": 1024,
"lastFrameAt": "2026-06-07T00:00:00.000Z",
"lastSpokeAt": "2026-06-07T00:00:00.000Z",
"maxIntensity": 255,
"renderState": "ready",
"spokeCount": 1
}When no radar data has arrived, renderState is empty and timestamps are null.
GET /api/radar/status
Returns hardware-focused discovery, receiver, decoder, and renderer diagnostics.
{
"diagnostics": {
"phase": "receiving-and-rendering",
"summary": "Radar spokes are decoding and rendering.",
"nextActions": ["Open /api/radar/latest.png or /api/radar/latest.json to inspect current rendered imagery."]
},
"discovery": {
"enabled": true,
"running": true,
"reportsReceived": 1,
"lastReportAt": "2026-06-07T00:00:00.000Z",
"lastReportSource": "192.0.2.11:6878",
"multicastGroup": "236.6.7.5",
"boundInterface": "0.0.0.0",
"multicastInterface": "192.168.15.188",
"udpPort": 6878,
"radar": {
"reportType": "0x01",
"command": "0xc4",
"status": "0x01",
"statusName": "standby",
"sourceAddress": "192.0.2.11",
"sourcePort": 6878,
"commandEndpoint": "236.6.7.10:6680",
"dataEndpoint": "236.6.7.8:6678",
"reportEndpoint": "236.6.7.9:6679",
"model": "HALO",
"serial": "123456",
"name": "HALO",
"firstSeenAt": "2026-06-07T00:00:00.000Z",
"lastSeenAt": "2026-06-07T00:00:00.000Z"
}
},
"receiver": {
"running": true,
"packetsReceived": 1,
"lastPacketAt": "2026-06-07T00:00:00.000Z",
"lastSourceAddress": "192.0.2.10:6678",
"multicastGroups": ["236.6.7.8"],
"boundInterface": "0.0.0.0",
"multicastInterface": "192.168.15.188",
"udpPort": 6678
},
"decoder": {
"packetsDecoded": 1,
"packetsRejected": 0,
"lastDecodedSpokeAt": "2026-06-07T00:00:00.000Z"
},
"renderer": {
"activePixelCount": 128,
"imageAvailable": true,
"imageSize": 1024,
"lastRenderedImageAt": "2026-06-07T00:00:00.000Z",
"lastSpokeAt": "2026-06-07T00:00:00.000Z",
"maxIntensity": 255,
"radarBrightnessScale": 100,
"radarRenderPalette": "chartplotter",
"renderState": "ready",
"spokeCount": 1,
"targetExpansion": 100,
"targetMaxAgeMs": 15000
},
"replay": {
"frameCount": 10,
"frameIntervalMs": 1000,
"newestFrameAt": "2026-06-07T00:00:00.000Z",
"oldestFrameAt": "2026-06-07T00:00:00.000Z",
"retentionSeconds": 300,
"totalBytes": 40960
},
"process": {
"uptimeSeconds": 120,
"memory": {
"rss": 67108864,
"heapTotal": 18874368,
"heapUsed": 10485760,
"external": 2097152,
"arrayBuffers": 1048576
}
},
"streaming": {
"clientsConnected": 1,
"lastClientConnectedAt": "2026-06-07T00:00:00.000Z",
"lastMessageAt": "2026-06-07T00:00:01.000Z",
"messagesSent": 10,
"totalClientsConnected": 2,
"updatesDropped": 1
},
"control": {
"enabled": true,
"running": true,
"mode": "transmit",
"desiredState": "transmit",
"observedState": "standby",
"observedStateAt": "2026-06-07T00:00:00.000Z",
"observedStateSource": "report",
"commandTarget": "236.6.7.10:6680",
"commandTargetSource": "discovered",
"wakeTarget": "236.6.7.5:6878",
"commandsSent": 3,
"lastCommandAt": "2026-06-07T00:00:00.000Z",
"lastCommandName": "stay-alive-a",
"lastRequestAt": "2026-06-07T00:00:00.000Z",
"lastError": null,
"stayAliveIntervalMs": 1000
}
}This endpoint is intended for hardware testing and troubleshooting. It helps confirm whether BlipWatch is receiving UDP packets, decoding radar spokes, and rendering current imagery.
GET /api/radar/stream
Opens a WebSocket stream for live radar notifications. The server sends an initial radar.snapshot message when a client connects, then throttled radar.update messages when a replay frame is captured or control/replay state changes.
Messages include current status, renderer metadata, replay metadata, and image URLs:
{
"type": "radar.update",
"timestamp": "2026-06-07T00:00:01.000Z",
"reason": "frame",
"image": {
"latestUrl": "/api/radar/latest.png",
"replayFrameAt": "2026-06-07T00:00:01.000Z",
"replayFrameUrl": "/api/radar/replay/frame?at=2026-06-07T00%3A00%3A01.000Z"
},
"renderer": {
"imageSize": 1024,
"renderState": "ready"
},
"replay": {
"frameCount": 10
},
"status": {
"diagnostics": {
"phase": "receiving-and-rendering"
}
}
}The stream applies lightweight throttling and skips clients with excessive buffered data. Connection counts, messages sent, and dropped update counts are exposed through /api/radar/status.streaming.
GET /api/radar/control/settings
Returns radar tuning capabilities and the latest requested tuning state.
{
"capabilities": {
"gain": {
"supported": true,
"reason": null
},
"seaClutter": {
"supported": true,
"reason": null
},
"rainClutter": {
"supported": true,
"reason": null
},
"range": {
"supported": true,
"reason": null
}
},
"tuning": {
"gain": {
"mode": "auto",
"value": null,
"lastRequestAt": null,
"lastError": null
},
"range": {
"rangeMeters": null,
"lastRequestAt": null,
"lastError": null
}
}
}POST /api/radar/control/settings
Validates, sends, and records a requested tuning control change from the API or dashboard advanced controls panel. Radar control must be enabled and running. Range values must be between 50 and 72704 meters. The dashboard may display range presets in feet/nautical miles or meters/kilometers, but API clients should continue sending integer rangeMeters.
Successful responses return 200 with the updated tuning status. If the control socket is unavailable or a UDP send fails, the endpoint returns 500 with radar_control_setting_failed.
Examples:
curl -X POST http://localhost:8080/api/radar/control/settings \
-H 'content-type: application/json' \
-d '{"setting":"gain","mode":"manual","value":42}'
curl -X POST http://localhost:8080/api/radar/control/settings \
-H 'content-type: application/json' \
-d '{"setting":"range","rangeMeters":463}'POST /api/radar/clear
Clears BlipWatch's current rendered radar image and publishes a stream update so connected dashboards refresh. This does not send a hardware command and does not change radar transmit, standby, gain, clutter, or range state.
curl -X POST http://localhost:8080/api/radar/clearGET /api/radar/replay
Returns replay buffer metadata.
{
"frameCount": 1,
"frameIntervalMs": 1000,
"newestFrameAt": "2026-06-07T00:00:00.000Z",
"oldestFrameAt": "2026-06-07T00:00:00.000Z",
"playback": {
"currentFrameAt": null,
"mode": "live",
"requestedAt": null,
"speed": 1,
"status": "live",
"updatedAt": "2026-06-07T00:00:00.000Z"
},
"retentionSeconds": 300
}GET /api/radar/replay/frames
Returns available replay frame metadata.
Optional query parameters:
from: ISO-8601 timestamp for the earliest frame to include.to: ISO-8601 timestamp for the latest frame to include.limit: positive integer limiting the response to the newest matching frames.
{
"frames": [
{
"capturedAt": "2026-06-07T00:00:00.000Z",
"metadata": {
"imageSize": 1024,
"renderState": "ready"
},
"sizeBytes": 4096
}
]
}GET /api/radar/replay/frame?at=<timestamp>
Returns the closest replay frame to the requested timestamp.
- Query parameter:
at, an ISO-8601 timestamp - Content type:
image/png - Response header:
x-blipwatch-frame-at
Error responses:
400whenatis missing404when no replay frame is available
GET /api/radar/replay/playback
Returns the current replay playback state.
{
"currentFrameAt": "2026-06-07T00:00:00.000Z",
"mode": "replay",
"requestedAt": "2026-06-07T00:00:00.000Z",
"speed": 5,
"status": "paused",
"updatedAt": "2026-06-07T00:00:01.000Z"
}POST /api/radar/replay/playback
Updates replay playback state for clients that need pause, resume, jump, scrub, and return-to-live behavior.
Supported JSON fields:
action: one ofpause,resume,jump,scrub, orlive.at: ISO-8601 timestamp. Required forjumpandscrub.speed: optional playback speed, one of1,2,5, or10.
Example:
curl -X POST http://localhost:8080/api/radar/replay/playback \
-H 'content-type: application/json' \
-d '{"action":"jump","at":"2026-06-07T00:00:00.000Z","speed":5}'Docker
Build the local image:
npm run docker:buildRun with standard bridge networking:
docker run --rm \
-p 8080:8080/tcp \
-p 6878:6878/udp \
-p 6678:6678/udp \
-e PORT=8080 \
-e HEADLESS=true \
-e RADAR_DISCOVERY_ENABLED=true \
-e RADAR_INTERFACE=auto \
-e RADAR_MULTICAST_GROUPS=236.6.7.8 \
-e RADAR_REPORT_MULTICAST_GROUP=236.6.7.5 \
-e RADAR_REPORT_UDP_PORT=6878 \
-e RADAR_UDP_PORT=6678 \
blipwatch:localFor onboard hardware directly connected to a radar Ethernet network, host networking is often the simplest way to receive UDP traffic:
docker run --rm --network host \
-e PORT=8080 \
-e HEADLESS=true \
-e RADAR_DISCOVERY_ENABLED=true \
-e RADAR_INTERFACE=auto \
-e RADAR_MULTICAST_GROUPS=236.6.7.8 \
-e RADAR_REPORT_MULTICAST_GROUP=236.6.7.5 \
-e RADAR_REPORT_UDP_PORT=6878 \
-e RADAR_UDP_PORT=6678 \
-e IMAGE_SIZE=1024 \
-e REPLAY_RETENTION_SECONDS=300 \
-e REPLAY_FRAME_INTERVAL_MS=1000 \
-e LOG_LEVEL=info \
ghcr.io/sternanchor/blipwatch:latestBuild a multi-architecture image for AMD64 and ARM64:
npm run docker:buildxThe CI/CD workflow publishes GHCR images for:
latestfor stablemainreleases- stable version tags from
main developfor preview releases- prerelease version tags from
develop - commit SHA tags from
develop
Raspberry Pi / Onboard Deployment
Use a Raspberry Pi or similar Linux host with Ethernet access to the radar network.
- Connect the host to the radar Ethernet network.
- Confirm the host can receive UDP traffic from the radar.
- Run the container with host networking.
- Open
http://<host>:8080/to confirm the server is running and inspect the current image/status.
Example:
docker run -d --name blipwatch --restart unless-stopped --network host \
-e PORT=8080 \
-e RADAR_INTERFACE=auto \
-e RADAR_MULTICAST_GROUPS=236.6.7.8 \
-e RADAR_REPORT_MULTICAST_GROUP=236.6.7.5 \
-e RADAR_REPORT_UDP_PORT=6878 \
-e RADAR_UDP_PORT=6678 \
-e LOG_LEVEL=info \
ghcr.io/sternanchor/blipwatch:latestUse LOG_LEVEL=debug during installation or troubleshooting to inspect UDP packet reception, decode failures, render updates, and replay capture.
Suggested Raspberry Pi tuning order:
- Start with
IMAGE_SIZE=1024,REPLAY_FRAME_INTERVAL_MS=1000, andREPLAY_RETENTION_SECONDS=300. - If
/api/radar/status.process.memory.rssgrows too quickly, reduce replay retention or increaseREPLAY_FRAME_INTERVAL_MS. - If rendering lags, lower
IMAGE_SIZEto768or512, then compareactivePixelCount,spokeCount, and profiler output. - Use
npm run profile:radaron the target host before and after tuning so changes have a repeatable baseline.
For systemd-style installs, run BlipWatch with a dedicated service user, set environment variables in an environment file, and prefer a wired interface on the radar network with VPNs disabled.
Release Process
BlipWatch uses Conventional Commits and semantic-release.
Examples:
feat: add live radar image endpoint
fix: handle malformed UDP radar packet
chore: add docker build workflow
docs: add raspberry pi deployment notesBranches:
developproduces preview prereleases, such as1.1.0-develop.1.mainproduces stable releases.
Release automation:
- Runs validation before publishing.
- Runs the
CI/CD / Validatecheck on pull requests intodevelopandmain; require this check in branch protection before allowing merges tomain. - Publishes the Node package to npm using Actions secret
NPM_TOKENwhen it is available to the workflow. - Publishes Docker images to GitHub Container Registry.
- Creates GitHub releases and release notes.
- Updates
CHANGELOG.md,package.json, andpackage-lock.jsonduring release commits.
Repository setup required for real publishing:
- Ensure
NPM_TOKENis available as a repository or org Actions secret for this repository. - Ensure npm package publishing permissions are configured.
- Ensure GHCR package permissions allow workflow publishing.
License
BlipWatch is licensed under the GNU General Public License v3.0. See LICENSE.
