# Server API for plugins

SignalK server provides an interface to allow plugins to:

- Discover Features.
- Access / update the full data model
- send / receive deltas (updates)
- Interact with APIs
- Expose HTTP endpoints

These functions are available via the `app` object passed to the plugin when it is invoked.

### Discover Features

#### `app.getFeatures(enabed)`

Returns an object detailing the available APIs and Plugins.

The `enabled` parameter is optional and has the following values:

- `undefined` (not provided): list all features
- `true`: list only enabled features
- `false`: list only disabled features

Example:

    let features = app.getFeatures();

    {
      "apis": [
        "resources","course"
      ],
      "plugins": [
        {
          "id": "anchoralarm",
          "name": "Anchor Alarm",
          "version": "1.13.0",
          "enabled": true
        },
        {
          "id": "autopilot",
          "name": "Autopilot Control",
          "version": "1.4.0",
          "enabled": false
        },
        {
          "id": "sk-to-nmea2000",
          "name": "Signal K to NMEA 2000",
          "version": "2.17.0",
          "enabled": false
        },
        {
          "id": "udp-nmea-sender",
          "name": "UDP NMEA0183 Sender",
          "version": "2.0.0",
          "enabled": false
        }
      ]
    }

### Accessing the Data Model

#### `app.getPath(path)`

Returns the entry for the provided path starting from the `root` of the full data model.

Example:

    let baseStations = app.getPath('shore.basestations');

    // baseStations:
    {
      'urn:mrn:imo:mmsi:2766140': {
        url: 'basestations',
        navigation: { position: {latitude: 45.2, longitude: 76.4} },
        mmsi: '2766140'
      },
      'urn:mrn:imo:mmsi:2766160': {
        url: 'basestations',
        navigation: { position: {latitude: 46.9, longitude: 72.22} },
        mmsi: '2766160'
      }
    }

#### `app.getSelfPath(path)`

Returns the entry for the provided path starting from `vessels.self` in the full data model.

Example:

    let uuid = app.getSelfPath('uuid');
    // Note: This is synonymous with app.getPath('vessels.self.uuid')

    app.debug(uuid);
    // urn:mrn:signalk:uuid:a9d2c3b1-611b-4b00-8628-0b89d014ed60

#### `app.registerPutHandler(context, path, callback, source)`

Register a handler to action `PUT` requests for a specific path.

The action handler can handle the request synchronously or asynchronously.

The `callback` parameter should be a function which accepts the following arguments:

- `context`
- `path`
- `value`
- `callback`

For synchronous actions, the handler must return a value describing the response of the request:

    {
      state: 'COMPLETED',
      statusCode: 200
    }

or

    {
     state:'COMPLETED',
     statusCode: 400,
     message:'Some Error Message'
    }

The `statusCode` value can be any valid HTTP response code.

For asynchronous actions, that may take considerable time to complete and the requester should not be kept waiting for the result, the handler must return:

    { state: 'PENDING' }

When the action has completed the handler should call the `callback` function with the result:

    callback({ state: 'COMPLETED', statusCode: 200 })

or

    callback({
      state:'COMPLETED',
      statusCode: 400,
      message:'Some Error Message'
    })

Example: Synchronous response:

    function myActionHandler(context, path, value, callback) {
      if(doSomething(context, path, value)){
        return { state: 'COMPLETED', statusCode: 200 };
      } else {
        return { state: 'COMPLETED', statusCode: 400 };
      }
    }

    plugin.start = (options) => {
      app.registerPutHandler('vessels.self', 'some.path', myActionHandler, 'somesource.1');
    }

Example: Asynchronous response:

    function myActionHandler(context, path, value, callback) {

      doSomethingAsync(context, path, value, (result) =>{
        if(result) {
          callback({ state: 'COMPLETED', result: 200 })
        } else {
          callback({ state: 'COMPLETED', result: 400 })
        }
      });

      return { state: 'PENDING' };
    }

    plugin.start = (options) => {
      app.registerPutHandler('vessels.self', 'some.path', myActionHandler);
    }

