KubOS Mission Applications Development Guide

Overview

In order to be compatible with the applications service, mission applications must comply with the applications framework:

  • The application should have separate handler functions for each supported run level
  • The application must be packaged with a manifest file which details the name, version, and author information for the binary

Once an application has been built, it should be transferred to the system, along with its manifest, and then registered with the applications service.

APIs

Users may write applications in the language of their choice. However, Kubos provides APIs to assist and simplify with application development for use with one of our preferred languages.

Our supported languages for mission applications are:

These APIs abstract the run level definitions and provide helper functions for use when querying other system and hardware services.

Run Levels

Run levels allow users the option to define differing behaviors depending on when and how their application is started.

Each application should have a definition for each of the available run levels:

  • OnBoot
  • OnCommand

When the application is first called, the run level will be fetched, and then the corresponding run level function will be called.

It is acceptable to only have a single set of logic no matter which run level is specified. In this case, each of the run level options should simply call the common logic function.

On Command

The OnCommand run level defines logic which should be executed when the application is started manually.

For example, a user might want a custom batch of telemetry to be gathered and returned occassionally. Rather than sending individual telemetry requests, they could code their application to take care of the work, so then they only have to send a single query in order to trigger the process.

On Boot

The OnBoot run level defines logic which should be executed when the applications service is started at system boot time.

This run level is frequently used for setting up continuous fetching and processing of data from the other system services and hardware. For instance, an application might be set up to fetch the current time from a GPS device and then pass that information through to the ADCS device.

Application Manifest

In order for the applications service to properly maintain versioning information, each application should be registered along with a manifest file, manifest.toml.

This file must have the following key values:

  • name - The name of the application
  • version - The version number of the application
  • author - The author of the application

For example:

name = "mission-app"
version = "1.1"
author = "Me"

Example Walkthrough

Let’s walkthrough the process of creating, installing, and updating an application on a Beaglebone Black target OBC.

Creating

For our application, we’ll start by creating a simple skeleton application, containing only the required run level handlers and some simple debug statements.

Rust

If we want our application to be written in Rust, we’ll start by creating a new project:

$ cargo new --bin example-mission-app
$ cd example-mission-app

We’ll update the src/main.rs file to have the following:

#[macro_use]
extern crate kubos_app;

use kubos_app::*;
use std::fs;

struct MyApp;

impl AppHandler for MyApp {
    fn on_boot(&self) {
        fs::write("/home/kubos/test-output", "OnBoot logic\r\n").unwrap();
    }
    fn on_command(&self) {
        fs::write("/home/kubos/test-output", "OnCommand logic\r\n").unwrap();
    }
}

fn main() {
    let app = MyApp;
    app_main!(&app);
}

And then update the config.toml file to add the kubos-app dependency

[dependencies]
kubos-app = { git = "https://github.com/kubos/kubos" }

And then compile the project for the Beaglebone Black target:

$ cargo build --target arm-unknown-linux-gnueabihf --release

The compiled binary will be in example-mission-app/target/arm-unknown-linux-gnueabihf/release/example-mission-app

Python

If we want our application to be written in Python, we’ll create a single new file, example-mission-app, making sure to include #!/usr/bin/env python at the top of the file:

#!/usr/bin/env python

import argparse

def on_boot():

    file = open("/home/kubos/test-output","w+")
    file.write("OnBoot logic\r\n")

def on_command():

    file = open("/home/kubos/test-output","w+")
    file.write("OnCommand logic\r\n")

def main():
    parser = argparse.ArgumentParser()

    parser.add_argument('--run', '-r', default='OnCommand')

    args = parser.parse_args()

    if args.run == 'OnBoot':
        on_boot()
    else:
        on_command()

if __name__ == "__main__":
    main()

And then we’ll update the file permissions to allow execution:

$ chmod +x example-mission-app

Note

We’re foregoing the usual ”.py” extension so that the file name is the same as the Rust example file name for the remainder of this walkthrough. It has no impact on actual execution.

Manifest

No matter which language we use, we’ll need a companion manifest.toml file:

name = "example-mission-app"
version = "1.0"
author = "Kubos User"

Transferring

Next, we’ll transfer the example-mission-app file and the manifest.toml file into a new directory, /home/kubos/example-app, on the OBC.

Registering

Once both files have been transferred, we can register the application using the register query:

mutation {
    register(path: "/home/kubos/example-app") {
        app {
            uuid,
            name,
            version
        }
    }
}

The response JSON should look like this:

{
    "app": {
        "uuid": "60ff7516-a5c4-4fea-bdea-1b163ee9bd7a",
        "name": "example-mission-app",
        "version": "1.0"
    }
}

Note

The UUID will be a custom value for each application which is registered

Starting

We’ll go ahead and start our app now to verify it works:

mutation {
    startApp(uuid: "60ff7516-a5c4-4fea-bdea-1b163ee9bd7a", runLevel: "OnCommand")
}

The response JSON should contain a number indicating the PID of our started application.

To verify that the app ran successfully, we’ll check the contents of our new test-output file:

$ cat /home/kubos/test-output
OnCommand logic

Updating

Now let’s create a new version of our application.

We’ll change the “OnCommand logic” string to “Updated OnCommand logic”, and then update our manifest.toml file to change the version key from "1.0" to "2.0".

After compiling (for Rust) and transferring the new files into a new folder, /home/kubos/example-app-2, we can register the updated application:

mutation {
    register(path: "/home/kubos/example-app-2") {
        app {
            uuid,
            name,
            version
        }
    }
}

The returned UUID should match our original UUID:

{
    "app": {
        "uuid": "60ff7516-a5c4-4fea-bdea-1b163ee9bd7a",
        "name": "example-mission-app",
        "version": "2.0"
    }
}

Verifying

We can now query the service to see all of the registered applications and versions:

{
    apps {
        active,
        app {
            uuid,
            name,
            version
        }
}

The response should show the two versions of our app, with the latest version being marked as active:

{
    "apps": [
        {
            "active": false,
            "app": {
                "uuid": "60ff7516-a5c4-4fea-bdea-1b163ee9bd7a",
                "name": "example-mission-app",
                "version": "1.0"
            }
        },
        {
            "active": true,
            "app": {
                "uuid": "60ff7516-a5c4-4fea-bdea-1b163ee9bd7a",
                "name": "example-mission-app",
                "version": "2.0"
            }
        },
    ]
}