Flightkit Workflow Feature Guide

An overview of the steps you’ll go through when developing flight software with Flightkit.

1. Introduction

Working on a piece of software always involves following some development workflow. When working on small tasks this is often informal and implicit, while on larger projects it is often more strictly defined.

Flightkit introduces a formalised development workflow which helps you produce re-usable code and reduce boilerplate. It also helps you compose your code together with that others have written.

In this feature guide we’ll discuss the most common jobs you will need to carry out when working with Flightkit, describe the hfk commands you’ll run when carrying out those jobs, and highlight any complexities you’ll run into in the process.

It is not intended to be a reference manual, but a guide to help you use Flightkit as effectively as possible.

2. A Simple Overview

When using Flightkit you are usually aiming to produce the finished flight software for use on a spacecraft. To do so you will carry out the following broad jobs:

  1. Identify what your flight software should do, at a high level.

  2. Identify what library code we have included with Flightkit which is useful to you.

  3. Identify and implement any additional functionality you require.

  4. Combine the above elements into a mission which can be tested and validated.

  5. Maintain your library and mission code until everything is ready to be delivered.

These jobs will often be carried out roughly in sequence, but changes during the development of your mission will inevitably lead to iteration between them.

In the following sections we will discuss each in some detail, and give hfk commands and advice useful at each stage.

3. The Workflow in Detail

3.1. Identifying mission structure

In order to produce complex software in any environment at least some software design work is required.

In Flightkit the software design process typically involves identifying the functional blocks your mission will require and deciding how they relate to each other. The following guidelines are worth bearing in mind when producing a design for implementation with Flightkit.

  • Try to record the responsibilities your flight software will have at a high level before deciding how the functionality will be organised. Often similar responsibilities will appear in multiple places across the software, and these can translate into reuse of functional blocks.

  • Where possible keep the coupling between functional blocks loose. That is, do not allow the design of one block to be intimately tied to the design of another. In Flightkit these loose couplings often translate to the use of services.

  • Beware of building very complex but inflexible structures early on in your design. Better to have a design which falls short of your end requirements but is flexible to expansion than a huge design which is too fragile to accomodate change.

It is also worth considering the structures which Flightkit will ask you to use to describe your software. At this stage there are four major artefacts to consider:

  • Component types. These are reusable functional blocks. They will typically fulfil a single functional responsibility identified within a design. They have some similarities with classes in object-oriented languages. They are reusable and instantiated in deployment types.

  • Services. These are descriptions of functional contracts, similar to API definitions. They are used to make loose couplings between component types.

  • Deployment types. These are collections of component instances, connected with services, which are reusable and instantiated in missions. Deployment instances are buildable artefacts - each can produce a binary which you can execute on your spacecraft.

  • Missions. Functionally these are collections of deployment instances. Missions also contain information about where those deployment instances will be executed, physically, within the space system.

Your complete design will typically produce a single mission. Depending on the complexity of the mission you will create one or more deployment types. Within the deployment types you will likely reuse many component types and services provided by Bright Ascension, while creating a fairly small number yourself. You may also reuse component types, services or even complete deployment types which were created for previous missions.

The following sections discuss how, once you have a sense of the shape your software will take, you can use Flightkit to build it.

3.2. Identifying available library functionality

With a software design in place you are in a position to identify and select pre-existing functionality which you can reuse in your software.

Flightkit includes various bundles of functionality provided by Bright Ascension for use in spacecraft flight software.

You can examine the documentation for these bundles once you have installed Flightkit. First run the following command to discover what bundles are available:

hfk bundle list

Each bundle is a versioned set of Flightkit artefacts you can use in your own workspaces. In order to find out what artefacts a bundle contains, and whether those artefacts are of use to you in implementing your design, you can run commands similar to the following:

hfk bundle generate-docs FlightkitFoundation:1.0.0 .

This will generate the documentation for version 1.0.0 of the FlightkitFoundation bundle in your current working directory.

The generated documentation provides details of the artefacts available to you:

  • Component types will typically fulfill the obligations of functional blocks in your design.

  • Services can be used to connect these functional blocks together.

  • Pre-made deployment types can provide guidance on how component types are often connected together.

  • Pre-made missions can provide insight into how these deployment types are used.

It is often useful at this stage to return to your design and adjust it to make better use of artefacts which are already available to you. If there are close but imperfect matches to your requirements consider whether your requirements can be tweaked!

In some cases you may even wish to use the contents of the bundles available to you as an inventory during the design process, rather than producing a design first and deciding how it will be implemented second.

After some iteration you should have a design in which at least some of the functional blocks are accounted for from the various bundles you have available to you.

In the next section we’ll discuss how you’d go about producing new functionality to fill the gaps.

3.3. Implementing new library functionality