### Working with Deltas

#### `app.handleMessage(pluginId, delta, skVersion = 'v1')`

Emit a delta message.

Note: These deltas are handled by the server in the same way as any other incoming deltas.

Example:

    app.handleMessage('my-signalk-plugin', {
      updates: [
        {
          values: [
            {
              path: 'navigation.courseOverGroundTrue',
              value: 1.0476934
            }
          ]
        }
      ]
    });

Plugins emitting deltas that use Signal K v2 paths (like the Course API paths) should call `handleMessage` with the optional `skVersion` parameter set to `v2`. This prevents v2 API data getting mixed in v1 paths' data in full data model & the v1 http API.

Omitting the `skVersion` parameter will cause the delta to be sent as `v1`.

#### `app.streambundle.getBus(path)`

Get a Bacon JS stream for a Signal K path that will stream values from any context.

The `path` parameter is optional. If it is not provided the returned stream produces values for all paths.

Stream values are objects with the following structure:

      {
        path: ...,
        value: ...,
        context: ...,
        source: ...,
        $source: ...,
        timestamp: ...
      }

Example:

    app.streambundle
      .getBus('navigation.position')
      .forEach(pos => app.debug(pos));

    // output
    {
      path: 'navigation.position',
      value: { longitude: 24.7366117, latitude: 59.72493 },
      context: 'vessels.urn:mrn:imo:mmsi:2766160',
      source: {
        label: 'n2k-sample-data',
        type: 'NMEA2000',
        pgn: 129039,
        src: '43'
      },
      '$source': 'n2k-sample-data.43',
      timestamp: '2014-08-15T19:00:02.392Z'
    }
    {
      path: 'navigation.position',
      value: { longitude: 24.82365, latitude: 58.159598 },
      context: 'vessels.urn:mrn:imo:mmsi:2766140',
      source: {
        label: 'n2k-sample-data',
        type: 'NMEA2000',
        pgn: 129025,
        src: '160'
      },
      '$source': 'n2k-sample-data.160',
      timestamp: '2014-08-15T19:00:02.544Z'
    }

#### `app.streambundle.getSelfBus(path)`

Get a Bacon JS stream for path from the `vessels.self` context.

The `path` parameter is optional. If it is not provided the returned stream contains values for all paths.

Example:

    app.streambundle
      .getSelfBus('navigation.position')
      .forEach(pos => app.debug(pos));

    // output
    {
      path: 'navigation.position',
      value: { longitude: 24.7366117, latitude: 59.72493 },
      context: 'vessels.urn:mrn:signalk:uuid:a9d2c3b1-611b-4b00-8628-0b89d014ed60',
      source: {
        label: 'n2k-sample-data',
        type: 'NMEA2000',
        pgn: 129039,
        src: '43'
      },
      '$source': 'n2k-sample-data.43',
      timestamp: '2014-08-15T19:00:02.392Z'
    }
    {
      path: 'navigation.position',
      value: { longitude: 24.7366208, latitude: 59.7249198 },
      context: 'vessels.urn:mrn:signalk:uuid:a9d2c3b1-611b-4b00-8628-0b89d014ed60',
      source: {
        label: 'n2k-sample-data',
        type: 'NMEA2000',
        pgn: 129025,
        src: '160'
      },
      '$source': 'n2k-sample-data.160',
      timestamp: '2014-08-15T19:00:02.544Z'
    }

#### `app.streambundle.getSelfStream(path)`

Get a Bacon JS stream for a path in the `vessels.self` context.

The `path` argument is optional. If it is not provided the returned stream produces values for all paths.

Note: This is similar to `app.streambundle.getSelfBus(path)`, except that the stream values contain only the `value` property from the incoming deltas.

