# Autopilot Provider plugins

This document should be read in conjunction with the SERVER PLUGINS document as it contains additional information regarding the development of plugins that implement the Signal K Autopilot API.

## Overview

The Signal K Autopilot API defines endpoints under the path `/signalk/v2/api/vessels/self/autopilots` providing a way for all Signal K clients to perform common autopilot operations independent of the autopilot device in use. The API is defined in an OpenAPI document.

Requests made to the Autopilot API are received by the Signal K Server, where they are validated and an authorisation check performed, before being passed on to a provider plugin to action the request on the autopilot device.

This de-coupling of request handling and autopilot communication provides the flexibility to support a variety of autopilot devices and ensures interoperability and reliabilty.

Autopilot API requests are passed to a provider plugin which will process and action the request facilitating communication with the autopilot device.

The following diagram provides an overview of the Autopilot API architectue.

Autopilot API architecture

## Provider Plugins:

An autopilot provider plugin is a Signal K server plugin that implements the Autopilot Provider Interface which:

- Tells server the autopilot devices provided for by the plugin
- Registers the methods used to action requests passed from the server to perform autopilot operations.

The `AutopilotProvider` interface is defined in `@signalk/server-api`

Multiple providers can be registered and each provider can manage one or more autopilot devices.

Note: An Autopilot Provider plugin MUST:

- Implement all Autopilot API interface methods.
- Facilitate communication on the target autopilot device to send commands and retrieve both status and configuration information
- Ensure the `engaged` path attribute value is maintained to reflect the operational status of the autopilot.
- Map the `engage` and `disengage` operations to an appropriate autopilot device `state`.
- Set the state as `off-line` if the autopilot device is not connected or unreachable.
- Set the mode as `dodge` when the autopilot device is is in dodge mode.

### Registering as an Autopilot Provider

A provider plugin must register itself with the Autopilot API during start up by calling the `registerAutopilotProvider`.

The function has the following signature:

    app.registerAutopilotProvider(provider: AutopilotProvider, devices: string[])

where:

- `provider`: is a valid AutopilotProvider object
- `devices`: is an array of identifiers indicating the autopilot devices managed by the plugin.

Example: Plugin registering as an autopilot provider.

    import { AutopilotProvider } from '@signalk/server-api'

    module.exports = function (app) {

        const plugin = {
        id: 'mypluginid',
        name: 'My autopilot Provider plugin'
        }

        const autopilotProvider: AutopilotProvider = {
            getData: (deviceId) => { return ... },
            getState: (deviceId) => { return ... },
            setState: (state, deviceId) => { ... },
            getMode: (deviceId) => { return ... },
            setMode: (mode, deviceId) => { ... },
            getTarget: (deviceId) => { return ... },
            setTarget(value, deviceId) => { ... },
            adjustTarget(value, deviceId) => { ... },
            engage: (deviceId) => { ... },
            disengage: (deviceId) => { ... },
            tack:(direction, deviceId) => { ... },
            gybe:(direction, deviceId) => { ... },
            dodge:(value, deviceId) => { ... }
        }

        const pilots = ['pilot1', 'pilot2']

      plugin.start = function(options) {
        ...
        try {
          app.registerAutopilotProvider(autopilotProvider, pilots)
        }
        catch (error) {
          // handle error
        }
      }

      return plugin
    }

### Sending Updates and Notifications from Autopilot device

The Autopilot API is responsible for sending both update and notification `deltas` to Signal K clients.

Data received from an autopilot device, regardless of the communications protocol (NMEA2000, etc), should be sent to the Autopilot API by calling the `autopilotUpdate` interface method.

This will ensure:

- Default pilot status is correctly maintained
- `steering.autopilot.*` both V1 and V2 deltas are sent

Important! The values provided via `autopilotUpdate` will be sent in the relevant delta message, so ensure they are in the correct units (e.g. angles in radians, etc).