To implement new library functionality you will be creating artefacts in your workspace which can be used in your mission. They may also be useful in missions you will create in future in ways they were not originally intended. You should bear this in mind and avoid creating artefacts which are "over-fitted" to your current requirements.

The library artefacts you will most frequently need to create are component types and deployment types.

We’ll take a look at each of these in turn.

3.3.1. Creating component types

Creating a new component type involves the practical steps of creating a new componentType.xml, adding to the XML file, writing some implementation code, and writing tests. These activities may be interleaved or carried out sequentially.

Creating the componentType.xml requires that you select a name for your new component type. This should succinctly express what the component type is going to do. There are various guidelines we try to follow at Bright Ascension:

  • Most component types should be named with nouns which describe a role. Examples include DataMonitor, PayloadManager and RadioController.

  • Some component types, particularly those which interact with hardware or other systems, are named for the physical system or function they provide access to. Examples include FileSystem, EPS and CommsGateway.

  • Component type names may also include a prefix called a package path. This lets many component types be organised relative to each other. You do not need to use package paths if your workspace only includes a handful of component types.

Once you have selected the names for each component type you require, you can create the componentType.xml files which will describe them using commands similar to the following:

hfk component-type create PayloadManager

This will create an XML file in your workspace at library/component_types/PayloadManager/componentType.xml which you will need to edit to describe the functionality you require.

  • You should provide documentation using the <Description> and <Documentation> elements. In these elements you should describe how you expect your component type to be used.

  • You can access other functionality via the <Services>, <Required><Module> and <Required><ComponentType> elements.

  • You should make your component type observable by adding sensible <Parameters>, and make it controllable by adding <Actions>.

Once you have a first iteration of the componentType.xml, you can run a command similar to the following to produce the files where you will write the implementation:

hfk component-type generate PayloadManager

This will produce various C and H files, along with build system files, under library/component_types/PayloadManager. Importantly:

  • The component type’s main C file, which contains its implementation, is library/component_types/PayloadManager/PayloadManager.c

  • Its main H file, which defines the main component type structure and initialisation data, is library/component_types/PayloadManager/include/PayloadManager.h

  • Its test file where you can (and should) add unit tests, is library/component_types/PayloadManager/test/PayloadManager_Test.c.

You can find a detailed account of the files associated with component types in the Component Layout Reference.

The implementation and testing of component types is covered in the Flightkit tutorials and will not be discussed here.

Once you have an implementation you are happy with, you can run any unit tests you have written as follows:

hfk component-type test PayloadManager

With a component type implemented, and tested via unit tests, you can instantiate it in a deployment type and use it in a mission.

3.3.2. Creating deployment types

While component types are relatively small reusable blocks of functionality, deployment types are "blueprints" for complete Flightkit binaries. They contain a whole collection of connected component instances. They are themselves instantiated in missions, where they can be built.

Creating a new deployment type requires creating a new deploymentType.xml, adding the desired component instances to it, and connecting them together as required.

Much like component types, deployment types need names which succinctly capture their purpose, and again like component types this should usually describe the role they are expected to fulfil in the mission they’re instantiated within.

Once you have selected the names for each deployment type you require, you can create the deploymentType.xml files which will describe them using commands similar to the following:

hfk deployment-type create DataHandler

This will create an XML file in your workspace at library/deployment_types/DataHandler/deploymentType.xml which you will need to edit to describe the set of component instances you require.

  • You should provide documentation using the <Description> and <Documentation> elements. In these elements you should describe the expected use cases for your deployment type.

  • You will usually want to specify an <Architecture> element. This element contains optional <Board> and <OSAL> elements. This gives you control over what board and OSAL the deployment type is used with. It also allows you to instantiate component types specific to that board and OSAL.

  • You will need to add <Component> elements within the deployment type’s <Implementation> element. These describe the component instances in the deployment type. Instances will require <Connections><Services> and <Tasks> elements, depending on what is specified in their componentType.xml.

Deployment types do not have any code associated with them at present, so this is all that is required to create one.

You can check you have provided all the necessary XML content by generating the documentation from the deployment type using the following command:

hfk deployment-type generate-docs DataHandler

This will generate the documentation for your deployment type in the output/library/deployment_types/DataHandler directory.

3.4. Creating missions

Missions are the environments where your complete system design will be realised. As described above, a mission contains a collection of deployment instances, along with physical information about where those deployment instances will be running.

More specifically, a mission defines three collections:

  • The physical assets involved in the space system. These are usually spacecraft, but could also be ground systems or other physical devices.

  • The targets on board those assets. Targets pair deployment types to assets.

  • The deployment instances accessed by those targets. Each deployment instance accessible via a target can be configured differently. All deployment instances on a target are of the same type.

A mission is described by its mission.xml file.

As for other artefacts, it is important to select an appropriate name when creating a mission. Using the name of the mission or project you are working on to name the Flightkit mission artefact is a good approach.

