Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: V2 Autopilot API #1596

Open
wants to merge 35 commits into
base: master
Choose a base branch
from
Open

feature: V2 Autopilot API #1596

wants to merge 35 commits into from

Conversation

panaaj
Copy link
Member

@panaaj panaaj commented Aug 11, 2023

Version 2 Autopilot API:

Goals:

  • Implement an API (with OpenAPI definition) for performing common autopilot operations.
    • Support commands being sent to specific autopilot devices (using the autopilot identifier) when more than one autopilot device is available.
    • Provide a path to a "default" autopilot device (the device currently in control of the vessel in a multiple device implementation) to insulate client applications from knowing which device is active.
    • Provide a list of available autopilots identifying the "default" device.
  • Emit deltas to the steering.autopilot path with $source = the autopilot device identifier
  • Emit deltas with $source = default containing values from the default autopilot device
  • Provide an interface for autopilot plugins
  • Interoperate with input stream providers (e.g. NMEA / SeaTalk) to manage the processing of autopilot data in the Signal K data model.

API will compliment these Signal K Server features to support autopilot operation:

  • Course API: To set the destination and enable course data calculations to create a cohesive data set for navigation operations.
  • signalk-to-nmea0183 plugin: Deliver the necessary NMEA sentences APB (for route control) and MWV (for wind steer) in the NMEA0183 data stream.

Overview:

autopilot_provider