The function has the following signature:

    app.autopilotUpdate(deviceID: string, apInfo: {[key:string]: Value})

where:

- `deviceId`: is the autopilot device identifier
- `appInfo`: object containing values keyed by <AutopilotInfo> attributes (as defined in @signalk/server-api)

Example Update:

    app.autopilotUpdate('my-pilot', {
      target: 1.52789,
      mode: 'compass'
    })

Notifications / Alarms are sent using one of the normalised alarm names below as the path and a `Notification` as the value.

- waypointAdvance
- waypointArrival
- routeComplete
- xte
- heading
- wind

Example Notification:

    app.autopilotUpdate('my-pilot', {
      alarm: {
        path: 'waypointAdvance',
        value: {
          state: 'alert'
          method: ['sound']
          message: 'Waypoint Advance'
        }
      }
    })

### Provider Methods:

`getData(deviceId)`: This method returns an AutopilotInfo object containing the current data values and valid options for the supplied autopilot device identifier.

- `deviceId:` identifier of the autopilot device to query.

returns: `Promise<{AutopilotInfo}>`

Note: It is the responsibility of the autopilot provider plugin to map the value of `engaged` to the current `state`.

Example:

    // API request
    GET /signalk/v2/api/vessels/self/autopilots/mypilot1

    // AutopilotProvider method invocation
    getData('mypilot1');

    // Returns:
    {
      options: {
        states: [
            {
                name: 'auto' // autopilot state name
                engaged: true // actively steering
            },
            {
                name: 'standby' // autopilot state name
                engaged: false // not actively steering
            }
        ]
        modes: ['compass', 'gps', 'wind']
    },
      target: 0.326
      mode: 'compass'
      state: 'auto'
      engaged: true
    }

`getState(deviceId)`: This method returns the current state of the supplied autopilot device identifier. If the autopilot device is not connected or unreachable then `off-line` should be returned.

- `deviceId:` identifier of the autopilot device to query.

returns: `Promise<{string}>`

Example:

    // API request
    GET /signalk/v2/api/vessels/self/autopilots/mypilot1/state

    // AutopilotProvider method invocation
    getState('mypilot1');

    // Returns:
    'auto'

`setState(state, deviceId?)`: This method sets the autopilot device with the supplied identifier to the supplied state value.

- `state:` state value to set. Must be a valid state value.
- `deviceId:` identifier of the autopilot device to query.

returns: `Promise<{void}>`

throws on error or if supplied state value is invalid.

Example:

    // API request
    PUT /signalk/v2/api/vessels/self/autopilots/mypilot1/state {value: "standby"}

    // AutopilotProvider method invocation
    setState('standby', 'mypilot1');

`getMode(deviceId)`: This method returns the current mode of the supplied autopilot device identifier.

- `deviceId:` identifier of the autopilot device to query.

returns: `Promise<{string}>`

Example:

    // API request
    GET /signalk/v2/api/vessels/self/autopilots/mypilot1/mode

    // AutopilotProvider method invocation
    getMode('mypilot1');

    // Returns:
    'compass'

`setMode(mode, deviceId)`: This method sets the autopilot device with the supplied identifier to the supplied mode value.

- `mode:` mode value to set. Must be a valid mode value.
- `deviceId:` identifier of the autopilot device to query.

returns: `Promise<{void}>`

throws on error or if supplied mode value is invalid.

Example:

    // API request
    PUT /signalk/v2/api/vessels/self/autopilots/mypilot1/mode {value: "gps"}

    // AutopilotProvider method invocation
    setMode('gps', 'mypilot1');

`setTarget(value, deviceId)`: This method sets target for the autopilot device with the supplied identifier to the supplied value.

- `value:` target value in radians.
- `deviceId:` identifier of the autopilot device to query.

returns: `Promise<{void}>`

throws on error or if supplied target value is outside the valid range.

