SignalK / specification

Signal K is a JSON-based format for storing and sharing marine data from different sources (e.g. nmea 0183, 2000, seatalk, etc)
Other
91 stars 68 forks source link

Proposal: Course API #629

Open panaaj opened 2 years ago

panaaj commented 2 years ago

Feature: Course API

Description:

Define an API for Signal K client applications that provides methods to set a destination or navigate a route.

These methods manage the setting of values in the relevant Signal K paths under both navigation.courseGreatCircle and navigation.courseRhumbline to enable a course computer to generate additional navigation information (XTE, DTG, etc) as well as facilitate display on a chart plotter.

The API will facilitate operations such as:


Motivation:

Currently Signal K makes available paths to store navigation data but it is largely left up to implementors of client applications to determine how they are used.

This is can cause interoperability issues and inconsistency in application (e.g. calculations in signalk-derived-data plugin will use a mixture of paths navigation.courseGreatCircle and navigation.courseRhumbline) so depending on an individual implementation results may vary.

Defining and implementing an API will provide reliability in how the values in these paths are populated ensuring confidence in the source data used in course calculations.

By clearly defining and managing the use of specific course paths within the Signal K schema, this will ensure consistency in the values they contain and engender confidence in their use.

Maintaining quality data in these paths enables the reliable operation of other navigation equipment such as:

by providing a trusted source of data for use in calculating navigation information for steering a course.

The paths within the Signal K schema pertaining to other navigation operations will be maintained by the relevant equipment or Signal K API.


1. Signal K Paths in Scope:

Note: All paths outlined below are relative to /signalk/v1/api/vessels/self.

The Signal K specification contains a navigation.course schema definition which is applied to both the navigation/courseGreatCircle and navigation/courseRhumbline paths.

The following properties under both these paths are in scope for management by this API:

activeRoute.href
activeRoute.startTime

nextPoint.value.href
nextPoint.value.type
nextPoint.position

previousPoint.value.href
previousPoint.value.type
previousPoint.position

This API will provide endpoints under the path navigation/course in order to set a course as well as query the current course information.


2.API Operation:

While the intended use of the in scope Signal K paths are defined in the specification, the actual use of these paths in practise determines the success of an implementation.

2.1 Use of previousPoint.


To facilitate course calculations such as XTE where the source position is required, the previousPoint.position attribute will be set (at the time the destination is set) as follows:


3.API Methods:

The following endpoints under the path navigation/course make up the Course API.

3.1 Set a position (lat, lon) as a destination


Use case: Provide "navigate to here" operation.

Action: PUT

Path: <self>/navigation/course/destination

Request body:

{
        "position": {"latitude": -28.5,"longitude":138.5}, 
        "type": "Location",
        "arrivalCircle": 500
}

where:

This will result in the following Signal K path values being set:

{
    "activeRoute": {
        "href": null,
        "startTime": null
    },
    "nextPoint": {
        "href": null,
        "type": null if type not supplied,
        "position": {
            "latitude": supplied latitude,
            "longitude": supplied longitude
        },
        "arrivalCircle": unchanged if value not suplied
    },
    "previousPoint": {
        "href": null,
        "type": null,
        "position": {
            "latitude": latitude of vessel at time of destination being set,
            "longitude": longitude of vessel at time of destination being set
        }
    }
}

3.2 Set a Waypoint as a destination


Use case: Provide "navigate to selected waypoint resource" operation.

Action: PUT

Path: <self>/navigation/course/destination

Request body:

{
        "href": "/resources/waypoints/urn:mrn:signalk:uuid:0d95e282-3e1f-4521-8c30-8288addbdbab",
        "arrivalCircle": 500
}

where:

This will result in the following Signal K path values being set:

{
    "activeRoute": {
        "href": null,
        "startTime": null
    },
    "nextPoint": {
        "href": supplied href,
        "type": null if type not supplied,
        "position": {
            "latitude": latitude of referenced waypoint,
            "longitude": longitude of referenced waypoint
        },
        "arrivalCircle": unchanged if value not supplied
    },
    "previousPoint": {
        "href": null,
        "type": null,
        "position": {
            "latitude": latitude of vessel at time of destination being set,
            "longitude": longitude of vessel at time of destination being set
        }
    }
}

3.3 Clear / Cancel a destination or Activated Route.


Use case: Provide "stop navigating to destination" or "deactivate as route" operation.

Action: DELETE

Path: <self>/navigation/course/destination or <self>/navigation/course/activeRoute

Request body: empty

