Developing a KubOS Service

This document goes over the basic components of creating a new service for KubOS.

It assumes you already have a good base understanding of the KubOS ecosystem and is intended as more of a reference document, rather than a detailed tutorial.

If you are unfamiliar with the system, we recommend first going through our new user tutorials.

Example Services

The underlying framework tends to be quite common between services. As a result, it will likely be useful to refer to an existing service when creating a new one.

The following are recommended example services:

Creating the Schema

All services should implement the base service schema, as documented in the service outline doc.

At a high level, the service should present operations which can be broken into two categories:

  • Queries allow users to fetch information about the state of the system and other telemetry items
  • Mutations are operations which may affect the state of the system

We recommend implementing the most basic operations first (ping, no-op, reset) to establish the initial service framework before moving on to the more complex (or unique) features.

In general, we don’t bother to expose all possible functionality of a particular hardware device. Instead, we focus on the most common functionality as well as the specific operations we know we’ll need. This allows us to reduce the amount of development time required to create a new service.

As a result, we make sure to include a commandRaw mutation in all hardware services. This allows the service to still be able to execute any functionality which wasn’t explicitly programmed.

Logging

We recommend logging any errors which your service encounters. These errors will likely also be returned in a GraphQL response, however we feel that this redudancy is important for maintaining overall system health.

All log messages issued by the service should be routed to the system logs. You may also choose to echo the messages to stdout, however that is not a required behavior.

Note

If you choose to route messages to stdout, they will only appear in the console of the user who started the process. As a result, services which are started automatically at boot will not issue messages to stdout if you log into the OBC at a later time.

Services should use the daemon logging facility (rather than the user facility). This will cause all service log messages to be routed to the /var/log/kubos-*.log files.

Please refer to the logging doc for more information about the setup and behavior of log messages.

Service Configuration

By default, all services require that the IP address and port of their GraphQL endpoint be defined in the system’s config.toml file. It is generally assumed that any port which is not already listed in the config file is available for use.

It may be useful for your service to have certain additional configurable settings. For instance, you might want to be able to adjust certain timeout values, or change which device bus your hardware is connected to. In this case, your service should read the needed configuration values from the system’s config.toml file.

More information about setting and fetching configuration values can be found in the service config doc.

Testing Your Service

The GraphiQL interface provides a good way to dynamically test each of your service’s operations.

Unit and integration tests are a good way to ensure that your service remains functional and compatible with the KubOS ecosystem over time. More information about setting up testing can be found in our testing doc.

Creating an Init Script

If you would like your service to be automatically started at system boot, you will need to create an init script. KubOS uses BusyBox’s init system, so the init scripts will need to use the following naming convention: S{run-level}{application-name}. The run-level value should be between 1 and 99. The lower the value, the earlier it will be run in the system boot process.

Note

The BusyBox init system does not require compliance with the LSB init script standard.

For Rust-based services, the monitor service’s init script provides a good example. Rust services (and other executables) should be started using start-stop-daemon.

For Python-based services, please refer to the Pumpkin MCU service init script for reference. Python services should be started by using the python command to start the service as a background process.

Installing Your Service

Once you have finished service development, you should install the service in its final location in your OBC.

Custom services may either live in the user data partition or in the root file system. If the service interacts with core avionics or communications hardware, and is not expected to change after launch, we recommend including it in the root file system for recovery purposes. Only services included in the root file system are recovered automatically by the OS recovery process. Please refer to our recovery architecture doc for more information about our OS recovery system.

In either case, you will need to update your system’s config.toml file in order to define the IP address and port for your service’s GraphQL endpoint.

User Data Partition

If your service will live in the user data partition, then there will be two steps needed to install the service.

  1. Transfer the service binary to /home/system/usr/bin. This is the preferred location for user executables and is in the system PATH.
  2. Transfer the service init script to /home/system/etc/init.d.

Root File System

In order for your service to be installed into the root file system, you will need to create a custom Buildroot package and then generate your own KubOS image.

More information about creating custom packages can be found in the Configuring KubOS doc.