Features:

  • API operations under the path /signalk/v2/api/vessels/self/steering/autopilots
  • Target operations to specific devices using /signalk/v2/api/vessels/self/steering/autopilots/{device_id}/*
  • Target operations to default device using /signalk/v2/api/vessels/self/steering/autopilots/default/*
  • Set the default device using /signalk/v2/api/vessels/self/steering/autopilots/defaultAutopilot/{device_id}
  • Server Plugin Interface
    • Enact API requests
    • Notify API of value changes originating from the autopilot device
    • Accept alarms from autopilot device.
  • Manage emission of deltas with path steering.autopilot
  • Define set of normalised alarm / notification path names.
  • Interoperation with NMEA / SeaTalk stream data handlers for incoming data originating from autopilot(s) plugin
  • Rudder control operations
  • Autopilot device connection detection plugin sets state to off-line
  • Notifications

Plugins (under development):

Related links:
Course API
Course API Definition
signalk-autopilot issue 1
Extending autopilot paths

@panaaj panaaj added the feature label Aug 11, 2023
@panaaj
Copy link
Member Author

panaaj commented Oct 8, 2023

@sbender9 would appreciate your feedback as to any forseen issues with the proposed API based on the current signalk-autopilot plugin functionality.
The API and plugin operations are detailed in the associated docs files.

@panaaj
Copy link
Member Author

panaaj commented Oct 8, 2023

@seandepagnier would appreciate your feedback with regards to the "Signal K aware" pilot scenario where the plugin requirements are more focussed on sending commands.

Just as an FYI, Freeboard-SK implements a PoC PyPilot plugin that requires pypilot_web to be running to provide the websocket endpoints where the:

  • state & mode options are discovered
  • current values are retrieved
  • state, mode and heading_command are set.

I'm not sure if this is the preferred method, but was a convenient method for the PoC.

@panaaj panaaj changed the title [WIP] feature: V2 Autopilot API feature: V2 Autopilot API Oct 8, 2023
@panaaj panaaj requested a review from tkurki October 15, 2023 04:35
@tkurki
Copy link
Member

tkurki commented Oct 23, 2023

I think this is a good start, but I see several issues here:

  • implementation (of access control) is now in server-api and exported there => I would find that confusing as a plugin author, since there is actually no api for plugins for this
  • shouldn't we have v2 api wide access control? why do it per api, as it is working per http method?
  • we have thought about "multiple instances of X" previously as an afterthought. Having multiple autopilots is a totally realistic scenario, so we should take that into account
  • PR description says validation but I can't see any?
  • I am not yet convinced that "server does validation but plugin taps into http" is the best method forward. OpenApi maps to a ts api, why not provide that? Or: please convince me this is the best method going forward
  • you mentioned APB and MWV - they are dependent on wind & course data, not autopilot control. What exactly did you mean with that?
  • from the diagram I first thought that the autopilot API talks with the autopilot plugin via http, when it is just next(), right?

I remember you mentioning that you have a PoC of pypilot autopilot plugin - is it somewhere to be found? All in all I think it would be a good idea to work this out end to end: have a working or at least conceptually sound autopilot plugin wired together with this and maybe even a rudimentary standaloe web ui at the other end. In the past we have multiple cases where we worked from the specification that turned out to be more theoretical than practical.

@panaaj
Copy link
Member Author

panaaj commented Oct 23, 2023

I remember you mentioning that you have a PoC of pypilot autopilot plugin - is it somewhere to be found? All in all I think it would be a good idea to work this out end to end: have a working or at least conceptually sound autopilot plugin wired together with this and maybe even a rudimentary standalone web ui at the other end.

This API is implemented in Freeboard-SK today to interface with PyPilot. v2.3.0 branch is a more complete implementation.
Autopilot API code is implemented in pypilot.ts and configured via Plugin Config.

  • you mentioned APB and MWV - they are dependent on wind & course data, not autopilot control. What exactly did you mean with that?

They are mentioned in the context that autopilot operation is already supported by Signal K through the signalk-tonmea0183 plugin ( and also Course API).... so this API builds on this.

  • from the diagram I first thought that the autopilot API talks with the autopilot plugin via http, when it is just next(), right?

Yes.. as per the text uses next()

  • we have thought about "multiple instances of X" previously as an afterthought. Having multiple autopilots is a totally realistic scenario, so we should take that into account

Originally this API was based on the Resources API model that supports multiple providers.
The implementation for autopilot API would depend on the operation model....e.g. does command go to all autopilots.. and / or ... the command is sent to individual autopilots by referencing their uuid?

@seandepagnier
Copy link

I am following this, and dont have much to add, except that, yes, I do have 2 pypilot installed on my boat. Generally one of them is off to save power, but it is essentially a backup pilot.

Right now, if one pypilot has a wind sensor attached, the other pypilot can use it if both pypilots are connected to the same signalk server.

It would be possible for example for each pilot to be wired to a wind sensor. Then if both pilots are on, whichever wind sensor is to windward could be used. There are other examples, but supporting multiple autopilots is a good idea, however be aware that multiple pypilots in the future may detect each other and communicate directly (outside of signalk) as well.

@panaaj
Copy link
Member Author

panaaj commented Oct 27, 2023

With regards to operation with one or more autopilots I see the following scenarios:

  1. Single plugin -> one autopilot
  2. Single plugin -> multiple autopilots
  3. Multiple plugins, each with one or more autopilots

Given the above scenarios, the breadth of devices and that the plugin is managing communication with the autopilot I see the following options:

  1. The API sends requests to all registered provider plugins.
    In this case the plugins will need to sort out what autopilot is sent the command.
    Will need to provide a mechanism for provider plugins to share who has / should have control
    Pro: all autopilots are potentially in the correct state for failover.
    Con: more than one autopilot could be engaged simultaneously.
  2. Provide a mechanism for the plugin to flag that it is the primary plugin and send the commands to it.
    Con: Failover in Scenario 3 above would mean backup autopilot may not be in the correct state.
  3. Only support one active provider plugin (first one registered)
    Con: Requires user to enable backup plugin, backup autopilot will not be in the correct state.

I'm leaning towards option 1. send commands to all plugins and let them sort it out.

@tkurki
Copy link
Member

tkurki commented Oct 28, 2023

I've been mulling this over, here are my thoughts:

Looking at the API definition and the api routes it looks like the Autopilot api would not be overly complex in TypeScript - it would look like this

interface AutoPilot {
  getData() => Promise<ApData>
  getState() => Promise<ApState>
  setState(s: ApState) => Promise<void>
  getMode() => Promise<ApMode>
  setMode(m: ApMode) => Promise<void>
  setTarget(target: Number) => Promise<void>
  adjustTarget(d: Number) => Promise<void>
  engage: () => Promise<void>
  disengage: () => Promise<void>
  tack(d: TackGybeDirection) => Promise<void>
  gybe(d: TackGybeDirection) => Promise<void>
}

or am I missing something?

Leaving routes implementation, validation and emitting deltas to each autopilot integration plugin sounds like a recipe for having as many different behaviors & intrepretations of how exactly the whole thing works as there are autopilot integration plugins.

Why would we not got the "provider" way? Meaning have the server implement routes, validation, emitting deltas and access control and then delegate the operations to the actual AP plugin integration code?

This would decouple the AP integration from the server, leaving configuring and routing autopilots to the server.

As for the multiple autopilot case: I think the plugins will need to sort out is not a good enough strategy. Take for instance just the engange operation: the user most definitely must decide what autopilot the operation is about.

In this case the REST resource structure would actually work pretty well: mount each autopilot's api at /signalk/v2/api/vessels/self/steering/autopilot/<autopilot_id>/. And a top level API to get a list of available autopilots (and their states?)

In general I think it is a good idea to sort the multiple instances case sooner rather than later, even if out there the single cases will far outnumber the multiple ones and it is kind of a sidetrack. Retrofitting support afterwards is much harder.

We need also a way for an autopilot integration to send updates about changes coming from non-SK controlled autopilot devices: the user adjusting the target or mode via pushbuttons on the unit or for N2K devices over CAN, Sean's comment about autopilots changing state outside the SK APIs control etc. So the server api should provide a way for the ingeration to emit autopilot state updates (deltas?).

As for multiple autopilot integrations of a single kind: the idea for adding support for instantiating a plugin multiple times, with multiple configurations has been there for quite a while. Maybe it's time for that? People have requested for example multiple separate configurations for sk-to-nmea0183.

BTW I think APB and MWV are not really autopilot control: APB is 100% derived from the data in SK Course api and vessel position and heading and MWV is just wind sensor data.

Did you put the pypilot code in Freeboard for convenience? Seems like an odd tangle, when it could as easily be an independent plugin. Or are you waiting for the capabilities API?

@panaaj
Copy link
Member Author

panaaj commented Oct 29, 2023

Did you put the pypilot code in Freeboard for convenience? Seems like an odd tangle, when it could as easily be an independent plugin. Or are you waiting for the capabilities API?

Convenience mainly, it exists as an experiment inside Freeboard-SK to prove out the user story:

"As a SIgnal K App want to be able to access autopilot status and options without needing to know specifics of the autopilot in use so that I can display the cuurent status, make selection from a list of valid options and perform common actions that are sent to the autopilot."

As per your comment, it is not overly complex and in it's current state provides the necessary functionality which is implemented as per the following screenshot (as of v2.3.0).
image

Operationally, I think it is where it needs to be.... but that's just me.

The choice to use PyPilot was also intentional, to simplify the use case by removing NMEA from the equation. Once operational requirements are proved out, this will inform what changes (if any) are required to the way NMEA messages are processed to support operation.
At present they are just decoded and deltas emitted to populate the model which is quite disconnected from API operation (also impacts the Course API).

Maybe this can be addressed at the same time as

adding support for instantiating a plugin multiple times, with multiple configurations

With regards supporting multiple auotpilot devices, what ever the solution, as long as the client app can:

  1. Get the valid options to set for state & mode
  2. Get the current, mode, state & target values
  3. Determine whether the autopilot is engaged or not
  4. Send commands to perform operations without having to know which device to send it to.

@seandepagnier
Copy link

Consider the common use case of two devices running opencpn or avnav, lets say a raspberry pi running signalk server and pypilot, and a laptop.

If a route is activated in opencpn, normally it sends commands to steer the autopilot without needing signalk. However, only one instance of opencpn can do this at a time. There is some integration between opencpn instances, but if the idea is to make this universal, it must work with other plotters, like avnav and communicate with signalk.

So for example, if you activated a route in opencpn, it would send the route via signalk, and other instances of other plotters would see this, create the route and activate it similarly. If the route is de-activated from any plotter it would deactivate from all of them, across plotters, and autopilots.

The use case of multiple plotters and a single pilot is much more common. Typically multiple autopilots are for backup and only one is even powered on at a time.

@tkurki
Copy link
Member

tkurki commented Oct 30, 2023

So for example, if you activated a route in opencpn, it would send the route via signalk, and other instances of other plotters would see this, create the route and activate it similarly. If the route is de-activated from any plotter it would deactivate from all of them, across plotters, and autopilots.

No disagreement from me! Sadly all interest in OpenCPN seems to be in integrating O instances, not universal APIs and communications (Headless use case, Route sharing). Now that O has the networking infrastructure for http and ws the foundation is there, but nobody seems to be building on it. Any ideas how to promote cross app cooperation appreciated.

But that is mostly about course api (activating route, advancing to next waypoint etc), not the Autopilot api (engange/disengage, change mode) under discussion here. They are pretty tightly related, but separate: you can use a an autopilot without a plotter and a plotter without an autopilot.

@seandepagnier
Copy link

Another thing to consider is 'trajectories' This is a logical advancement in route following. What this means, is the autopilot actually tries to follow spline or beizer curve based on the route rather than route segments.

It is something consider as there are a few more parameters, and the intended 'trajectory' should be rendered in the plotter.

Add ability to select the primary provider.
@tkurki
Copy link
Member

tkurki commented Nov 1, 2023

@seandepagnier Is there a way to run pypilot in ”test” mode so that from the outside it looks like everything works as it should even though no actual mechanism is connected? Would be useful for testing Adrian’s pypilot integration end2end on the desktop.

Convert the funky properties with function types to
methods.
@panaaj
Copy link
Member Author

panaaj commented Nov 7, 2023

So if assumption 1 is not valid, does that impact assumption 7?

@tkurki
Copy link
Member

tkurki commented Nov 10, 2023

can come up with mostly theoretical setups where there are multiple actuators, like a main rudder and a windvane type auxiliary feathering rudder.

But that is not the point. I'd like think that goal here is to come up with a an API that does not make too rigid assumptions but still makes the the basic cases easy. To me this includes

  • being able to explicitly address a certain autopilot in a system with multiple pilots. A real world scenario could be setting that target heading for the currently inactive ap, then disengaging the current one and engaging the other
  • being able to discern which of multiple autopilots in the system the state update is for. A practical example would be the user using keys to set the target heading, without engaging the autopilot
  • being able to control ap without always needing to explicitly target the single autopilot in the system
  • if we have for example 2 pypilots i'd want to render a UI that presents state for both of them and allows controlling them independently, without the notion of active / primary coming in the way
  • weird real world cases, not just the happy path. A not so far fetched scenario could an AP that is mechanically disconnected and one that is engaged & active and you'd want to control the disconnected one to test its behavior

So please look beyond "but it does not make sense to engage /have in control more than one autopilot". I believe my points above invalidate assumptions 1, 2 and 7.

@panaaj
Copy link
Member Author

panaaj commented Nov 11, 2023

  • being able to control ap without always needing to explicitly target the single autopilot in the system

So I interpret this as a client just being able to PUT 'autopilot/target' or GET 'autopilot/target'.... right?

Maybe the wording was a bit clumsy but this is the intent of assumption 2.

@seandepagnier
Copy link

But that is not the point. I'd like think that goal here is to come up with a an API that does not make too rigid assumptions but still makes the the basic cases easy.

I agree with this statement.

  • being able to explicitly address a certain autopilot in a system with multiple pilots. A real world scenario could be setting that target heading for the currently inactive ap, then disengaging the current one and engaging the other

This is unrealistic mainly because engaging autopilots almost always sets the target heading to the current heading. Changing course and pilot at the same time is possible but I don't think it is a very useful operation or very important.

  • being able to discern which of multiple autopilots in the system the state update is for. A practical example would be the user using keys to set the target heading, without engaging the autopilot

This is the same as the first point again. Normally you would engage the pilot first, and then set the target heading. Please elaborate on why the opposite order (that you suggest) is useful.

About multiple autopilots:
Generally, only one is used at a time, and in most cases, there is no real reason to waste power running additional autopilots even if they are in standby. The example of one pilot on the rudder, and another on a pendulum oar or trim tab is a good one, but generally I would prefer to choose which pilot to use by using the power switch to turn the other one off to conserve power. It would however be a good idea to design the signalk interface to support both at the same time so that you can switch back and forth with software too.

The most obvious use cases for actually powering up multiple pilots would be for example:
hydraulic steering with 2 hydraulic pumps, one on each autopilot. If the 'standby' pypilot detects the primary pypilot is failing to move the rudder and keep the boat on course, it can automatically 'take over' and hold the course until it detects the primary pypilot is active and making corrections. This would allow any of the primary pilot, pump, electronics, to fail at any time and the boat would not round up, jibe, etc. Furthermore, if both pilots are operational, both pumps could run at the same time to give faster corrections which means one pilot is the controller and the standby pilot the 'worker'

If we can map out all the actual use cases that make sense we can ensure the api supports these. What other reasons would multiple pilots be used?

@tkurki
Copy link
Member

tkurki commented Nov 11, 2023

Just so that it is not just me coming up with weird, unrealistic scenarios: people do have multiple autopilots fully installed https://forums.sailinganarchy.com/threads/autopilot-backup-install-or-spares.225694/

@tkurki
Copy link
Member

tkurki commented Nov 11, 2023

Again: my point is that we should not build assumptions like engaging autopilots always sets the target heading. You did not say that: if we assume your engaging autopilots almost always sets the target heading then the API should be flexible enough to support cases where the target is not automatically set.

map out all the actual use cases that make sense

I think we can list use cases, but it is foolish to assume that we can map all use cases. Some use cases may not even make sense (like having multiple active ap's), but arise from the messy real world (user errors, flaky comms, race conditions etc).

Rereading my own comments I think we should be in pretty good shape re: multiple aps if the api supports

  1. being able to explicitly address a certain autopilot in a system with multiple pilots
  2. being able to discern which of multiple autopilots in the system the state update is for
  3. being able to control ap without always needing to explicitly target the single autopilot in the system

Moving forward towards solutions...I think we have several ways to solve these

  • put device identity in the path: steering.autopilot.* => steering.autopilot.apId.*. I don't think we need to reflect how the sk server works in how the adId is formulated, just that the system needs to ensure uniqueness. This works great in http API and is "restful", but is a bit cumbersome in deltas, where you can't use the path by itself but need to extract the id. This method is used in Signal K v1 in various places, like batteries and propulsion/engines. To make single ap systems simple we can use the vessels.self alias pattern: reserve the steering.autopilot.default.* paths for "i don't need to know the id since there is just one" use cases.
  • use the $source field to to identify the autopilot. This would mean an extra parameter in the http APIs. Making that parameter optional would make the single ap use case very simple. Delta paths would be simple and explicit. I think this is actually something that we should do anyway
  • add a new instance (or something to that effect) field to deltas and http requests. $source seems a little awkward in http requests, as it is the target, not the source of the operation

I am wondering if it is time to break the SK v1 mechanism where delta paths map 1:1 to http paths. In practise we could use steering.autopilot.apId.* paths in http and steering.autopilot.* paths with identity in $source in deltas. With v2 we have more freedom and the http api is separate from deltas.

Sidenote: I don't think we want to reflect the internals of SK server in the API, so ap's in the API should have just a single identifier, not providerId.deviceId. We can internally derive the identifier from plugin id and instance id. But if for example pypilot would implement this natively the two parts would not make much sense.

Oh I can come up with one more desirable API feature: steer developers dealing with the API to think about multiple APs and not just build "oh there's only ever going to be one" systems.

btw thanks for insightful discussion! I feel we are making progress here.

@panaaj
Copy link
Member Author

panaaj commented Nov 11, 2023

FYI resources API uses the approach of a query parameter to target the provider.

GET ./resources/routes?provider=myproviderid

@tkurki
Copy link
Member

tkurki commented Nov 11, 2023

Yep. Maybe we could have picked a little bit more generic name for that parameter, but I think the abstraction there is correct and not too tied to current SK server.

@seandepagnier
Copy link

  • use the $source field to to identify the autopilot. This would mean an extra parameter in the http APIs. Making that parameter optional would make the single ap use case very simple. Delta paths would be simple and explicit. I think this is actually something that we should do anyway
    I think this is the obvious way to support everything. Specify a source
  • add a new instance (or something to that effect) field to deltas and http requests. $source seems a little awkward in http requests, as it is the target, not the source of the operation

Whichever is more clean, but a way to specifically address a particular autopilot vs broadcast to all pilots. In the case of broadcasting the signalk server may need to be aware of which pilot is "active" and for this it might need a separate key to allow clients to inform the service which pilot to use so that broadcast requests without a particular target will only enable the primary autopilot.

Allow plugin to register as the Default Provider.
Delta source value contains provider id  and device id
Delta source value is `autopilotApi` for updates from defaultProvider.
Target provider in API requests using query parameter.
Add device id to API paths
Change API  path to `steering/autopilots` to align with id in path.
Updated OpenAPI definitions
Updated docs.
@panaaj
Copy link
Member Author

panaaj commented Nov 28, 2023

Mock providers have been removed.
The following WIP plugins

have been updated to align with this PR and can be used for testing and / or template.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants