Introduction
As previously discussed, Java, C, and C++ are already perfectly capable tools for writing aerospace-grade software, while Rust programming language is an interesting new candidate that can combine the advantages of the previous generation of programming tools while discarding their drawbacks. However, if one wants to create space-based software development platform for a wider audience than ever before, one needs a solution that combines both ease of use with sufficient robustness for mission-critical applications in a demanding environment. Given below is the summary of requirements for such a programming language and proposed implementation for this requirement set, which will be tested on-board ESA OPS-SAT mission: https://www.esa.int/Enabling_Support/Operations/OPS-SAT
Requirements
- Suitable for real-time applications. A typical mission for the satellite in space may consist of the performing a set of imaging observations of the ground below, with specific set for exposure values and wavelength filters selection. It needs to precisely timed or otherwise made conditional on other mission parameters (satellite orbital position and attitude orientation) to be performed successfully.
- Robustness. Reality of space environment often interferes with the above plans: a satellite may be in dangerously low power state, satellite camera may face into the wrong direction or satellite onboard computer can tripped into reboot by a stray particle. Satellite hardware resources can be limited and not every downlink session will allow mission data to be downloaded. Therefore, its runtime environment has to be smart when it comes of task execution and storage of results and data.
- Data-centric. When editing and transferring instrument data and telemetry parameters to and from different satellite subsystems and uplink/downlink channels, one need good tools for aggregation, abstraction and marshaling of data, from individual bits to multi-megabyte imagery data. Any satellite instrument or component is expected to have some parameters or variables that can be either read and written to (like imaging camera exposure) or only read from (temperature sensors, very commonly). It would be useful to represent this difference in their declaration syntax and also check the validity of operations done upon them before the execution
- Portability. The cost of software development for satellite missions comes from the need to qualify the software in the space, that is to run under realistic scenarios onboard the real satellite in space environment conditions. Therefore, it makes sense to compile source code not to target platform directly, but to some intermediate representation that be reused on multiple satellite hardware platforms, following the original Java “write once, run everywhere” promise.
- Simplicity. Finally, the last aspect of proposed specification of language is about not what to include into its feature set, but what to exclude from it. Available development tools should allow the developers to interact with the most interesting parts of the satellite – its instruments and instrument-generated data while offloading all the work necessary for satellite survival, such as power management as well as specifics of real-time systems outside of user scope as much as possible.
Core Design Concepts
Space Programming Language Interpreter & Command Environment or SPLICE is a domain- specific programming language that enables development of software applications tailored for the conditions of space environment. It is explicit at what should be done but does not specify how execution of the program should be done, leaving that to execution runtime.
Its major programming constructs are tasks and queues, inspired by the real-time operating systems like VxWorks or FreeRTOS that are often used as execution platforms for satellite mission software. Note that other elements of real-time systems, like interrupts or priorities are not exposed directly to users and are managed implicitly, within the language runtime, to reduce the complexity of software development for the users.
Tasks are lists of actions, that are specified to be executed either at given intervals or on certain conditions, specified by the following qualities:
- How often they are run, either once only or within intervals between repeated execution defined in time range (1 second, 1 minute or 1 hour), only once or as fast as the task scheduler allows.
- When they are triggered to run – either unconditionally after fixed time intervals or conditionally bound by external (measured environment parameter value) or internal state (a successful execution of prerequisite task).
- What exactly are they doing – command the instruments or perform necessary calculations with the data values received.
Task lifetime is described through a following diagram:

