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

Merged
merged 2 commits into from
Dec 15, 2024
Merged

feature: V2 Autopilot API #1596

merged 2 commits into from
Dec 15, 2024

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 for installations with one or multiple autopilot devices.
  • 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.
  • Support commands being sent to specific autopilot devices (using the autopilot identifier) when multiple autopilot devices are available.
  • Provide a list of available autopilots identifying the "default" device.
  • Emit deltas to the steering.autopilot path with $source = the autopilot device identifier
  • Emit delta steering.autopilot.default to notify clients when the default autopilot device is set / changed
  • 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:

  • = endorsed and implemented.

REST API

  • API root path: /signalk/v2/api/vessels/self/steering/autopilots
  • Angle values can be supplied in requests in either radians (default) or degrees
  • List autopilot providers (indicating _default): GET /signalk/v2/api/vessels/self/steering/autopilots
  • Target operations to specific provider: /signalk/v2/api/vessels/self/steering/autopilots/{device_id}/*
  • Target operations to default provider: /signalk/v2/api/vessels/self/steering/autopilots/_default/*
  • Set the default device: POST /signalk/v2/api/vessels/self/steering/autopilots/_providers/_default/{device_id}
  • Retrieve Autopilot device options: GET /signalk/v2/api/vessels/self/steering/autopilots/{device_id}
  • Set mode / state: PUT /signalk/v2/api/vessels/self/steering/autopilots/{device_id}/mode
  • Set target PUT /signalk/v2/api/vessels/self/steering/autopilots/{device_id}/target
  • Generic engage / disengage operations: mode / state managed by provider based on device capabilities
  • Rudder control / dodge operations

DELTAS

  • Delta base path: vessels.self.steering.autopilot.* with $source set to the device_id
  • Emit delta tovessels.self.steering.autopilot.default when default device is set / changed
  • Manage the emission of deltas: API controls the sending of deltas. What about n2k-signalk?

Notifications

  • Normalised alarm paths / names.

Server / Provider Interface

  • Enact API requests
  • Method for provider to send Updates / Notifications from the autopilot device via the API interface

Provider

  • Autopilot device connection detection: Set state to off-line
  • Communication with device
  • Send updates / notifications from the pilot to API

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.

@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.

@tkurki
Copy link
Member

tkurki commented Nov 3, 2023

I think we are missing a few use cases:

  • rudder control: using the ap mechanism to control rudder, without any ap logic involved. Like when my tillerpilot is there and powered, but just holding the tiller, and I can use + and - to control the rudder.
  • ”Is the autopilot there” as in is it powered on and reachable. Or is this already covered?

@panaaj
Copy link
Member Author

panaaj commented Nov 4, 2023

  • rudder control: using the ap mechanism to control rudder, without any ap logic involved. Like when my tillerpilot is there and powered, but just holding the tiller, and I can use + and - to control the rudder.

From an API perspective you could just use the target/adjust endpoint.
The plugin could take the appropriate action, especially if this feature is not common to all autopilots.

  • ”Is the autopilot there” as in is it powered on and reachable. Or is this already covered?

I feel this can be reflected in the GET ./steering/autopilot response...if the provider cannot contact the autopilot then the AutopilotInfo response could contain null values..

      {
        options: {
          states: [],
          modes: []
        },
        target: null,
        state: null,
        mode: null,
        engaged: null
      }

@panaaj
Copy link
Member Author

panaaj commented Nov 5, 2023

There needs to be a mechanism for the plugin to provide updates from the autopilot via the plugin to the API.... traditionally a plugin would use handleMessage() and emit a delta but bypassing the API is probably not the ideal solution.

Perhaps include a method e.g. apUpdate() in the interface. This way the Autopilot API can receive the value, perform the necessary processing and emit the necessary deltas.

export interface AutopilotApi {
  register(
    pluginId: string,
    provider: AutopilotProvider,
    primary?: boolean
  ): void
  unRegister(pluginId: string): void
  /* method for plugin to provide Autopilot API updated values from autopilot */
  apUpdate(pluginId: string, attrib: string, value: any): void
}

pluginId: API should only process updates coming from the plugin that is the primaryProvider
attrib: the attribute whose value has changed e.g. mode, target, state, options, etc.
value: the new value.

@tkurki
Copy link
Member

tkurki commented Nov 5, 2023

I agree on the AutopilotProvider needing the ability to send updates. Isn't the basic case being able to update AutopilotInfo? Per above: "connection to the ap killed, reset state" or "somebody turned the ap on, now i see it, set state to reflect this". Several ways how to structure this in code, for example AutopilotProviderRegistry could have this method.

