Signal K
    Preparing search index...

    Radar API

    The Signal K server Radar API provides a unified interface for viewing and controlling marine radar equipment from any manufacturer. The API is chartplotter-friendly: clients can build dynamic UIs that automatically adapt to any radar's capabilities without hardcoding support for specific brands or models.

    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.

    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:

    1. Fetch capabilities once when a radar connects — this tells you what the radar can do
    2. Generate UI widgets from the schema:
      • type: "boolean" → Toggle switch
      • type: "number" with range → Slider with min/max/step
      • type: "enum" with values → Dropdown or button group (hide values where readOnly: true)
      • type: "compound" → Nested panel (e.g., mode selector + value slider)
      • readOnly: true on control → Display-only label (for info like serial number)
      • readOnly: true on enum value → Value can be reported but not set (e.g., "off", "warming" for power)
    3. Apply constraints dynamically — gray out controls when conditions are met, show reasons
    4. Poll state for current values — the schema tells you what to expect

    Example: Rendering a Gain Control

    // Capability definition tells you everything needed:
    const gainControl = {
    id: 'gain',
    name: 'Gain',
    type: 'compound',
    modes: ['auto', 'manual'],
    properties: {
    mode: { type: 'enum', values: [{ value: 'auto' }, { value: 'manual' }] },
    value: { type: 'number', range: { min: 0, max: 100, unit: 'percent' } }
    }
    }

    // Your UI renders:
    // - Mode toggle: [Auto] [Manual]
    // - Value slider: 0 ----[50]---- 100 (disabled when mode=auto)

    Whether it's a Furuno DRS4D-NXT with 20+ 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:

    1. Characteristics — hardware capabilities (Doppler, dual-range, no-transmit zones, supported ranges)
    2. Controls — schema for each control (type, valid values, modes, read-only status)
    3. Constraints — dependencies between controls (e.g., "gain is read-only when preset mode is active")
    Category Description Examples
    base Available on all radars power, range, gain, sea, rain
    extended Model-specific features dopplerMode, beamSharpening, targetExpansion, noTransmitZones
    installation Setup/configuration settings antennaHeight, bearingAlignment

    Read-only information (serialNumber, firmwareVersion, operatingHours) is exposed as controls with readOnly: true.

    Note: Clients should consider showing installation category controls in a separate setup panel, potentially with confirmation dialogs, as these are typically configured once during radar installation.

    /signalk/v2/api/vessels/self/radars
    ├── GET → List all radar IDs
    ├── /_providers
    │ └── GET → List registered providers
    └── /{id}
    ├── /capabilities GET → Get radar schema (characteristics, controls)
    ├── /state GET → Get current values for all controls
    ├── /controls
    │ ├── GET → Get all control values
    │ ├── PUT → Set multiple controls
    │ └── /{controlId}
    │ ├── GET → Get single control value
    │ └── PUT → Set single control value
    ├── /stream → WebSocket (binary spoke data)
    └── /targets → ARPA target tracking
    ├── GET → List tracked targets with CPA/TCPA
    ├── POST → Manual target acquisition
    ├── WS → Stream target updates
    └── /{targetId}
    └── DELETE → Cancel target tracking

    Retrieve a list of all available radar IDs:

    HTTP GET "/signalk/v2/api/vessels/self/radars"
    

    Response:

    ["Furuno-6424", "Navico-HALO"]
    

    The capability manifest describes everything a radar can do. Clients should fetch this once and cache it.

    HTTP GET "/signalk/v2/api/vessels/self/radars/{id}/capabilities"
    

    Response:

    {
    "id": "Furuno-6424",
    "make": "Furuno",
    "model": "DRS4D-NXT",
    "modelFamily": "DRS-NXT",
    "serialNumber": "6424",
    "characteristics": {
    "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": 512,
    "hasDoppler": true,
    "hasDualRange": true,
    "maxDualRange": 22224,
    "noTransmitZoneCount": 2
    },
    "controls": [
    {
    "id": "power",
    "name": "Power",
    "description": "Radar power state",
    "category": "base",
    "type": "enum",
    "values": [
    { "value": "off", "label": "Off", "readOnly": true },
    { "value": "standby", "label": "Standby" },
    { "value": "transmit", "label": "Transmit" },
    { "value": "warming", "label": "Warming Up", "readOnly": true }
    ]
    },
    {
    "id": "range",
    "name": "Range",
    "description": "Detection range in meters",
    "category": "base",
    "type": "enum",
    "values": [
    { "value": 50, "label": "50m" },
    { "value": 1852, "label": "1nm" },
    { "value": 3704, "label": "2nm" }
    ]
    },
    {
    "id": "gain",
    "name": "Gain",
    "description": "Receiver gain adjustment",
    "category": "base",
    "type": "compound",
    "modes": ["auto", "manual"],
    "defaultMode": "auto",
    "properties": {
    "mode": { "type": "string" },
    "value": {
    "type": "number",
    "range": { "min": 0, "max": 100, "unit": "percent" }
    }
    }
    },
    {
    "id": "serialNumber",
    "name": "Serial Number",
    "description": "Radar hardware serial number",
    "category": "base",
    "type": "string",
    "readOnly": true
    },
    {
    "id": "firmwareVersion",
    "name": "Firmware Version",
    "description": "Radar firmware version",
    "category": "base",
    "type": "string",
    "readOnly": true
    },
    {
    "id": "operatingHours",
    "name": "Operating Hours",
    "description": "Total hours of radar operation",
    "category": "base",
    "type": "number",
    "range": { "min": 0, "max": 999999, "step": 0.1, "unit": "hours" },
    "readOnly": true
    },
    {
    "id": "dopplerMode",
    "name": "Doppler Mode",
    "description": "Target velocity color coding",
    "category": "extended",
    "type": "enum",
    "values": [
    { "value": "off", "label": "Off" },
    { "value": "normal", "label": "Normal" },
    { "value": "approaching", "label": "Approaching Only" }
    ]
    }
    ],
    "constraints": [
    {
    "controlId": "gain",
    "condition": {
    "type": "read_only_when",
    "dependsOn": "presetMode",
    "operator": "!=",
    "value": "custom"
    },
    "effect": {
    "readOnly": true,
    "reason": "Controlled by preset mode"
    }
    }
    ],
    "supportedFeatures": ["arpa", "guardZones", "trails"]
    }

    Current values for all controls, plus operational status:

    HTTP GET "/signalk/v2/api/vessels/self/radars/{id}/state"
    

    Response:

    {
    "id": "Furuno-6424",
    "timestamp": "2025-01-15T10:30:00Z",
    "status": "transmit",
    "controls": {
    "power": "transmit",
    "range": 1852,
    "gain": { "mode": "auto", "value": 50 },
    "sea": { "mode": "auto", "value": 30 },
    "rain": { "mode": "manual", "value": 0 },
    "serialNumber": "6424",
    "firmwareVersion": "01.05",
    "operatingHours": 29410.6,
    "dopplerMode": "normal"
    },
    "disabledControls": []
    }

    All control operations require appropriate security permissions.

    HTTP PUT "/signalk/v2/api/vessels/self/radars/{id}/controls/{controlId}"
    

    Setting power state:

    HTTP PUT "/signalk/v2/api/vessels/self/radars/Furuno-6424/controls/power"
    

    Request body:

    { "value": "transmit" }
    

    Setting range:

    HTTP PUT "/signalk/v2/api/vessels/self/radars/Furuno-6424/controls/range"
    

    Request body:

    { "value": 1852 }
    

    Setting gain (compound control):

    HTTP PUT "/signalk/v2/api/vessels/self/radars/Furuno-6424/controls/gain"
    

    Request body:

    { "value": { "mode": "manual", "value": 75 } }
    

    Update multiple controls in a single request:

    HTTP PUT "/signalk/v2/api/vessels/self/radars/{id}/controls"
    

    Request body:

    {
    "gain": { "mode": "manual", "value": 60 },
    "sea": { "mode": "auto" },
    "rain": { "mode": "manual", "value": 20 }
    }

    Radar spoke data is streamed via WebSocket as binary frames. The state response includes an optional streamUrl field indicating where to connect.

    // Fetch radar state
    const state = await fetch(
    '/signalk/v2/api/vessels/self/radars/Furuno-6424/state'
    ).then((r) => r.json())

    // Determine WebSocket URL
    const wsUrl =
    state.streamUrl ??
    `ws://${location.host}/signalk/v2/api/vessels/self/radars/${state.id}/stream`

    // Connect to stream
    const socket = new WebSocket(wsUrl)
    socket.binaryType = 'arraybuffer'

    socket.onmessage = (event) => {
    const spokeData = new Uint8Array(event.data)
    // Process binary spoke data...
    }
    Scenario streamUrl Description
    External server Present High-bandwidth streams bypass Signal K
    Integrated plugin Absent Signal K handles everything via /stream
    WASM plugin Present Points to dedicated binary stream endpoint

    The Radar API has ARPA (Automatic Radar Plotting Aid) target tracking with CPA/TCPA calculations and SignalK notification integration.

    HTTP GET "/signalk/v2/api/vessels/self/radars/{id}/targets"
    

    Response:

    {
    "radarId": "Furuno-6424",
    "timestamp": "2025-01-15T10:30:00Z",
    "targets": [
    {
    "id": 1,
    "status": "tracking",
    "position": {
    "bearing": 45.2,
    "distance": 1852,
    "latitude": 52.3702,
    "longitude": 4.8952
    },
    "motion": {
    "course": 180.0,
    "speed": 6.5
    },
    "danger": {
    "cpa": 150,
    "tcpa": 324
    },
    "acquisition": "auto",
    "firstSeen": "2025-01-15T10:25:00Z",
    "lastSeen": "2025-01-15T10:30:00Z"
    },
    {
    "id": 3,
    "status": "tracking",
    "position": {
    "bearing": 270.0,
    "distance": 3500
    },
    "motion": {
    "course": 90.0,
    "speed": 4.2
    },
    "danger": {
    "cpa": 820,
    "tcpa": 450
    },
    "acquisition": "manual",
    "firstSeen": "2025-01-15T10:20:00Z",
    "lastSeen": "2025-01-15T10:30:00Z"
    }
    ]
    }

    Acquire a target at a specific bearing and distance:

    HTTP POST "/signalk/v2/api/vessels/self/radars/{id}/targets"
    

    Request body:

    {
    "bearing": 45.0,
    "distance": 2000
    }

    Response:

    {
    "success": true,
    "targetId": 5,
    "status": "acquiring"
    }
    HTTP DELETE "/signalk/v2/api/vessels/self/radars/{id}/targets/{targetId}"
    

    Response:

    {
    "success": true
    }

    Stream real-time target updates:

    WS "/signalk/v2/api/vessels/self/radars/{id}/targets"
    

    Server sends messages:

    // Target position/motion update
    {
    "type": "target_update",
    "target": {
    "id": 1,
    "status": "tracking",
    "position": { "bearing": 45.5, "distance": 1800 },
    "motion": { "course": 180.0, "speed": 6.5 },
    "danger": { "cpa": 140, "tcpa": 310 }
    }
    }

    // New target acquired
    {
    "type": "target_acquired",
    "target": { /* full ArpaTarget */ }
    }

    // Target tracking lost
    {
    "type": "target_lost",
    "targetId": 3,
    "reason": "no_return",
    "lastPosition": { "bearing": 120.5, "distance": 3500 }
    }

    When a target temporarily disappears (behind waves, in rain clutter), the tracker maintains prediction for a configurable grace period before marking it as lost.

    Configure via radar state:

    HTTP PUT "/signalk/v2/api/vessels/self/radars/{id}/state"
    
    {
    "arpaSettings": {
    "targetLostTimeout": 45,
    "cpaAlertThreshold": 500,
    "tcpaAlertThreshold": 600
    }
    }
    Setting Default Description
    targetLostTimeout 45 Seconds before marking target lost
    cpaAlertThreshold 500 CPA (meters) triggering alert state
    tcpaAlertThreshold 600 TCPA (seconds) within which to check CPA

    ARPA targets publish collision warnings to SignalK's notification system, enabling chart plotters to display radar-based alerts alongside AIS alerts.

    notifications.navigation.closestApproach.radar:{radarId}:target:{targetId}
    notifications.navigation.radarGuardZone.radar:{radarId}:zone:{zoneId}
    notifications.navigation.radarTargetLost.radar:{radarId}:target:{targetId}

    Published when a target's CPA crosses a threshold. States follow SignalK conventions:

    State CPA Threshold Description
    normal > 1000m Target tracked, no danger
    alert < 1000m Approaching, monitor closely
    warn < 500m Getting close
    alarm < 200m Danger, take action
    emergency < 100m Imminent collision

    Example notification:

    {
    "path": "notifications.navigation.closestApproach.radar:Furuno-6424:target:3",
    "value": {
    "state": "warn",
    "method": ["visual", "sound"],
    "message": "ARPA target 3: CPA 320m in 5m 24s",
    "timestamp": "2025-01-15T10:30:00Z",
    "data": {
    "cpa": 320,
    "tcpa": 324,
    "bearing": 45.2,
    "distance": 1852,
    "targetCourse": 180,
    "targetSpeed": 6.5
    }
    }
    }

    Published when a target enters a guard zone:

    {
    "path": "notifications.navigation.radarGuardZone.radar:Furuno-6424:zone:1",
    "value": {
    "state": "alarm",
    "method": ["visual", "sound"],
    "message": "Target in guard zone 1",
    "timestamp": "2025-01-15T10:30:00Z",
    "data": {
    "zoneId": 1,
    "zoneName": "Starboard sector",
    "targetBearing": 45.2,
    "targetDistance": 500
    }
    }
    }

    Published when tracking is lost on a manually-acquired target. Auto-acquired targets silently drop without notification.

    {
    "path": "notifications.navigation.radarTargetLost.radar:Furuno-6424:target:5",
    "value": {
    "state": "warn",
    "method": ["visual"],
    "message": "ARPA target 5 lost",
    "timestamp": "2025-01-15T10:30:00Z",
    "data": {
    "targetId": 5,
    "lastBearing": 120.5,
    "lastDistance": 3500,
    "trackedDuration": 324
    }
    }
    }

    Use standard SignalK delta subscription to receive radar notifications:

    {
    "context": "vessels.self",
    "subscribe": [
    {
    "path": "notifications.navigation.closestApproach.radar:*",
    "policy": "instant"
    },
    {
    "path": "notifications.navigation.radarGuardZone.*",
    "policy": "instant"
    },
    {
    "path": "notifications.navigation.radarTargetLost.*",
    "policy": "instant"
    }
    ]
    }

    Optional features a radar provider may implement. This declares what API capabilities are available, NOT hardware capabilities (those are in characteristics).

    type SupportedFeature = 'arpa' | 'guardZones' | 'trails' | 'dualRange'
    
    Feature Description Related Endpoints
    arpa ARPA target tracking with CPA/TCPA GET/POST/DELETE /targets, WS /targets
    guardZones Guard zone alerting GET/PUT /guardZones
    trails Target history/trail data GET /trails
    dualRange Dual-range simultaneous display Secondary range controls

    Important distinction:

    • characteristics.hasDoppler = true means the hardware supports Doppler
    • supportedFeatures.includes('trails') means the provider implements the /trails endpoint

    A radar may have Doppler hardware but the provider might not implement trails. Clients should check both when deciding what UI to show.

    Client usage example:

    const caps = await fetch(`/radars/${id}/capabilities`).then((r) => r.json())

    // Use optional chaining for backward compatibility
    const hasArpa = caps.supportedFeatures?.includes('arpa') ?? false
    const hasGuardZones = caps.supportedFeatures?.includes('guardZones') ?? false

    if (hasArpa) {
    // Show ARPA target panel, enable target acquisition
    }
    interface CapabilityManifest {
    id: string
    make: string
    model: string
    modelFamily?: string
    serialNumber?: string
    firmwareVersion?: string
    characteristics: Characteristics
    controls: ControlDefinition[]
    constraints?: ControlConstraint[]
    supportedFeatures?: SupportedFeature[] // Optional API features this provider implements
    }
    interface Characteristics {
    maxRange: number // Maximum detection range in meters
    minRange: number // Minimum detection range in meters
    supportedRanges: number[] // Discrete range values in meters
    spokesPerRevolution: number
    maxSpokeLength: number
    hasDoppler: boolean
    hasDualRange: boolean
    maxDualRange?: number // Max range in dual-range mode (meters), omitted if 0
    noTransmitZoneCount: number
    }
    interface ControlDefinition {
    id: string // Semantic ID (e.g., "gain", "beamSharpening")
    name: string // Human-readable name
    description: string // Tooltip/help text
    category: 'base' | 'extended' | 'installation'
    type: 'boolean' | 'number' | 'enum' | 'compound' | 'string'
    range?: RangeSpec // For number types
    values?: EnumValue[] // For enum types
    properties?: Record<string, PropertyDefinition> // For compound types
    modes?: string[] // e.g., ["auto", "manual"]
    defaultMode?: string
    readOnly?: boolean // True for info fields
    default?: any
    }

    interface RangeSpec {
    min: number
    max: number
    step?: number
    unit?: string // e.g., "percent", "meters", "hours"
    }

    interface EnumValue {
    value: string | number
    label: string
    description?: string
    readOnly?: boolean // True if this value can be reported but not set by clients
    }

    interface PropertyDefinition {
    type: string
    description?: string
    range?: RangeSpec
    values?: EnumValue[]
    }
    interface RadarState {
    id: string
    timestamp: string // ISO 8601
    status: 'off' | 'standby' | 'transmit' | 'warming'
    controls: Record<string, any>
    disabledControls?: DisabledControl[]
    streamUrl?: string // WebSocket URL for spoke data
    }

    interface DisabledControl {
    controlId: string
    reason: string
    }
    interface ControlConstraint {
    controlId: string
    condition: {
    type: 'disabled_when' | 'read_only_when' | 'restricted_when'
    dependsOn: string // Control ID this depends on
    operator: string // "==", "!=", "<", ">", etc.
    value: any // Value to compare against
    }
    effect: {
    disabled?: boolean
    readOnly?: boolean
    allowedValues?: any[] // Restricted set when condition is met
    reason?: string // Human-readable explanation
    }
    }
    interface ArpaTarget {
    id: number // Target ID (1-99 typically)
    status: 'tracking' | 'lost' | 'acquiring'
    position: {
    bearing: number // Degrees true from own vessel
    distance: number // Meters from own vessel
    latitude?: number // Calculated lat (if heading available)
    longitude?: number // Calculated lon (if heading available)
    }
    motion: {
    course: number // Computed COG (degrees true)
    speed: number // Computed SOG (m/s)
    }
    danger: {
    cpa: number // Closest Point of Approach (meters)
    tcpa: number // Time to CPA (seconds, negative = past)
    }
    acquisition: 'manual' | 'auto'
    firstSeen: string // ISO 8601 timestamp
    lastSeen: string // ISO 8601 timestamp
    }
    interface TargetListResponse {
    radarId: string
    timestamp: string // ISO 8601
    targets: ArpaTarget[]
    }
    type TargetStreamMessage =
    | { type: 'target_update'; target: ArpaTarget }
    | { type: 'target_acquired'; target: ArpaTarget }
    | {
    type: 'target_lost'
    targetId: number
    reason: 'no_return' | 'manual_cancel'
    lastPosition: { bearing: number; distance: number }
    }
    interface ArpaSettings {
    targetLostTimeout: number // Seconds before marking target lost (default: 45)
    cpaAlertThreshold: number // CPA meters for alert state (default: 500)
    tcpaAlertThreshold: number // TCPA seconds within which to check CPA (default: 600)
    }

    The Radar API supports registration of multiple radar provider plugins. All radars from all providers are aggregated under the unified API.

    HTTP GET "/signalk/v2/api/vessels/self/radars/_providers"
    

    Response:

    {
    "mayara-radar": {
    "name": "Mayara Radar Plugin"
    },
    "navico-radar": {
    "name": "Navico Radar Provider"
    }
    }

    To create a radar provider plugin, implement the RadarProvider interface:

    interface RadarProvider {
    name: string
    methods: RadarProviderMethods
    }

    interface RadarProviderMethods {
    // Required - radar discovery
    getRadars(): Promise<string[]>
    getCapabilities(radarId: string): Promise<CapabilityManifest | null>
    getState(radarId: string): Promise<RadarState | null>

    // Required - control
    setControl(radarId: string, controlId: string, value: any): Promise<boolean>
    setControls(radarId: string, controls: Record<string, any>): Promise<boolean>

    // Optional - streaming (for integrated providers)
    handleStreamConnection?(radarId: string, ws: WebSocket): void

    // Optional - ARPA targets
    getTargets?(radarId: string): Promise<TargetListResponse | null>
    acquireTarget?(
    radarId: string,
    bearing: number,
    distance: number
    ): Promise<{ success: boolean; targetId?: number }>
    cancelTarget?(radarId: string, targetId: number): Promise<boolean>
    handleTargetStreamConnection?(radarId: string, ws: WebSocket): void
    }

    Register with the server:

    app.radarApi.register(plugin.id, {
    name: 'My Radar Plugin',
    methods: {
    getRadars: async () => ['radar-1'],
    getCapabilities: async (id) => ({
    id,
    make: 'MyBrand',
    model: 'Model-X',
    characteristics: {
    /* ... */
    },
    controls: [
    /* ... */
    ]
    }),
    getState: async (id) => ({
    id,
    timestamp: new Date().toISOString(),
    status: 'transmit',
    controls: { power: 'transmit', gain: { mode: 'auto', value: 50 } }
    }),
    setControl: async (id, controlId, value) => {
    // Send command to radar hardware
    return true
    },
    setControls: async (id, controls) => {
    // Send multiple commands
    return true
    }
    }
    })

    For WASM plugins, use the radarProvider capability and implement the corresponding FFI exports.

    Plugins that want to consume radar data (rather than provide it) can access the Radar API programmatically using the getRadarApi() method on the server app object.

    This provides typed, in-process access to the Radar API without going through HTTP:

    plugin.start = async (settings) => {
    // Check if Radar API is available
    if (app.getRadarApi) {
    try {
    const radarApi = await app.getRadarApi()

    // List all available radars
    const radars = await radarApi.getRadars()
    app.debug(`Found ${radars.length} radars`)

    // Get info for a specific radar
    for (const radar of radars) {
    const info = await radarApi.getRadarInfo(radar.id)
    if (info) {
    app.debug(
    `Radar ${info.name}: status=${info.status}, range=${info.range}m`
    )
    }
    }
    } catch (err) {
    app.debug('Radar API not available:', err.message)
    }
    }
    }

    This pattern follows the same approach as the History API's getHistoryApi() method. The property is optional to support older servers that don't have radar API support.

    Following consistent naming conventions ensures that clients can work with any radar provider without custom mapping code. When multiple providers use the same control IDs, client UIs "just work" across brands.

    The Radar API uses semantic control IDs that are vendor-neutral. Each vendor's proprietary control names should be mapped to standardized IDs:

    Vendor-Specific Name     →    Semantic API ID
    ─────────────────────────────────────────────
    Furuno "RezBoost" → beamSharpening
    Navico "Beam Sharpening" → beamSharpening
    Furuno "Target Analyzer" → dopplerMode
    Navico "VelocityTrack" → dopplerMode
    Element Convention Example
    Control IDs lowerCamelCase beamSharpening, dopplerMode
    Property names lowerCamelCase mode, value, enabled
    String enum values lowercase "auto", "manual", "standby"
    Numeric enum values integers 0, 1, 2, 3
    Display names Title Case "Beam Sharpening"
    Units lowercase "meters", "degrees", "percent"

    Use lowerCamelCase for all control identifiers:

    ✓ beamSharpening
    ✓ dopplerMode
    ✓ interferenceRejection
    ✓ noTransmitZones

    beam_sharpening (no underscores)
    BeamSharpening (no PascalCase)
    BEAM_SHARPENING (no SCREAMING_CASE)

    Name controls by what they do, not what the vendor calls them:

    beamSharpening     (describes function)
    rezBoost (vendor-specific name)

    dopplerMode (describes technology)
    targetAnalyzer (Furuno marketing name)
    velocityTrack (Navico marketing name)

    Providers should use these standard IDs when their radar supports the equivalent functionality:

    Base Controls (All Radars)

    ID Type Description
    power enum Operational state: off, standby, transmit, warming
    range number Detection range in meters
    gain compound Signal amplification: {mode, value}
    sea compound Sea clutter suppression: {mode, value}
    rain compound Rain clutter suppression: {mode, value}

    Read-Only Info

    ID Type Description
    serialNumber string Hardware serial number
    firmwareVersion string Firmware version string
    operatingHours number Total hours of operation

    Signal Processing

    ID Type Common Vendor Names
    beamSharpening enum Furuno: RezBoost, Navico: Beam Sharpening
    dopplerMode compound Furuno: Target Analyzer, Navico: VelocityTrack
    dopplerSpeed number Navico: Doppler Speed
    birdMode enum Furuno: Bird Mode
    noiseReduction boolean Noise Reduction
    mainBangSuppression number MBS

    Interference Filtering

    ID Type Description
    interferenceRejection boolean/enum Filters interference from other radars
    localInterferenceRejection enum Navico: Local IR
    sidelobeSuppression compound Navico: SLS

    Target Processing

    ID Type Description
    targetSeparation enum Distinguishes closely-spaced targets
    targetExpansion enum Makes small targets more visible
    targetBoost enum Amplifies weak targets
    autoAcquire boolean Automatic ARPA target acquisition

    Operating Modes

    ID Type Description
    presetMode enum Pre-configured mode (harbor/offshore/weather/custom)
    scanSpeed enum Antenna rotation speed
    txChannel enum TX frequency channel selection

    Receiver Controls

    ID Type Description
    tune compound Receiver tuning: {mode, value}
    colorGain compound Color intensity: {mode, value}

    Installation Settings

    ID Type Description
    bearingAlignment number Heading offset correction (degrees)
    antennaHeight number Antenna height above waterline (meters)
    noTransmitZones compound Sectors where radar won't transmit

    Mode + Value Pattern (for controls with auto/manual):

    {
    "gain": {
    "mode": "auto",
    "value": 50
    }
    }

    Enabled + Mode Pattern (for toggleable features with sub-modes):

    {
    "dopplerMode": {
    "enabled": true,
    "mode": "approaching"
    }
    }

    Array Pattern (for multi-item controls):

    {
    "noTransmitZones": {
    "zones": [
    { "enabled": true, "start": 90, "end": 180 },
    { "enabled": false, "start": 0, "end": 0 }
    ]
    }
    }

    Before creating a new control ID:

    1. Check if a semantic ID already exists - Don't create echoBoost if targetBoost covers the same functionality
    2. Use semantic naming - Name by function, not vendor marketing
    3. Document the vendor mapping - Help other developers understand the equivalence
    4. Follow the patterns - Use established compound control structures

    Radar API responses include Cache-Control: no-cache headers. Clients should not cache radar data as it can change at any time:

    • Model identification: Some radars (e.g., Furuno) identify their model via TCP connection, which happens after initial discovery
    • Status changes: Radar power state, transmit status can change
    • Control values: Gain, sea clutter, range, etc. can be modified by the user or other clients

    Clients that need to minimize API calls should implement their own caching strategy with appropriate invalidation logic.