Example:

    app.streambundle
      .getSelfStream('navigation.position')
      .forEach(pos => app.debug(pos));

    // output

      my-signalk-plugin { longitude: 24.736677, latitude: 59.7250108 } +600ms
      my-signalk-plugin { longitude: 24.736645, latitude: 59.7249883 } +321ms
      my-signalk-plugin { longitude: 24.7366563, latitude: 59.7249807 } +174ms
      my-signalk-plugin { longitude: 24.7366563, latitude: 59.724980699999996 } +503ms

#### `app.streambundle.getAvailablePaths()`

Get a list of available full data model paths maintained by the server.

Example:

    app.streambundle.getAvailablePaths();

    // returns
    [
      "navigation.speedOverGround",
      "navigation.courseOverGroundTrue",
      "navigation.courseGreatCircle.nextPoint.position",
      "navigation.position",
      "navigation.gnss.antennaAltitude",
      "navigation.gnss.satellites",
      "navigation.gnss.horizontalDilution",
      "navigation.gnss.positionDilution",
      "navigation.gnss.geoidalSeparation",
      "navigation.gnss.type","navigation.gnss.methodQuality",
      "navigation.gnss.integrity",
      "navigation.magneticVariation",
    ]

#### `app.registerDeltaInputHandler ((delta, next) => {} )`

Register a function to intercept all delta messages before they are processed by the server.

The callback function should call `next(delta)` with either:

- A modified delta (if it wants to alter the incoming delta)
- With the original delta to process it normally.

Note: Not calling `next(delta)` will cause the incoming delta to be dropped and will only show in delta statistics.

Other, non-delta messages produced by provider pipe elements are emitted normally.

    app.registerDeltaInputHandler((delta, next) => {
      delta.updates.forEach(update => {
        update.values.forEach(pathValue => {
          if(pathValue.startsWith("foo")) {
            pathValue.path = "bar"
          }
        })
      })
      next(delta)
    });

### Configuration

#### `app.savePluginOptions(options, callback)`

Save changes to the plugin's configuration options.

Example:

    let options = {
      myConfigValue = 'Something the plugin calculated'
    };

    app.savePluginOptions(options, () => {app.debug('Plugin options saved')});

#### `app.readPluginOptions()`

Read the stored plugin configuration options.

Example:

    let options = app.readPluginOptions();

#### `app.getDataDirPath()`

Returns the full path of the directory where the plugin can persist its internal data, e.g. data files, etc.

Example:

    let myDataFile = require('path').join( app.getDataDirPath(), 'somedatafile.ext')

### Messages and Debugging

#### `app.setPluginStatus(msg)`

Set the current status of the plugin that is displayed in the plugin configuration UI and the Dashboard.

The `msg` parameter should be a short text message describing the current status of the plugin.

Example:

    app.setPluginStatus('Initializing');
    // Do something
    app.setPluginStatus('Done initializing');

Note: Replaces deprecated `setProviderStatus()`

#### `app.setPluginError(msg)`

Set the current error status of the plugin that is displayed in the plugin configuration UI and the Dashboard.

The `msg` parameter should be a short text message describing the current status of the plugin.

Example:

    app.setPluginError('Error connecting to database');

Note: Replaces deprecated `setProviderError()`

#### `app.debug(...)`

Log debug messages.

This function exposes the `debug` method from the debug module. The npm module name is used as the debug name.

`app.debug()` can take any type and will serialize it before outputting.

_Note: Do not use `debug` from the debug module directly! Using `app.debug()`provided by the server ensures that the plugin taps into the server's debug logging system, including the helper switches in Admin UI's Server Log page.

#### `app.error(message)`

Report errors in a human-oriented message. Currently just logs the message, but in the future error messages hopefully will show up in the admin UI.

#### `reportOutputMessages(count)`

Report to the server that the plugin has sent data to other hosts so it can be displayed on the Dashboard.

Note: This function is for use when the plugin is sending data to hosts other than the Signal K server (e.g. network packets, http requests or messages sent to a broker).

This function should NOT be used for deltas that the plugin sends with `handleMessage()`!