Example:

    // API request
    PUT /signalk/v2/api/vessels/self/autopilots/mypilot1/target {value: 129}

    // AutopilotProvider method invocation
    setTarget(129, 'mypilot1');

`adjustTarget(value, deviceId)`: This method adjusts target for the autopilot device with the supplied identifier by the supplied value.

- `value:` value in radians to add to current target value.
- `deviceId:` identifier of the autopilot device to query.

returns: `Promise<{void}>`

throws on error or if supplied target value is outside the valid range.

Example:

    // API request
    PUT /signalk/v2/api/vessels/self/autopilots/mypilot1/target {value: 2}

    // AutopilotProvider method invocation
    adjustTarget(2, 'mypilot1');

`engage(deviceId)`: This method sets the state of the autopilot device with the supplied identifier to a state that is actively steering the vessel.

- `deviceId:` identifier of the autopilot device to query.

returns: `Promise<{void}>`

throws on error.

Example:

    // API request
    POST /signalk/v2/api/vessels/self/autopilots/mypilot1/engage

    // AutopilotProvider method invocation
    engage('mypilot1');

`disengage(deviceId)`: This method sets the state of the autopilot device with the supplied identifier to a state that is NOT actively steering the vessel.

- `deviceId:` identifier of the autopilot device to query.

returns: `Promise<{void}>`

throws on error.

Example:

    // API request
    POST /signalk/v2/api/vessels/self/autopilots/mypilot1/disengage

    // AutopilotProvider method invocation
    disengage('mypilot1');

`tack(direction, deviceId)`: This method instructs the autopilot device with the supplied identifier to perform a tack in the supplied direction.

- `direction`: 'port' or 'starboard'
- `deviceId:` identifier of the autopilot device to query.

returns: `Promise<{void}>`

throws on error.

Example:

    // API request
    POST /signalk/v2/api/vessels/self/autopilots/mypilot1/tack/port

    // AutopilotProvider method invocation
    tack('port', 'mypilot1');

`gybe(direction, deviceId)`: This method instructs the autopilot device with the supplied identifier to perform a gybe in the supplied direction.

- `direction`: 'port' or 'starboard'
- `deviceId:` identifier of the autopilot device to query.

returns: `Promise<{void}>`

throws on error.

Example:

    // API request
    POST /signalk/v2/api/vessels/self/autopilots/mypilot1/gybe/starboard

    // AutopilotProvider method invocation
    gybe('starboard', 'mypilot1');

`dodge(value, deviceId)`: This method instructs the autopilot device with the supplied identifier to enter / exit dodge mode and alter the current course by the supplied value (radians) direction.

- `value`: +/- value in radians 'port (-ive)' or 'starboard' to change direction. Setting the value to `null` indicates exit of dodge mode.
- `deviceId:` identifier of the autopilot device to query.

returns: `Promise<{void}>`

throws on error.

To address different pilot behaviour, the `dodge` function can be used in the following ways:

1. Enter dodge mode at the current course

    // API request
    POST /signalk/v2/api/vessels/self/autopilots/mypilot1/dodge

    // _AutopilotProvider method invocation
    dodge(0, 'mypilot1');

2. Enter dodge mode and change course

    // API request
    PUT /signalk/v2/api/vessels/self/autopilots/mypilot1/dodge {"value": 5}

    // AutopilotProvider method invocation
    dodge(5, 'mypilot1');

3. Cancel dodge mode

    // API request
    DELETE /signalk/v2/api/vessels/self/autopilots/mypilot1/dodge

    // AutopilotProvider method invocation
    dodge(null, 'mypilot1');

### Unhandled Operations

A provider plugin MUST implement ALL Autopilot API interface methods, regardless of whether the operation is supported or not.

For an operation that is not supported by the autopilot device, then the plugin should `throw` an exception.

Example:

    {
        // unsupported operation method definition
        gybe: async (d, id) => {
            throw new Error('Unsupprted operation!)
        }
    }