Queues represent the major building I/O block, such instrument command queues, uplink or downlink channels or persistent/temporary data storage, specified by the following:
- Interface description of the source or the destination of the data flow, specifying property type, command or telecommunication data packet format.
- Read or write availability, as some queues, for example connected to telemetry sensors can only provide read-only functionality, and some such as ones of communication subsystem can offer both read and write functions.
- Queues can be either persistent, saving their intermediate state to the persistent storage (such underlying file location) or ephemeral, discarding it between the execution runs.
- As both ephemeral and persistent memory is a finite resource, queues also need to define maximum number of data elements stored internally and shedding behaviour – what to do when amount of data being written into the queue exceeds its internal storage capabilities. Shedding behaviour can be defined as “newest element gets discarded first” or “oldest element gets discarded first”, depending on the operational needs.
Multiple tasks can be grouped into groups and queues into instruments, for better program structure and user convenience. To ensure deterministic behaviour, loops and control flow constructs are provided only at the task level, simplifying the logic of program execution.
4. SPLICE Syntax Examples
Let’s now have a look at the proposed syntax through some SPLICE program examples. Most of the syntax examples below should be self-explanatory for any software developer exposed to the commonly used programming languages. Most of syntax constructs are borrowed from C-derived languages, including Python, Rust and C++.
/* Comments are the same as in C-derived languages, both for single and multiline types */ /* Types declaration use Rust-like syntax */ //32-bit signed integer var an_integer:i32 //8-bit unsigned integer var unsigned_int:u8; //single-precision float var a_float:f32; /* variables can be initialized at declaration, for cardinal types initialization is optional though */ var initialized_integer:i32 = 0; /* strings are always allocated statically and has to be always initialized at declaration */ var a_string:string=”a string value”; /* constants declaration examples */ //floating point example const PI:f32 = 3.14; //integer constant const TASK_OK:i32 = 0; //and a string one const INFO:string = “a string constant”; /* Custom type declarations */ //complex types can be assembled from built-in ones type TPacket { header: u32; data_a: f32; data_b: f32; data_c: f32; } //type declarations can use nested complex types type TCommand { uid: i32; command: string; data: TPacket; } //variables of complex types can be declared as well var packet:TPacket; // ..and initialized, when necessary or desired so // but for some of the fields initialization can be optional var packet:TCommand = { uid:0, command: ”Command string”, data: { header:0, data_a:3.4, data_b:-0.5, } }; // constants can be of custom type as well const TEST_PACKET:TPacket = { header:0, data_a:0.0, data_b: 0.0, data_c:0.0 } /* Instrument declarations – semantically they are interfaces */ //a simple instrument example – GPS receiver inst gps { //”prop” keyword indicates read-only value prop lat:f32; prop lon:f32; prop alt:f32; //can be turned on/off from user programs var powered:bool; } /* UHF transceiver instrument example. Note the use of queues, as being in a ground station view is required to send or receive any data */ inst uhf_comms { //NOT safe to turn it off prop powered:bool; queue downlink { size: 100; type: TPacket; // shedding modifer, as explained above shed: “last”; // access modifier: “push” is write-only accs: push; } queue uplink { size: 100; type: TPacket; //access modifier: “pull” is read-only accs: pull; } } /* another example – taken from actual satellite mission */ inst camera { var exposure:f32; var gainR:f32; var gainB:f32; var gainG:f32; //can be turned on or off safely var powered:bool; prop imageCount:u32; queue commands { size: 10; type: TCommand; accs: push; shed: “last”; } }
/* task and groups declarations examples: in the most basic case, a list of actions to be executed under certain conditions and with specific execution frequency. groups are collections of tasks, where all tasks has visibility of each other execution results and local data in read-only mode*/ group task_group_1 { task task_1 { //tasks can have their own variables and constants data { var lat:f32=32.21; var lon:f32=120.1; const ADCS_MODE_NADIR = 5; var target_mode: TADCSMode = { command:”set_mode”, mode_id: ADCS_MODE_NADIR, duration: 300s } } /* frequency of execution, can be "once", “always” or time value in seconds */ freq: “once”; /* prerequisite section, statements below has to be true to allow execution */ preq { gps.lat == lat; gps.long == lon; } /* executive section - the actual actions and logic of the task. Task execution result is saved after task execution and updated after each run, so it can be used for other tasks as prerequisite */ exec { //pushing a command to subsystem queue adcs.command.push(target_mode); return TASK_OK; } } /* one task can read another task data (from the same group), but not write to. Variables, constants or queues can be used for data transfer between tasks */ task task_2 { data { const NX:i32=task_1.ADCS_MODE_NADIR; var gainR:f32=1.0; var gainB:f32=0.5; var gainG:f32=0.5; var exp:f32=0.1; var cam_commd:TCommand; } /* time literals have prefix as “s”,”m”,”h” and so on, depending on mission needs */ freq: 60s; /* task execution result can be a prerequisite too, in addition to conditional expression */ preq { adcs.mode == NX; task_1.result == TASK_OK; } /* There can be no loops or conditional expressions in the execution section – just a list of statements */ exec { cam_commd.uid =1; cam_commd.command = “RAW”; cam_commd.data_a = gainR; cam_commd.data_b = gainB; cam_commd.data_c = gainG; /* instruments can be controlled through command queues or by directly editing their parameters */ camera.exposure =0.2; camera.command.push(cam_commd); /* task context, including local data is saved between runs, allowing complex execution logic */ gainR = gainR + 1.0; gainB = gainB + 0.5; gainG = gainG + 0.1; return TASK_OK; } } }
Progress To Date
At the moment, the current implementation of SPLICE only exists as intermediate representation in VM bytecode and simple assembly language, with most of high-level syntax is not yet fully defined and many planned features still undergoing internal discussion.
Undoubtedly, as we gain experience through orbital operations later in 2020 and learn more from both successes and mistakes, both specifications and implementation will evolve and improve, adding more features to allow more interesting applications. Going forward into our next satellite mission, we plan to implement a fully featured software development kit, including high-level compiler and satellite ground simulator as well as allowing deployment of user programs onboard our own crowd-flyable mission – NOVA, to be launched later this year. Keep in touch with us on social media to follow our progress and send us feedback and comments!