The `count` parameter is optional and represents the number of messages sent between this call the previous call. If omitted the call will count as one output message.

Example:

    app.reportOutputMessages(54);

### Serial Port

#### `app.getSerialPorts() => Promise<Ports>`

This returns a Promise which will resolve to a Ports object which contains information about the serial ports available on the machine.

### Resources API Interface

#### `app.registerResourceProvider(ResourceProvider)`

Used by Resource Provider plugins to register each resource type it handles.

See `Resource Provider Plugins` for details.

#### `app.resourcesApi.getResource(resource_type, resource_id, provider_id?)`

Retrieve the resource with the supplied SignalK resource_type and resource_id.

Note: Requires a registered Resource Provider for the supplied `resource_type`.

- `resource_type`: Any Signal K (i.e. `routes`,`waypoints`, `notes`, `regions` & `charts`) or user defined resource types.

- `resource_id`: The resource identifier. (e.g. `ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a`)

- `provider_id` (optional): The id of the Resource Provider plugin to use to complete the request. Most commonly used for creating a new resource entry when more than one provider is registered for the specified resource type.

- returns: `Promise<any>` Promise for the resource.

Example:

    app.resourcesApi.getResource(
      'waypoints',
      'ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a'
      }
    ).then ( () => {
      // success
      ...
    }).catch (error) {
      // handle error
      console.log(error.message);
      ...
    }

#### `app.resourcesApi.setResource(resource_type, resource_id, resource_data, provider_id?)`

Create / update value of the resource with the supplied SignalK resource_type and resource_id.

Note: Requires a registered Resource Provider for the supplied `resource_type`.

- `resource_type`: Any Signal K (i.e. `routes`,`waypoints`, `notes`, `regions` & `charts`) or user defined resource types.

- `resource_id`: The resource identifier. (e.g. `ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a`)

- `resource_data`: A complete and valid resource record.

- `provider_id` (optional): The id of the Resource Provider plugin to use to complete the request. Most commonly used for creating a new resource entry when more than one provider is registered for the specified resource type.

- returns: `Promise<void>`

Example:

    app.resourcesApi.setResource(
      'waypoints',
      'ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a',
      {
        "name": "My Waypoint",
        "feature": {
          "type":"Feature",
          "geometry": {
            "type": "Point",
            "coordinates": [138.5, -38.6]
          },
          "properties":{}
        }
      }
    ).then ( () => {
      // success
      ...
    }).catch (error) {
      // handle error
      console.log(error.message);
      ...
    }

#### `app.resourcesApi.deleteResource(resource_type, resource_id, provider_id?)`

Delete the resource with the supplied SignalK resource_type and resource_id.

Note: Requires a registered Resource Provider for the supplied `resource_type`.

- `resource_type`: Any Signal K (i.e. `routes`,`waypoints`, `notes`, `regions` & `charts`) or user defined resource types.

- `resource_id`: The resource identifier. (e.g. `ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a`)

- `provider_id` (optional): The id of the Resource Provider plugin to use to complete the request.

- returns: `Promise<void>`

Example:

    app.resourcesApi.deleteResource(
      'notes',
      'ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a'
    ).then ( () => {
      // success
      ...
    }).catch (error) {
      // handle error
      console.log(error.message);
      ...
    }

#### `app.resourcesApi.listResources(resource_type, params, provider_id?)`

Retrieve collection of resource entries of the supplied resource_type matching the provided criteria.

Note: Requires a registered Resource Provider for the supplied `resource_type`.

- `resource_type`: Any Signal K (i.e. `routes`,`waypoints`, `notes`, `regions` & `charts`) or user defined resource types.

- `params`: Object contining `key | value` pairs repesenting the crteria by which to filter the returned entries.

- `provider_id` (optional): The id of the Resource Provider plugin to use to complete the request.

Note: The registered Resource Provider must support the supplied parameters for results to be filtered.

- returns: `Promise<{[key: string]: any}>`

