The Signal K server Radar API provides a unified interface for viewing and controlling marine radar equipment from any manufacturer. The API is (web)app-friendly: clients can build dynamic UIs that automatically adapt to any radar's capabilities without hardcoding support for specific brands or models.
This is version v3.1.0 of the API. The version will use semver for version updates.
Radar functionality is provided by "provider plugins" that handle the interaction with radar hardware and stream spoke data to connected clients.
Requests to the Radar API are made to HTTP REST endpoints rooted at /signalk/v2/api/vessels/self/radars or the Signal K websocket stream at /signalk/v1/stream.
Like signalk-server vis-a-vis the Signal K specification there is a reference implementation
for this API, which may very well remain the only implementation of the server side of the API,
at https://github.com/MarineYachtRadar/mayara-server. However, like Signal K itself, there is no
reason it needs to remain the only implementation. In particular it would be ultra cool if some
manufacturer of marine hardware would implement this API -- even though this is very unlikely.
This API uses a self-describing schema pattern that benefits both radar provider developers and client/chartplotter developers.
Build a single, adaptive UI that works with any radar—now and in the future—without hardcoding brand-specific logic.
How it works:
dataType: "number" → Slider with min/max/stepdataType: "enum" with descriptions → Dropdown or button groupdataType: "string" → Text input fielddataType: "button" → Action buttondataType: "sector" → Angle range selector (start/end angles)dataType: "zone" → Guard zone editor (angles + distances)dataType: "rect" → Rectangular exclusion zone (two corners + width)isReadOnly: true → Display-only labelExample: Rendering a Gain Control
$ curl -s http://10.56.0.1:6502/signalk/v2/api/vessels/self/radars/nav1034A/capabilities | jq '.controls.gain'
{
"category": "base",
"dataType": "number",
"description": "How sensitive the radar is to returning echoes",
"hasAuto": true,
"hasAutoAdjustable": false,
"id": 4,
"maxValue": 100.0,
"minValue": 0.0,
"name": "Gain",
"stepValue": 1.0
}
$ curl -s http://10.56.0.1:6502/signalk/v2/api/vessels/self/radars/nav1034A/controls/gain
{"auto":false,"value":58}
Your UI renders:
[Auto] [Manual]0 ----[58]---- 100 (disabled or hidden when mode=auto)Whether it's a Furuno DRS4D-NXT with 20, a Navico HALO with 40 controls or a basic radar with 5 controls, the same client code handles both.
Different manufacturers have vastly different hardware capabilities, control sets, value ranges, and operating modes. Instead of clients hardcoding knowledge about each model, your provider plugin declares what the radar can do:
| Category | Description | Examples |
|---|---|---|
base |
Available on all radars | power, range, gain, sea, rain |
targets |
Target tracking settings | targetExpansion, targetTrails |
guardZones |
Guard zone configuration | guardZone1, guardZone2 |
trails |
Trail display settings | trailsTime, clearTrails |
advanced |
Model-specific features | dopplerMode, beamSharpening, interferenceRejection |
installation |
Setup/configuration settings | antennaHeight, bearingAlignment, noTransmitSector1 |
info |
Read-only information | serialNumber, firmwareVersion, transmitTime |
Read-only information (serialNumber, firmwareVersion, operatingHours) is exposed as controls with isReadOnly: true. Some controls are dynamically read-only when a particular mode is set. This is handled with an optional allowed: <bool> field in the control value.
Some further considerations as how to show controls:
id field which may be used for ordering.advanced and especially the installation categories could be shown in a different panel.installation controls are typically configured once.power and range controls are used often and should be easy to be controlled.gain, sea and rain controls are usually represented graphically on a PPI window./signalk/v2/api/vessels/self/radars
├── GET → List all active radars
├── /interfaces
│ └── GET → List network interfaces and listener status
└── /{radar_id}
├── /capabilities GET → Get radar capabilities and control definitions
├── /controls
│ ├── GET → Get all control values
│ └── /{control_id}
│ ├── GET → Get single control value
│ └── PUT → Set single control value
├── /spokes → WebSocket (spoke data in binary format)
└── /targets
├── GET → List tracked targets
├── POST → Acquire target manually
└── /{target_id}
└── DELETE → Cancel target tracking
/signalk/v1/stream → WebSocket (control values and targets for all radars)
Retrieve all available radars with their current info:
HTTP GET "/signalk/v2/api/vessels/self/radars"
Response:
{
"nav1034A": {
"brand": "Navico",
"model": "HALO",
"name": "HALO 034A",
"radarIpAddress": "192.168.1.50",
"spokeDataUrl": "ws://192.168.1.100:8080/signalk/v2/api/vessels/self/radars/nav1034A/spokes",
"streamUrl": "ws://192.168.1.100:8080/signalk/v1/stream"
},
"nav1034B": {
"brand": "Navico",
"model": "HALO",
"name": "HALO 034B",
"radarIpAddress": "192.168.1.50",
"spokeDataUrl": "ws://192.168.1.100:8080/signalk/v2/api/vessels/self/radars/nav1034B/spokes",
"streamUrl": "ws://192.168.1.100:8080/signalk/v1/stream"
}
}
Check which network interfaces are available and which radar brands are listening:
HTTP GET "/signalk/v2/api/vessels/self/radars/interfaces"
Response:
{
"brands": ["Navico", "Furuno", "Raymarine"],
"interfaces": {
"en0": {
"status": "Ok",
"ip": "192.168.1.100",
"netmask": "255.255.255.0",
"listeners": {
"Navico": "Active",
"Furuno": "No match for 172.31.255.255",
"Raymarine": "Listening"
}
},
"en1": {
"status": "WirelessIgnored"
}
}
}
This endpoint is useful for diagnosing network configuration issues when radars are not being detected.
The capability manifest describes everything a radar can do. Clients should fetch this at the beginning of a session. The contents do not change during radar operation.
HTTP GET "/signalk/v2/api/vessels/self/radars/{radar_id}/capabilities"
Response:
{
"maxRange": 74080,
"minRange": 50,
"supportedRanges": [
50, 75, 100, 250, 500, 750, 1000, 1500, 2000, 3000, 4000, 6000, 8000, 12000,
16000, 24000, 36000, 48000, 64000, 74080
],
"spokesPerRevolution": 2048,
"maxSpokeLength": 1024,
"pixelValues": 16,
"hasDoppler": true,
"hasDualRadar": false,
"hasDualRange": true,
"hasSparseSpokes": false,
"noTransmitSectors": 2,
"controls": {
"gain": {
"id": 4,
"name": "Gain",
"description": "How sensitive the radar is to returning echoes",
"category": "base",
"dataType": "number",
"minValue": 0.0,
"maxValue": 100.0,
"stepValue": 1.0,
"hasAuto": true,
"hasAutoAdjustable": false
}
},
"legend": {
"lowReturn": 1,
"mediumReturn": 8,
"strongReturn": 13,
"targetBorder": 17,
"dopplerApproaching": 18,
"dopplerReceding": 19,
"historyStart": 20,
"pixelColors": 16,
"pixels": [
{ "type": "normal", "color": { "r": 0, "g": 0, "b": 0, "a": 0 } },
{ "type": "normal", "color": { "r": 0, "g": 0, "b": 51, "a": 255 } }
]
}
}
Capability fields:
hasDoppler - if true, the radar can detect boats or objects approaching or receding and emits separate pixel colors for these.hasDualRadar - if true, the physical radome reports itself as two independent radars that can be set to different ranges and modes. Currently only Navico 4G and HALO support this.hasDualRange - mutually exclusive with hasDualRadar, indicates a more limited form of supporting two ranges with one device.minRange and maxRange - define what ranges the radar supports (in meters).supportedRanges - list of all discrete range values the radar supports (in meters).maxSpokeLength and spokesPerRevolution - define how many pixels the radar produces each revolution.noTransmitSectors - how many sectors the radar can stop transmitting to avoid obstacles like masts.pixelValues - number of distinct pixel intensity values.hasSparseSpokes - if true, the radar produces fewer spokes per revolution than spokesPerRevolution indicates (see Spoke skipping).All spokes are sent with one byte per pixel. The legend explains what each byte value represents.
{
"lowReturn": 1,
"mediumReturn": 8,
"strongReturn": 13,
"dopplerApproaching": 18,
"dopplerReceding": 19,
"historyStart": 20,
"pixelColors": 16,
"pixels": [
{ "type": "normal", "color": "#00000000" },
{ "type": "normal", "color": "#0000ffff" },
{ "type": "dopplerApproaching", "color": "#ff00ffff" },
{ "type": "dopplerReceding", "color": "#00ff00ff" },
{ "type": "history", "color": "#454545ff" }
]
}
The lowReturn, mediumReturn, and strongReturn indicate offsets in the array, typically used for smoothing algorithms.
If the radar doesn't implement Doppler, the dopplerApproaching and dopplerReceding fields will be null. If the provider doesn't implement target trails, historyStart will be null.
There are two different ways that radars handle "dual" ranges.
Navico radars implement this by acting as if both radars are full independent, to the point where both radars use different ports and IP addresses. They can be seen to be dependent in that if you change some controls they also change on the other radar. The NoTransmitZones are examples of such controls. These radars therefore also show up as two radars in the API. As long as clients listen to updates to controls, which they should do anyway to be able to function in a setting where there is for instance a MFD device, they can assume that all controls can be set.
Furuno radars do this in a way where the second range shares as many control settings as possible. Currently there is no support for Furuno dual range yet and its impact on the API is unknown.
The controls object in capabilities lists all controls the radar supports. Control data types:
| dataType | Description |
|---|---|
| number | Numeric value with min/max/step |
| enum | Discrete set of values with descriptions |
| string | Text value |
| button | Action trigger (no value) |
| sector | Angle range (start/end) |
| zone | Guard zone (angles + distances) |
| rect | Rectangular exclusion zone |
{
"id": 47,
"name": "Transmit time",
"description": "How long the radar has been transmitting over its lifetime",
"category": "info",
"dataType": "number",
"isReadOnly": true,
"minValue": 0.0,
"maxValue": 3599996400.0,
"stepValue": 3600.0,
"units": "s"
}
The units field indicates the unit of measurement for the control value. A conforming server implementation sends only SI units to clients:
| Category | SI Unit | Abbreviation |
|---|---|---|
| Distance | Meters | m |
| Speed | Meters per second | m/s |
| Angle | Radians | rad |
| Rotational speed | Radians per second | rad/s |
| Duration | Seconds | s |
Note how in the above example the server has converted a value in hours (3600 seconds) to seconds to conform to the above, but the client can convert the value back to hours for representation to a human.
A conforming API server will allow the following units to be specified when receiving values from a client:
| Category | Unit | Abbreviation |
|---|---|---|
| Distance | Meters | m |
| Distance | Kilometers | km |
| Distance | Nautical miles | nm |
| Speed | Meters per second | m/s |
| Speed | Knots | kn |
| Angle | Radians | rad |
| Angle | Degrees | deg |
| Rotational speed | Radians per second | rad/s |
| Rotational speed | Rotations/minute | rpm |
| Duration | Seconds | s |
| Duration | Minutes | min |
| Duration | Hours | h |
{
"id": 0,
"name": "Power",
"description": "Radar operational state",
"category": "base",
"dataType": "enum",
"minValue": 0.0,
"maxValue": 3.0,
"stepValue": 1.0,
"descriptions": {
"0": "Off",
"1": "Standby",
"2": "Transmit",
"3": "Preparing"
},
"validValues": [1, 2]
}
The validValues array indicates which values can be set by clients. The power control guarantees that at least these values can be set across all radars: 1 (Standby) and 2 (Transmit).
{
"id": 53,
"name": "Custom name",
"description": "User defined name for the radar",
"category": "advanced",
"dataType": "string"
}
A button triggers an action without needing a value:
{
"id": 15,
"name": "Clear trails",
"description": "Clear target trails",
"category": "trails",
"dataType": "button"
}
{
"id": 35,
"name": "No Transmit sector",
"description": "First no-transmit sector",
"category": "installation",
"dataType": "sector",
"hasEnabled": true,
"minValue": -3.141592653589793,
"maxValue": 3.141592653589793,
"stepValue": 0.0017453292519943296,
"units": "rad"
}
A sector defines a start and end angle from -π to +π radians, plus an enabled flag. The value for start is transmitted in value and the end in endValue.
$ curl -s http://localhost:6502/signalk/v2/api/vessels/self/radars/nav1034A/controls/noTransmitSector1
{"enabled":true,"value":-1.5533,"endValue":-1.2217}
{
"id": 16,
"name": "Guard zone",
"description": "First guard zone for target detection",
"category": "guardZones",
"dataType": "zone",
"hasEnabled": true,
"minValue": -3.141592653589793,
"maxValue": 3.141592653589793,
"maxDistance": 100000.0,
"units": "rad"
}
A zone defines five attributes: start angle, end angle, start distance, end distance, and enabled.
$ curl -s http://localhost:6502/signalk/v2/api/vessels/self/radars/nav1034A/controls/guardZone1
{"enabled":true,"value":-0.5585,"endValue":1.7104,"startDistance":100.0,"endDistance":232.0}
{
"id": 60,
"name": "Exclusion zone",
"description": "Rectangular exclusion zone",
"category": "guardZones",
"dataType": "rect",
"hasEnabled": true,
"maxValue": 100000.0
}
A rect defines a rectangular zone using two corners and a perpendicular width. The corners (x1, y1) and (x2, y2) define one edge of the rectangle in meters relative to the radar position (positive X is starboard, positive Y is ahead). The width extends perpendicular to this edge.
$ curl -s http://localhost:6502/signalk/v2/api/vessels/self/radars/nav1034A/controls/exclusionZone1
{"enabled":true,"x1":-50.0,"y1":100.0,"x2":50.0,"y2":100.0,"width":200.0}
Controlling the radar can be done via HTTP REST requests or via the stream websocket.
HTTP GET "/signalk/v2/api/vessels/self/radars/{radar_id}/controls"
Response:
{
"gain": { "auto": false, "value": 50 },
"sea": { "auto": true, "autoValue": 25, "value": 30 },
"range": { "value": 3000 }
}
HTTP GET "/signalk/v2/api/vessels/self/radars/{radar_id}/controls/{control_id}"
Response:
{ "auto": false, "value": 50 }
HTTP PUT "/signalk/v2/api/vessels/self/radars/{radar_id}/controls/{control_id}"
Simple numeric control:
{ "value": 75 }
Control with auto mode:
{ "auto": false, "value": 75 }
or just change auto mode:
{ "auto": true }
Control with auto adjustment (e.g., Sea on HALO):
When in auto mode, some controls accept an adjustment value:
{ "auto": true, "autoValue": -20 }
Sector control:
{ "enabled": true, "value": -1.5533, "endValue": -1.2217 }
Zone control:
{
"enabled": true,
"value": -0.5585,
"endValue": 1.7104,
"startDistance": 100.0,
"endDistance": 500.0
}
Button control:
For buttons, send an empty body or {} - the PUT request itself triggers the action.
Control values contain different fields depending on the control's dataType (defined in the capability schema).
Common fields:
| Field | Description |
|---|---|
value |
The control value (numeric or string) (if dataType is not rect) |
auto |
Whether automatic mode is enabled (if hasAuto is true) |
autoValue |
Adjustment when auto=true (if hasAutoAdjustable is true) |
timestamp |
ISO 8601 timestamp when value was last changed |
dataType-specific fields:
| Field | dataType | Description |
|---|---|---|
enabled |
sector, zone, rect | Whether the control is enabled |
endValue |
sector, zone | End angle (radians) |
startDistance |
zone | Inner radius (meters) |
endDistance |
zone | Outer radius (meters) |
x1 |
rect | First corner X (meters, starboard +) |
y1 |
rect | First corner Y (meters, ahead +) |
x2 |
rect | Second corner X (meters, starboard +) |
y2 |
rect | Second corner Y (meters, ahead +) |
width |
rect | Perpendicular width (meters) |
The Radar API defines ARPA (Automatic Radar Plotting Aid) target tracking with CPA/TCPA calculations and SignalK notification integration.
mayara-server fully supports both ARPA and MARPA, but this is an optional part of the API. When a server does not support it it shall
return HTTP status 501.
If the radar is a dual-radar device then mayara-server has a CLI option --merge-targets, when this
is used targets will be shared between both ranges and move from one radar to another.
HTTP GET "/signalk/v2/api/vessels/self/radars/{id}/targets"
Response:
[
{
"id": 1,
"status": "tracking",
"position": {
"bearing": 0.789,
"distance": 1852,
"latitude": 52.3702,
"longitude": 4.8952
},
"motion": {
"course": 3.14159,
"speed": 3.34
},
"danger": {
"cpa": 150,
"tcpa": 324
},
"acquisition": "auto",
"sourceZone": 1,
"firstSeen": "2025-01-15T10:25:00Z",
"lastSeen": "2025-01-15T10:30:00Z"
}
]
Units: All distances are in meters. All angles (bearing, course) are in radians [0, 2π). Speed is in m/s. Time values (tcpa) are in seconds.
Optional fields: Sub-structures are omitted when data is not yet known or not applicable:
motion: Omitted when motion is not yet computed (target still acquiring). Present with speed: 0 and course: 0 for confirmed stationary targets (buoys, anchored vessels).danger: Omitted when vessels are diverging (no CPA exists) or own-ship motion unavailableposition.latitude/longitude: Omitted when radar position is unavailablesourceZone: Omitted for manually acquired targets or Doppler-detected targetsHTTP POST "/signalk/v2/api/vessels/self/radars/{id}/targets"
Request body:
{
"bearing": 0.785,
"distance": 2000
}
HTTP DELETE "/signalk/v2/api/vessels/self/radars/{id}/targets/{targetId}"
There are two types of websocket:
The JSON data websocket provides real-time control value updates for all radars via the standard Signal K stream.
The URI is found in the radar response as streamUrl or can be constructed as:
ws://{host}:{port}/signalk/v1/stream
This websocket endpoint works identical to a Signal K stream, as documented in https://signalk.org/specification/1.5.0/doc/streaming_api.html
In short:
subscribe=none can be used to start without any subscriptions and sendCachedValues=false to disable sending all currently cached values.The recommended way of connecting is to either send subscribe=none and then a subscribe to all controls, as in the example below, with a policy of instant. The number of updates after the
initial cache dump is low, about 2 messages per second.
"subscribe": [
{
"path": "radars.*.controls.*",
"period": 1000
},
]
To receive real-time ARPA target updates, subscribe to the targets path:
{
"subscribe": [
{
"path": "radars.*.targets.*",
"policy": "instant"
}
]
}
You can subscribe to both controls and targets simultaneously:
{
"subscribe": [
{ "path": "radars.*.controls.*", "period": 1000 },
{ "path": "radars.*.targets.*", "policy": "instant" }
]
}
Example of received meta-data:
{
"updates": [
{
"$source": "mayara",
"timestamp": "2026-02-23T18:15:26.409454084Z",
"meta": [
{
"path": "radars.nav1034A.controls.guardZone1",
"value": {
"id": 13,
"name": "Guard zone",
"description": "First guard zone for target detection",
"category": "guardZones",
"dataType": "zone",
"hasEnabled": true,
"minValue": -3.141592653589793,
"maxValue": 3.141592653589793,
"units": "rad",
"maxDistance": 100000.0
}
},
{
"path": "radars.nav1034A.controls.firmwareVersion",
"value": {
"id": 48,
"name": "Firmware version",
"description": "Version of the radar firmware",
"category": "info",
"dataType": "string",
"isReadOnly": true
}
}
]
}
]
}
Example of received data:
{
"updates": [
{
"$source": "mayara",
"values": [
{
"path": "radars.nav1034A.controls.spokes",
"value": { "value": 2048 }
}
]
}
]
}
Example of setting a control:
{
"path": "radars.nav1034A.controls.guardZone1",
"value": {
"value": 0.735,
"endValue": 3.1415,
"startDistance": 0,
"endDistance": 500,
"enabled": true
}
}
Target updates are sent whenever a target's position, motion, or status changes:
{
"updates": [
{
"$source": "mayara",
"timestamp": "2025-01-15T10:30:00Z",
"values": [
{
"path": "radars.nav1034A.targets.1",
"value": {
"id": 1,
"status": "tracking",
"position": {
"bearing": 0.789,
"distance": 1852,
"latitude": 52.3702,
"longitude": 4.8952
},
"motion": {
"course": 3.14159,
"speed": 3.34
},
"danger": {
"cpa": 150,
"tcpa": 324
},
"acquisition": "auto",
"sourceZone": 1,
"firstSeen": "2025-01-15T10:25:00Z",
"lastSeen": "2025-01-15T10:30:00Z"
}
}
]
}
]
}
Targets are created either automatically (ARPA) or manually (MARPA, via a REST or stream message.)
In all cases the targets go through the following states: acquiring -> tracking -> lost.
When a target is deleted (either because it has been in status lost for a while or a client explicitly deletes it), a final null value is sent:
{
"updates": [
{
"$source": "mayara",
"timestamp": "2025-01-15T10:32:00Z",
"values": [
{
"path": "radars.nav1034A.targets.1",
"value": null
}
]
}
]
}
Because radars can produce up to 4 megabytes of data per rotation, this data is transmitted on a separate websocket per radar and is in a binary format. The data is encoded using Protocol Buffers (protobuf), Google's language-neutral binary serialization format. Protobuf provides compact encoding and fast parsing, with official implementations available for most programming languages including JavaScript, Python, Java, C++, Go, and Rust.
The message schema is stable and will not change within a major version (per semver):
syntax = "proto3";
/*
* The data stream coming from a radar is a series of spokes.
* The number of spokes per revolution is different for each type of
* radar and can be found in the capabilities at
* .../v2/api/vessels/self/radars/{id}/capabilities as 'spokesPerRevolution'.
* The maximum length of each spoke is also defined there, as well as the legend that provides
* a lookup table for each byte of data in the spoke.
*
* The angle and bearing fields below are in terms of spokes, so
* range from [0..spokesPerRevolution>.
*
* Angle is a mandatory field and tells you the rotation of the spoke
* relative to the front of the boat, going clockwise. 0 means directly
* ahead, spokesPerRevolution / 4 is to starboard, spokesPerRevolution / 2 is directly astern, etc.
*
* Bearing, if set, means that either the radar or the radar server has
* enriched the data with a true bearing, e.g. 0 is directly North,
* spokesPerRevolution / 4 is directly West, spokesPerRevolution / 2 is South, etc.
*
* Likewise, time and lat/lon indicate the best effort when the spoke
* was generated, and the lat/lon of the radar at the time of generation.
*
*/
message RadarMessage {
message Spoke {
uint32 angle = 1; // [0..spokesPerRevolution>, angle from bow
optional uint32 bearing = 2; // [0..spokesPerRevolution>, offset from True North
uint32 range = 3; // [meters], range in meters of the last pixel in data
optional uint64 time = 4; // [millis since UNIX epoch] Time when spoke was generated or received
optional double lat = 6; // Location of radar at time of generation
optional double lon = 7; // Location of radar at time of generation
bytes data = 5;
}
repeated Spoke spokes = 2;
}
The URL is found in the radars REST response as spokeDataUrl or can be constructed as:
/signalk/v2/api/vessels/self/radars/{radar_id}/spokes
This a Javascript example how to set up the connection to receive spokes:
// Fetch radars
const response = await fetch('/signalk/v2/api/vessels/self/radars/')
const data = await response.json()
// Choose a radar_id from the returned radars
const radarId = Object.keys(data)[0]
const radar = data[radarId]
// Connect to spoke data stream
const wsUrl =
radar.spokeDataUrl ??
`ws://${location.host}/signalk/v2/api/vessels/self/radars/${radarId}/spokes`
const socket = new WebSocket(wsUrl)
socket.binaryType = 'arraybuffer'
socket.onmessage = (event) => {
const spokeData = new Uint8Array(event.data)
// Process binary spoke data...
}
Every spoke contains spoke_len bytes. The radar API always uses one byte per pixel, with every byte representing a value explained by the legend contained in the capabilities.
The legend provides a lookup table mapping each byte value to its meaning and suggested display color:
pixelColors - 1: Normal radar returns, ranging from no echo (0) to strongest echo. The lowReturn, mediumReturn, and strongReturn fields indicate thresholds within this range, useful for smoothing or color gradient algorithms.targetBorder: Indicates the edge of a tracked ARPA target.dopplerApproaching: Object moving toward the radar (requires Doppler-capable radar).dopplerReceding: Object moving away from the radar (requires Doppler-capable radar).historyStart onward: Historical trail data showing where targets were in previous rotations.The pixels array provides the complete mapping from byte value to RGBA color. Clients can use this directly for rendering, or implement their own color scheme based on the semantic pixel types (normal, targetBorder, dopplerApproaching, dopplerReceding, history).
If the radar doesn't support a feature, the corresponding legend field will be absent or null (e.g., dopplerApproaching and dopplerReceding are absent for non-Doppler radars).
In a later API release it is likely that the legend will be expanded to contain color mappings for different palettes.
Some radars have a high value for spokesPerRevolution but actually only produce fewer spokes
per each revolution. This is true for Furuno radars but not the other
supported radars from Garmin, Navico and Raymarine. The Furuno radars set hasSparseSpokes in
the capabilities struct to true.
A conforming GUI must allow for this and either implement some way to expand missing spokes or to reconsider the width of spokes to be from the angle/bearing from the received spoke to the previously received spoke.
A typical value for Furuno is to have spokesPerRevolution = 8192 but the actual # of spokes
will be ~ 900. Weirdly enough it is not a "round" figure like 1440, 2048, 512 or 250 like the
other radars.
interface RadarsResponse {
version: string
radars: Record<string, RadarInfo>
}
interface RadarInfo {
name: string
brand: string
model?: string
radarIpAddress: string
spokeDataUrl: string
streamUrl: string
}
interface Capabilities {
maxRange: number
minRange: number
supportedRanges: number[]
spokesPerRevolution: number
maxSpokeLength: number
pixelValues: number
hasDoppler: boolean
hasDualRadar: boolean
hasDualRange: boolean
hasSparseSpokes: boolean
noTransmitSectors: number
controls: Record<string, ControlDefinition>
legend: Legend
}
interface ControlDefinition {
id: number
name: string
description: string
category:
| 'base'
| 'targets'
| 'guardZones'
| 'trails'
| 'advanced'
| 'installation'
| 'info'
dataType: 'number' | 'enum' | 'string' | 'button' | 'sector' | 'zone' | 'rect'
isReadOnly?: boolean
hasEnabled?: boolean
minValue?: number
maxValue?: number
stepValue?: number
maxDistance?: number
units?: 'm' | 'm/s' | 'rad' | 'rad/s' | 's'
descriptions?: Record<string, string> // For enum types
validValues?: number[] // For enum types
hasAuto?: boolean
hasAutoAdjustable?: boolean
autoAdjustMinValue?: number
autoAdjustMaxValue?: number
}
interface ControlValue {
value?: number | string
units?:
| 'm'
| 'km'
| 'nm'
| 'm/s'
| 'kn'
| 'rad'
| 'deg'
| 'rad/s'
| 'rpm'
| 's'
| 'min'
| 'h'
auto?: boolean
autoValue?: number
enabled?: boolean
endValue?: number // End angle for sectors/zones (radians)
startDistance?: number // Inner radius for zones (meters)
endDistance?: number // Outer radius for zones (meters)
x1?: number // Rect: first corner X (meters)
y1?: number // Rect: first corner Y (meters)
x2?: number // Rect: second corner X (meters)
y2?: number // Rect: second corner Y (meters)
width?: number // Rect: perpendicular width (meters)
timestamp?: string // ISO 8601 timestamp when value was last changed
}
interface Legend {
lowReturn: number
mediumReturn: number
strongReturn: number
targetBorder: number
dopplerApproaching?: number
dopplerReceding?: number
historyStart: number
pixelColors: number
pixels: LegendPixel[]
}
interface LegendPixel {
type: 'normal' | 'dopplerApproaching' | 'dopplerReceding' | 'history'
color: string
}
interface Target {
id: number
status: 'tracking' | 'lost' | 'acquiring'
position: {
bearing: number // radians [0, 2π)
distance: number // meters
latitude?: number // omitted if radar position unavailable
longitude?: number // omitted if radar position unavailable
}
motion?: {
// omitted if motion not yet computed; present with zeros for stationary targets
course: number // radians [0, 2π)
speed: number // m/s
}
danger?: {
// omitted if vessels diverging or own-ship motion unavailable
cpa: number // meters
tcpa: number // seconds
}
acquisition: 'manual' | 'auto'
sourceZone?: number // guard zone (1 or 2) that acquired this target; omitted for manual/Doppler
firstSeen: string // ISO 8601 timestamp
lastSeen: string // ISO 8601 timestamp
}