This will result in the following Signal K path values being set:

{
    "activeRoute": {
        "href": null,
        "startTime": null
    },
    "nextPoint": {
        "href": null,
        "type": null,
        "position": null,
        "arrivalCircle": unchanged
    },
    "previousPoint": {
        "href": null,
        "type": null,
        "position": null
    }
}

3.4 Activate a Route to follow.


Use case: Provide "activate / follow a route" operation.

Action: PUT

Path: <self>/navigation/course/activeRoute

Request body:

{
        "href": "/resources/routes/urn:mrn:signalk:uuid:0d95e282-3e1f-4521-8c30-8288addbdbab",
        "pointIndex": 0,
        "reverse": false,
        "arrivalCircle": 500
}

where:

This will result in the following Signal K path values being set:

{
        "activeRoute": {
        "href": supplied href,
        "startTime": Time at which route was activated (as per Signal K schema)
    },
    "nextPoint": {
        "href": null,
        "type": null if value not supplied,
        "position": {
            "latitude": latitude of route point at supplied index,
            "longitude": longitude of route point at supplied index
        },
        "arrivalCircle": unchanged if value not supplied
    },
    "previousPoint": {
        "href": null,
        "type": null,
        "position": {
            "latitude": latitude of: vessel if supplied pointIndex=0 or point at poiintIndex-1,
            "longitude": longitude of: vessel if supplied pointIndex=0 or point at poiintIndex-1
        }
    }
}

3.5 Select point in the active Route as destination.


Use case: Provide "go to point in route" operation.

Action: PUT

Path: <self>/navigation/course/activeRoute/pointIndex

Request body:

{
    "value": 3
}

where:

This will result in the following Signal K path values being set:

{
    "activeRoute": {
        "href": unchanged,
        "startTime": unchanged
    },
    "nextPoint": {
        "href": unchanged,
        "type": unchanged,
        "position": {
            "latitude": latitude of route point at supplied index,
            "longitude": longitude of route point at supplied index
        },
        "arrivalCircle": unchanged
    },
    "previousPoint": {
        "href": unchanged,
        "type": unchanged,
        "position": {
            "latitude": latitude of: vessel if pointIndex=0 or route point at poiintIndex-1,
            "longitude": longitude of: vessel if pointIndex=0 or route point at poiintIndex-1
        }
    }
}

3.6 Increment / decrement point in the active Route as destination.


Use case: Provide "previous / next point" operation.

Action: PUT

Path: <self>/navigation/course/activeRoute/nextPoint

Request body:

{
    "value": -1
}

where:

This will result in the following Signal K path values being set:

{
    "activeRoute": {
        "href": unchanged,
        "startTime": unchanged
    },
    "nextPoint": {
        "href": unchanged,
        "type": unchanged,
        "position": {
            "latitude": latitude of previous (if -1 supplied) or next (if 1 supplied) route point,
            "longitude": longitude of previous (if -1 supplied) or next (if 1 supplied) route point
        },
        "arrivalCircle": unchanged
    },
    "previousPoint": {
        "href": unchanged,
        "type": unchanged,
        "position": {
            "latitude": latitude of: vessel (if pointIndex=0) or point at poiintIndex-1,
            "longitude": longitude of: vessel (if pointIndex=0) or point at poiintIndex-1
        }
    }
}

3.7 Restart course calculations.


Use case: Provide "restart XTE" operation.

Action: PUT

Path: <self>/navigation/course/activeRoute/restart

Request body: empty