To create a mission, you can run a command similar to the following:

hfk mission create Voyager3

This will create a mission.xml in your workspace at missions/Voyager3/mission.xml.

Note that missions are fundamentally different to the artefacts described in the library tree. Unlike those artefacts, they are not types and cannot be reused.

The default mission.xml created by hfk mission create contains some basic structure to help you define a new mission.

It contains the <Assets> and <Targets> elements which you must populate.

It contains XML comments with guidance about naming targets and deployment instances.

Once you have added an asset and target you can instantiate the deployment types you have created for the mission.

Once you have at least one deployment instance you will need to generate the initialisation data for that deployment instance. To do so, you can run a command similar to the following:

hfk mission generate Voyager3

This will create initialisation data files for each component instance in each deployment instanace in the mission. These can be found in a tree within the mission’s directory:

Voyager3
└── PrimaryComputer
    ├── Failsafe
    │   ├── init
    │   │   ├── Version_Init.c
    │   │   ├── comms
    │   ... │   ├── PacketStream_Init.c
    │       ... ├── SpacePacket_Init.c
    │           ...
    └── Active
        ├── init
        │   ├── Version_Init.c
        │   ├── comms
        ... │   ├── PacketStream_Init.c
            ... ├── SpacePacket_Init.c
                ...

Each component instance has its own initialisation data file. Each file contains a structure whose type is defined by the component type, and whose values you must set in order for that component instance to behave as required within the deployment instance.

Once a mission’s initialisation data is complete it may be built using a command similar to the following:

hfk mission build Voyager3

This will produce a binary in the output tree for each deployment instance named in the mission. These binaries can then be executed on the assets named in the mission.xml.

In all likelihood you will reach this part of the workflow having only created a subset of the artefacts you require to complete your mission. You would then iterate back to earlier stages - perhaps you need to produce a new component type to add to your deployment type, or you may need to create additional deployment types.

As you carry out your work it may be necessary to make changes to artefacts you’ve already created. The next section discusses these maintenance tasks.

3.5. Maintenance

3.5.1. Maintaining component types

Maintaining component types as your requirements and designs change usually requires some combination of the following steps:

  • Modifications to the componentType.xml.

  • Generation of the component type, and propagating changes from the .template.c file into the main implementation C file.

  • Updates or addition to the unit tests.

  • Generation of a new bundle containing the component type, if required.

Note that it is expected that bundles will use semantic versioning. You should use the changes you have made to your component type to determine whether to increment the major, minor or patch version of your bundle.

For the sake of any users of your bundles you may wish to reduce the number of breaking changes you make over time. To do so consider whether your changes can be made in a non-breaking way. For example, a parameter can be renamed by first adding a new parameter with the new name, and indicating to users that the old parameter is deprecated and will be removed in future. You can then remove the deprecated parameter at the same time as you make other breaking changes.

3.5.2. Maintaining deployment types

Deployment types are fairly lightweight artefacts in Flightkit, and typically reside in the same workspace as the mission in which they are instantiated.

As a result, maintaining deployment types will usually involve:

  • Propagating changes made to component types into the deployment type(s) where they are instantiated.

  • Ensuring all connections between component instances remain valid.

  • Generating a new bundle containing the deployment type, if required.

As for component type changes, changes to a deployment type may necessitate a bundle version change. If you make changes to a deployment type which will require a user to update their deployment instance or its initialisation data, then you should indicate this by releasing a new major version of your bundle.

3.5.3. Maintaining missions

As described above, missions consist of the physical assets in a system, the functional deployment instances in a system, and the targets which pair these things together.

Maintaining the physical part of a mission requires you to keep your asset instances up to date with their type definitions. If a host within the asset type you’re using is moved from one subsystem to another, for example, then you will need to update your asset instance to match.

The functional part of a mission, on the other hand, is more complex to maintain.

  • You will need to maintain the initialisation data provided to each component instance in your deployment instances. This data is set at build time and provided to each component instance at run time. Changes to component types may add or remove initialisation data members and require updates to init/ C files. You will likely also need to update these files as your mission progresses to change behaviour of component instances.

  • You may wish, or need, to modify the library configurations defined for your deployment instances. Library configurations control how each component type is built for the deployment instance in question. Changes to component types may add or remove configuration flags and require updates to lib_config/meson.build files.

  • You may wish to modify the build configurations used by your deployment instances. Build configurations define the core build settings used to build your deployment instances' binaries. This includes compiler flags and settings passed to the meson build system. You will often start development with a build configuration provided by BAL, but as your development progresses you may wish to define your own in order to customise your builds.

It is likely you will compose your mission using artefacts from bundles. As a mission maintainer, therefore, you should pay close attention to the version of each bundle you are using. When changing the version of a bundle you’re using you should consult the change notes in order to determine what changes will be required to your mission code.