Example:

    app.resourcesApi.listResources(
      'waypoints',
      {region: 'fishing_zone'}
    ).then (data => {
      // success
      console.log(data);
      ...
    }).catch (error) {
      // handle error
      console.log(error.message);
      ...
    }

### Course API Interface

The `Course API` provides the following functions for use by plugins.

#### `app.getCourse()`

Retrieves the current course information.

- returns: Resolved Promise on success containing the same course information returned by the `/course` API endpoint.

#### `app.clearDestination()`

Cancels navigation to the current point or route being followed.

- returns: Resolved Promise on success.

#### `app.setDestination(dest)`

Set course to a specified position / waypoint.

- `dest`: Object containing destination position information as per `/course/destination`.

- returns: Resolved Promise on success.

#### `app.activateRoute(rte)`

Follow a route. in the specified direction and starting at the specified point.

- `rte`: Object containing route information as per `/course/activeRoute`.

- returns: Resolved Promise on success.

### PropertyValues

The PropertyValues mechanism provides a means for passing configuration type values between different components running in the server process such as plugins and input connections.

A plugin can both emit values and listen for values emitted by others.

The difference between the PropertyValues mechanism and Event Emitters in NodeJs is that when `onPropertyValues` is called, the `callback()` function will be invoked and passed an array containing all of the previous values for that property name, starting with the initial value of `undefined`. If no values have been emitted for that property name the callback will be invoked with a value of `undefined`.

    app.emitPropertyValue: (name: string, value: any) => void

    onPropertyValues: (
      name: string,
      callback: (propValuesHistory: PropertyValue[]) => void
    ) => Unsubscribe

PropertyValue has the following structure:

    interface PropertyValue {
      timestamp: number // millis
      setter: string // plugin id, server, provider id
      name: string
      value: any
    }

Note that the value can be also a function.

This mechanism allows plugins to offer extensions via "Well Known Properties", for example

- additional NMEA0183 sentence parsers for custom sentences via `nmea0183sentenceParser`
- additional PGN definitions for propietary or custom PGNs

Code handling incoming PropertyValues should be fully reactive due to:

- Plugins being able to emit PropertyValues when they activated and / or started
- There being no defined load / startup order for plugins / connections.

So even if all plugins / connections emit during their startup, you cannot depend on a specific PropertyValue being available. It may be present when your code starts or it may arrive after your code has started.

Note: The PropertyValues mechanism is not intended to be used for data passing on a regular basis, as the total history makes it a potential memory leak.

To safeguard against a component accidentally emitting regularly, via a fixed upper bound is enforced for the value array per property name. New values will be ignored if the upper bound is reached and are logged as errors.

### Exposing custom HTTP paths & OpenApi

Plugins are able to provide an API via a function called `registerWithRouter(router)`, which like the plugin's `start` and `stop` functions, will be called during plugin startup with an Express router as the parameter.

The router will be mounted at `/plugins/<pluginId>` and you can use standard Express (`.get()` `.post()` `.use()`, etc) methods to add HTTP path handlers.

Note: `GET /plugins/<pluginid>` and `POST /plugins/<pluginid>/configure` are reserved by server (see below).

It should be noted that Express does not have a public API for deregistering subrouters, so `stop` does not do anything to the router.

If a plugin does provide an API, it is strongly recommended that it provide an OpenApi description to document its operation.

Doing so promotes interoperability with other plugins / webapps by making it easy to find and use the functionality built into plugins. It is also a means to avoid duplication, promote reuse and the possibility of including them in the Signal K specification.

See Server Plugins for details.

### Plugin configuration HTTP API

#### `GET /plugins/`

Get a list of installed plugins and their configuration data.

#### `GET /plugins/<pluginid>`

Get information from an installed plugin.

Example result:

    {
      "enabled": false,
      "id": "marinetrafficreporter",
      "name": "Marine Traffic Reporter"
    }

#### `POST /plugins/<pluginid>/configure`

Save configuration data for a plugin. Stops and starts the plugin as a side effect.