This will result in the following Signal K path values being set:
```JSON
{
    "activeRoute": {
        "href": unchanged,
        "startTime": unchanged
    },
    "nextPoint": {
        "href": unchanged,
        "type": unchanged,
        "position": {
            "latitude": unchanged,
            "longitude": unchanged
        },
        "arrivalCircle": unchanged 
    },
    "previousPoint": {
        "href": unchanged,
        "type": unchanged,
        "position": {
            "latitude": latitude of vessel,
            "longitude": longitude of vessel
        }
    }
}

3.8 Set arrival circle.


Use case: Provide ability to set the radius of a circle centered at destination indicating arrival.

Action: PUT

Path: <self>/navigation/course/arrivalCircle

Request body:

{
    "value":  500
}

where:

This will result in the following Signal K path values being set:

{
    "activeRoute": {
        "href": unchanged,
        "startTime": unchanged
    },
    "nextPoint": {
        "href": unchanged,
        "type": unchanged,
        "position": {
            "latitude": unchanged,
            "longitude": unchanged
        },
        "arrivalCircle": supplied value
    },
    "previousPoint": {
        "href": unchanged,
        "type": unchanged,
        "position": {
            "latitude": unchanged,
            "longitude": unchanged
        }
    }
}

3.9 Query current course details.


Use case: Provide "get current course", "get course details" operation.

Action: GET

Path: <self>/navigation/course

Response: JSON formatted object containing the current course details as per the following example:

{
    "activeRoute": {
        "href": "/resources/routes/urn:mrn:signalk:uuid:0d95e282-3e1f-4521-8c30-8288addbdbab",
        "startTime": "2021-10-23T05:17:20.065Z",
        "pointIndex": 2,
        "reverse": false,
        "pointIndex": 2
    },
    "nextPoint": {
        "href": null,
        "type": "RoutePoint",
        "position": {
            "latitude":-29.5,
            "longitude":137.5
        }
    },
    "previousPoint": {
        "href": null,
        "type": null,
        "position": {
            "latitude":-29.05,
            "longitude":137.75
        }
    }
}

4. Signal K Stream Deltas

The implementation of the Course API requires that the relevant delta messages are sent for the in-scope Signal K paths when:

The absence of a delta for a specific Signal K path indicates that the path has never had a value assigned to it.

Where a delta value is null, this indicates that a previous value is no longer valid (i.e. there is a provider for this path but there is no current value available).

Delta messages for in-scope paths are as follows:

[
    {
        "path": "navigation.courseGreatCircle.activeRoute.href",
        "value": reference to route resource,
        "context": "vessels.self",
        "source": source of value
    },
    {
        "path": "navigation.courseGreatCircle.activeRoute.startTime",
        "value": Time at which route was activated (as per Signal K schema),
        "context": "vessels.self",
        "source": source of value
    },
    {
        "path": "navigation.courseGreatCircle.nextPoint.position",
        "value": {
            "latitude": number,
            "longitude": number
        },
        "context": "vessels.self",
        "source": source of value
    },
    {
        "path": "navigation.courseGreatCircle.nextPoint.value.href",
        "value": reference to waypoint resource,
        "context": "vessels.self",
        "source": source of value
    },
    {
        "path": "navigation.courseGreatCircle.nextPoint.value.type",
        "value": string,
        "context": "vessels.self",
        "source": source of value
    },
    {
        "path": "navigation.courseGreatCircle.previousPoint.position",
        "value": {
            "latitude": number,
            "longitude": number
        },
        "context": "vessels.self",
        "source": source of value
    },
    {
        "path": "navigation.courseGreatCircle.previousPoint.value.href",
        "value": reference to waypoint resource,
        "context": "vessels.self",
        "source": source of value
    },
    {
        "path": "navigation.courseGreatCircle.previousPoint.value.type",
        "value": string,
        "context": "vessels.self",
        "source": source of value
    }
]
tkurki commented 2 years ago

This is really commendable work!

I think we should also include what deltas are sent. For example if I am running several UIs, like multiple instances of Freeboard, I want to keep them in sync. A practical use case would be setting the course down below in the use navigation station and the settings reflected at the helm.

Do we want to keep somehow track of who set the course? For example the user, or a course computer advancing the route as the vessel makes way along the route (in the latter case it would be useful to know who originally set the route to follow).

Is it useful to have the irrelevant fields as ´null` or should they be just undefined / not there at all?

How about advance/go back one waypoint?

What about the sequence

I am not sure if we have really used hrefs anywhere in anger yet. As the SK http paths are mounted at /signalk/v1/api should there be a mechanism to indicate that the path in href is relative to that, or should there be something in the begining of the path, denoting that this is the case and the path does not start at the http server's root? Or should the hrefs be relative?

panaaj commented 2 years ago

I think we should also include what deltas are sent. For example if I am running several UIs, like multiple instances of Freeboard, I want to keep them in sync. A practical use case would be setting the course down below in the use navigation station and the settings reflected at the helm.

Agreed. I will add something to the README.md document in apis/course (which can be used as the documentation for this API).

panaaj commented 2 years ago

Do we want to keep somehow track of who set the course? For example the user, or a course computer advancing the route as the vessel makes way along the route (in the latter case it would be useful to know who originally set the route to follow).

Have added a source attribute to hold a value representing who made the change.

panaaj commented 2 years ago

