npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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 blipwatch

Open 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 to SternAnchor/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_dispatch and provide a specific version.

Local Development

Install dependencies:

npm install

Run validation:

npm run typecheck
npm run lint
npm test
npm run build
npm pack --dry-run

GitHub 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 start

Run from an npm install:

npm install -g blipwatch
blipwatch

Run with a desktop launcher window (no terminal management required):

npm run desktop

The launcher lets you start/stop the server and open the dashboard with one click.

Run in development mode:

npm run dev

Run with debug logging:

npm run debug

Debug 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:radar

The 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 start

HTTP 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/clear

GET /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:

  • 400 when at is missing
  • 404 when 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 of pause, resume, jump, scrub, or live.
  • at: ISO-8601 timestamp. Required for jump and scrub.
  • speed: optional playback speed, one of 1, 2, 5, or 10.

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:build

Run 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:local

For 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:latest

Build a multi-architecture image for AMD64 and ARM64:

npm run docker:buildx

The CI/CD workflow publishes GHCR images for:

  • latest for stable main releases
  • stable version tags from main
  • develop for 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.

  1. Connect the host to the radar Ethernet network.
  2. Confirm the host can receive UDP traffic from the radar.
  3. Run the container with host networking.
  4. 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:latest

Use LOG_LEVEL=debug during installation or troubleshooting to inspect UDP packet reception, decode failures, render updates, and replay capture.

Suggested Raspberry Pi tuning order:

  1. Start with IMAGE_SIZE=1024, REPLAY_FRAME_INTERVAL_MS=1000, and REPLAY_RETENTION_SECONDS=300.
  2. If /api/radar/status.process.memory.rss grows too quickly, reduce replay retention or increase REPLAY_FRAME_INTERVAL_MS.
  3. If rendering lags, lower IMAGE_SIZE to 768 or 512, then compare activePixelCount, spokeCount, and profiler output.
  4. Use npm run profile:radar on 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 notes

Branches:

  • develop produces preview prereleases, such as 1.1.0-develop.1.
  • main produces stable releases.

Release automation:

  • Runs validation before publishing.
  • Runs the CI/CD / Validate check on pull requests into develop and main; require this check in branch protection before allowing merges to main.
  • Publishes the Node package to npm using Actions secret NPM_TOKEN when it is available to the workflow.
  • Publishes Docker images to GitHub Container Registry.
  • Creates GitHub releases and release notes.
  • Updates CHANGELOG.md, package.json, and package-lock.json during release commits.

Repository setup required for real publishing:

  • Ensure NPM_TOKEN is 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.