One of the aims of the new Companion structure is to make it easier to develop, provide, install, and use integrations. This post provides a general overview of the structure (as it currently stands), and describes our design intent for how extensions can be integrated most effectively. The system isn’t finalised yet, and we welcome discussion into the methods suggested here, along with whether there are particular details/permissions/access that are important to cover/include, or perhaps whether some alternative integrations system would work better.
Current (Beta) Structure
1.1 Core Functionality
1.2 Frontend (Web Interface)
1.3 Integration Example - DVL
2.2 Installing/Getting Extensions
2.4 Versioning & Compatibility
2.5 Extending Core Functionality
The core Companion functionality is run within a Docker container, which allows it to stay isolated, and be updated independently of other code/services running on the device.
To those unfamiliar with Docker, it’s a way of keeping code environments/programs isolated while still having access to the underlying operating system. One isolated program block is called an “image”, which when run forms a “container”. Several containers can be run at once.
The web interface searches/scans for webpages (running on Companion) that are accessible via TCP ports which are configured to ‘listen’, and can be accessed by external users (server at IP
0.0.0.0). Those webpages are then listed at
≣/Tools/Available Services, along with documentation if they provide it:
As many of you are likely aware, the old Companion software has a beta integration of DVL functionality focused on the Water-Linked DVL-A50. While DVL support is undoubtedly useful, it’s device specific and also not something that everyone with an ArduSub device needs to use, so in the new Companion that is instead implemented as an external integration, which users can enable if it’s relevant.
@williangalvani has worked hard to make the Companion-DVL repository, which serves as an example of what can be done already. The relevant functionality (communication with the DVL, sending mavlink messages, etc) is implemented with Python, and there’s a web interface created using the Flask library.*
At the moment installation is handled by ssh’ing into the Companion computer (
ssh email@example.com, password=
raspberry), and telling docker to run the desired container. Docker then tries to find it locally (on the device), and when that fails it searches the specified location in DockerHub (requires wifi) and downloads and runs it automatically. That process requires the Docker image of the relevant code (built using a
Dockerfile) to be pushed to and hosted on DockerHub.
*Implementation Note: Flask is just one possible way of creating a webpage. Many of Companion’s core services use uvicorn to serve FastAPI-based webpages (which are self-documenting). The Version Chooser service uses Connexion, which is a self-documenting wrapper around Flask. It’s also not a requirement to use Python if you don’t want to (e.g. the camera manager and mavlink2rest are both written in Rust).
The main web interface will have a dashboard, to display information from the most important services. We’re intending to make it possible to embed web views from custom services as part of that dashboard, so that custom integrations can be presented prominently if desired.
To facilitate embedded views we’ll likely need to define some form of registering interface, where a service can present itself as view-compatible, and the user can choose between available views/services to display.
The hope at this stage is to make some kind of ‘web store’ type page which allows a user to find and select integrations/extensions they’re interested in from within the Companion web interface. That will likely require some level of standardisation of the installation process for extensions, but comes with the significant benefit that extensions would become effectively plug-and-play for users.
Different extensions require access to different things. Some will require access to connected peripherals (e.g. USB devices), and many will likely require some level of access to mavlink messages from the autopilot, as well as the ability to send mavlink messages to the autopilot and/or topside. It’s also possible that certain integrations will want/need to communicate with or interface with other extensions.
We’ll need to make these available, but for us to get this right from as early as possible it’s important for us to know what kind of extensions you’re interested in making - please feel free to comment below with extension ideas you may be interested in, or think others may find useful
As Companion and various extensions evolve and add new features, it’s important to make sure that the user’s version of Companion is compatible with a suitable version of an extension they want to install. The same idea applies for extensions that need to work together. There are two main ways this can be handled, and we’re interested in your preferences:
- Versioning is handled entirely by individual extensions - as part of the extension installation process the extension accesses the current version number of Companion and determines how to set itself up to work with that (or provide some warning about an incompatibility/upgrading requirement)
- Companion handles some or all of the version (and dependency) checking - requires extensions to store some standardised form of metadata, perhaps in something like Docker object labels
At this point there’s no way to extend core functionality (e.g. to add an extra camera type) without running a modified core image. It’s possible this problem can be avoided to some extent by splitting off commonly extensible functionality into its own Docker container (in which case you could replace just the camera module for example), but there remains the potential issue of a user wanting multiple integrations that replace the same component (e.g. to support two extra camera types), and replacing/overriding core functionality could mean the user misses out on useful updates for that component.
In cases like this it may make sense for us to make an intentionally extensible module, which supports some simple microservices to add some kind of common functionality. This is quite a challenging and nuanced issue, so please contribute to the discussion if it affects you and you have ideas or preferences on how it should best be handled.
While we of course intend to document all the functionality of Companion, along with how to develop for/with it, please let us know if there are particular topics you would like to see covered first, or concepts you’re particularly interested in examples of or resources for