Is it useful to have the irrelevant fields as ´null` or should they be just undefined / not there at all?

The convention to date seems to have been:

This convention seems to work quite well currently.

panaaj commented 2 years ago

How about advance/go back one waypoint?

Was covered in course/activeRoute/nextPoint API by the request being able to contain either:

  1. increment attribute the value of which can be 1 (increment) or -1 (decrement)
  2. pointIndex attribute which sets the specific point in the route

Have separated this out to two APIs:

Both of these APIs will consider the value of the reverse attribute value provided when the route was activated.

panaaj commented 2 years ago

What about the sequence

  • activate route with waypoint 0
  • steer the boat so that it is near, but not really at waypoint 0
  • forward the course, so that the active waypoint is 1 now previousPoint should be waypoint0, shouldn't it? And XTE calculated between vessel position and the line between points 0 and 1.

My understanding is that XTE works on source and destination position.... so if the vessel source position was only proximal to the destination location prior to advancing to the next point....... would using the route point position as the source instead of the vessel position negatively impact the XTE calculation significantly?

What does this mean for autopilot operation, etc?

panaaj commented 2 years ago

I am not sure if we have really used hrefs anywhere in anger yet. As the SK http paths are mounted at /signalk/v1/api should there be a mechanism to indicate that the path in href is relative to that, or should there be something in the begining of the path, denoting that this is the case and the path does not start at the http server's root? Or should the hrefs be relative?

From the relevant part of the specification the implication is that it's relative to signalk/v1/api... I'm OK with that based on the work done with Freeboard and resources so far.

Extract from navigation.course.activeRoute.href

"href": {
  "description": "A reference (URL) to the presently active route, in resources.",
  "example": "/resources/routes/urn:mrn:signalk:uuid:3dd34dcc-36bf-4d61-ba80-233799b25672",
  "type": "string"
 }
tkurki commented 2 years ago

https://www.manualsdir.com/manuals/199085/raymarine-chartplotter.html?page=48&original=1

Relevant features:

tkurki commented 2 years ago

Re: XTE: forget my example of being near or far from the waypoint. If we are navigating a route imho XTE would be the perpendicular distance between current position and the route leg, defined by two waypoints of the route, that we (or the autopilot) has set to be nextPoint and previousPoint. Does not really matter what our position is - XTE can be calculated even when we are behind previousPosition.

The only exception would be "restart XTE" case.

panaaj commented 2 years ago

Added restart api endpoint and updated description of previousPoint to align.

panaaj commented 2 years ago

Added arrivalCircle endpoint to enable setting the radius to determine arrival at destination. Also added arrivalCircle attribute to destination and activeRoute endpoints.

tkurki commented 2 years ago

Do we want to include specifying the notifications for entering arrival circle and passing waypoint perpendicular, or is that going into autopilot functionality?

sbender9 commented 2 years ago

I don't love the "PUT" or "POST". I think these should all be PUT, or at least pick one.

panaaj commented 2 years ago

Do we want to include specifying the notifications for entering arrival circle and passing waypoint perpendicular, or is that going into autopilot functionality?

I think that it's worth specifiying at least the path of the notification that would be raised. e.g. notifications.navigation.course.arrivalCircleEntered

panaaj commented 2 years ago

I don't love the "PUT" or "POST". I think these should all be PUT, or at least pick one.

I'm OK with just PUT.

panaaj commented 2 years ago

Updated OpenAPI definition to remove POST operatrions. Updated the README.md to reflect these changes as well as general formatting and readability.

panaaj commented 2 years ago

This is more in the realm of implementation, but the frequency at which deltas are sent and whether the course values are persisted, so they survive a server restart, all contribute to expected behaviour. Are these worth including?

panaaj commented 2 years ago

I am adding a bunch of comments, see what you make of them.

I am starting to think that it would be useful to create the server implementation and a UI to exercise this API before merging this. While this sounds pretty straightforward the devils in the details and they are hard to get right just working in the abstract.

I have been mocking this up by extending the freeboard-sk-helper plugin and a modified sk-resources-fs plugin to allow route details to be retrieved. I can confirm the proposed operation.

As far as embedding the course API into the server it will first require the resource path handling functionality to enable the courseAPI to retrieve route details.

I have working skeleton of the resources path handling as described in server issues #1351 .

If it is the correct protocol I can push this branch to signalk-server repository.

tkurki commented 2 years ago

If it is the correct protocol I can push this branch to signalk-server repository.

Please do. I had already trouble looking for PR 1351 and then realised it was an issue, not a PR.

Collaboration is easier when working off the same repo: a bit less work with checking out work locally and at least trivial corrections like typos need not go via comments. Can also collaborate on stuff where we are not stepping on each other's toes.