I don't think we can build things so that in SK land there is only a single, primary autopilot. The user may press disengage on one pilot and engage on another and we need to be able to deal with that. All in all I don't think the current method where there is only one connected (not "primary" but "only this one is connected") is flexible enough for even the simplest cases with >1 autopilots.

From an API perspective you could just use the target/adjust endpoint.

Target is separate from rudder control. Example use case:

  • I see something floating up ahead
  • I disenagage the autopilot, keeping tillerpilot in place
  • I use rudder control to steer to the side/around the obstacle
  • once clear I want to return to the same target heading (assuming my destination is far enough and don't need to adjust the target)

@panaaj
Copy link
Member Author

panaaj commented Nov 7, 2023

  • rudder control: using the ap mechanism to control rudder, without any ap logic involved. Like when my tillerpilot is there and powered, but just holding the tiller, and I can use + and - to control the rudder.
  • ”Is the autopilot there” as in is it powered on and reachable. Or is this already covered?

Added to feature list (at top) for implementation.

@panaaj
Copy link
Member Author

panaaj commented Nov 7, 2023

I'd like to focus the discussion on operation with multiple autopilots.

Firstly some assumptions (feel free to correct any that are not correct):

  1. Only one autopilot device will be "in control" of the vessel at any time
  2. Consumers of the Autopilot API do NOT need to know which autopilot device is "in control" when making requests.
  3. A provider plugin will manage the the communication with the autopilot device
  4. One or more autopilots may be managed by a provider plugin
  5. The provider plugin will keep the API updated with value changes stemming from operations initated on the autopilot device.
  6. Deltas with a path that is in scope of the Autopilot API will be emitted by the API.
  7. Values returned by the API and emitted in deltas will reflect the status of the "in control" autopilot device (whether it is engaged or not).

So here is an attempt at expected operation:

  • User installs provider plugin(s) for the autopilot devices in use.
  • At server / plugin start, plugins register as a provider

1. Autopilot device led operations:

  • If a plugin detects an AP it manages is "engaged" it updates the API
  • API treats this provider as "in control" and is the source of values in query results and deltas.
    Qu. If no AP is engaged and API is queried what values should be returned? Values from the first provider registered?
  • API operations are sent to the "in control" provider.
  • If user disengages the autopilot the provider remains "in control" until another AP device is engaged
    Qu. What happens if two AP's are engaged at the same time? Should the API ignore the new "engaged" AP until an update marking it as "disengaged" is received?

2. For API lead operations:

  • Query requests will return values from "in control" provider
  • Operation request will be sent to "in control" provider
    Qu. What is the sequence of events when changing away from the "in control" provider via an API call when the AP is engaged? Disengage first then change?

Does this cover the MVP for Autopilot API operation?

@tkurki
Copy link
Member

tkurki commented Nov 7, 2023

Only one autopilot device will be "in control" of the vessel at any time

Why do we need or want to make this assumption? I honestly think that the api would be simpler and more flexible to different real world scenarios if the autopilots work independent of each other. Like if the user engages multiple APs outside the api's control per your scenario above.

@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.

@panaaj
Copy link
Member Author

panaaj commented Nov 28, 2023

The following WIP plugins

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

@tkurki
Copy link
Member

tkurki commented Dec 14, 2024

I squashed the branch to a single commit, as the history was pretty messy and not worth preserving. Befofe overwriting I pushed the original as v2_api_autopilot_orig, in case I messed something up.

@panaaj could you please review that everything seems to be in order here in the squashed version?

Correct value to contain `autopilots` not `autopilot`
@panaaj
Copy link
Member Author

panaaj commented Dec 14, 2024

@panaaj could you please review that everything seems to be in order here in the squashed version?

It looks good.
I have posted a commit to fix a path value but that was not related to the "squash".

@seandepagnier
Copy link

It is great to see progress on this.

I am trying to understand the design concept because it seems there are 2 possibilities for pypilot.

  1. using a signalk plugin like this, signalk server now can communicate directly with pypilot
  2. without a signalk plugin, pypilot can communicate directly with signalk, but needs to receive autopilot specific keys. This was always the eventual intention but it could not be implemented because the autopilot API was unclear. I would also want to handle route following this way completely avoiding nmea0183 if possible. Eventually things could get more complex and it is likely that pypilot-specific signalk keys are needed because features that extend beyond what maps into generic pilots may be useful to expose via signalk.

Currently pypilot does communicate with signalk for sensor data, like wind, gps and many others, but not directly for autopilot commands. It would be good to support autopilot commands. Are they ready? If so, which programs are you using to generate them? I am unaware of any, for example, a signalk-autopilot-pi for opencpn could be useful, but also via node red or? I need to do this to implement support. In this case a special signalk plugin (pypilot-autopilot-provider) would not be required as the signalk translation occurs within pypilot, and the pypilot_web is not needed.

I suggest implementing both approaches, and then comparing the results in real use. Eventually one or the other may be deprecated, but probably not for years, and in the meantime serves the goal of giving the user the maximum possible flexibility in configuring the system. I will have to put safeguards if the pypilot-autopilot-provider signalk plugin is active to ignore control of the autopilot through a direct signalk connection.

A direct connection via signalk may provide lower latency for manual control, but a signal plugin which directly sends UDP packets to pypilot (bypassing the web and signalk layer completely) could potentially offer lower latency. This is a reason to try both ways to find the best way for manual control. low latency gives the best responsiveness for open-loop manual control. With actual rudder angles sent rather than relative movement when rudder feedback sensor exists it is less of an issue, but always matters.

The other pilots, the n2k ones. It seems they also have 2 paths?
https://github.com/SignalK/n2k-signalk/
and
https://github.com/SignalK/signalk-autopilot/

Now both may be signalk plugins, but in both cases these plugins are converting signalk into n2k paths for autopilots. So is this by design? Are both used (one for input and one for output) or? I am not understanding why the n2k-signalk is not able to provide all the conversions needed.

@tkurki
Copy link
Member

tkurki commented Dec 15, 2024

@panaaj how about the task list in the PR description - lots of open items?

@tkurki
Copy link
Member

tkurki commented Dec 15, 2024

A bit of clarification:

  • n2k-signalk converts NMEA2000 to Signal K, including 129284 navigation data. Technically it is not a plugin, but a library the server depends on and that gets installed automatically as part of it
  • signalk-autopilot is the "old, v1" plugin for autopilot support

@panaaj has been working on autopilot providers and can fill in the details. Please have also a look at the descriptions Adrian wrote for this PR.

Meanwhile the idea is to merge this PR, publish a new server version with it and see how far it takes us.

@tkurki tkurki merged commit 8fdae2c into master Dec 15, 2024
3 checks passed
@tkurki tkurki deleted the v2_api_autopilot branch December 15, 2024 20:14
@panaaj
Copy link
Member Author

panaaj commented Dec 15, 2024

@panaaj how about the task list in the PR description - lots of open items?

These are all done. I was looking to tick them off after review to reflect the wider position not just mine.

@panaaj
Copy link
Member Author

panaaj commented Dec 16, 2024

I am trying to understand the design concept because it seems there are 2 possibilities for pypilot.

  1. using a signalk plugin like this, signalk server now can communicate directly with pypilot
  2. without a signalk plugin, pypilot can communicate directly with signalk, but needs to receive autopilot specific keys. This was always the eventual intention but it could not be implemented because the autopilot API was unclear. I would also want to handle route following this way completely avoiding nmea0183 if possible. Eventually things could get more complex and it is likely that pypilot-specific signalk keys are needed because features that extend beyond what maps into generic pilots may be useful to expose via signalk.

@seandepagnier
Certainly 1. is the approach for the MVP of the Autopilot API.
The focus has been to provide a means for clients to send common commands and get the status of the connected autopilot device(s) using a server plugin as a broker. This has been implemented by:

  • Exposing a set of REST endpoints to both query status, available options and send commands
  • Creating a plugin interface to facilitate information flow from:
    • REST endpoints -> Server API -> plugin -> AP device
    • AP device -> plugin -> Server API -> deltas

The details have been included in the server documention and OpenAPI definitions.

With regards 2. I interpret this as the "Signal K aware" autopilot or "no plugin" approach.
In it's simplest form the autopilot would implement the necessary functions that would normally be provided by the plugin via the server/plugin interface.

There is more work to do in this space and I would certainly be keen to open a dialog around this to determine how we can move forward. We have an autopilot channel in the SignalK Discord which would be the preferred way to capture ideas.

With regards avoiding NMEA0183, the Course API (which has been imlemented for about 18 months or so) exposes course information under the path navigation.course. It maintains ALL the necessary paths so they are consistent and can be relied on for getting the current destination and performing calculations:

  • navigation.course.activeRoute
  • navigation.course.nextPoint
  • navigation.course.previousPoint

Not sure if this completely covers that statement but FYI the documentation can be found here

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