Designing a Command Engine for a CubeSat - Part 1
Software
BY Edwin Mwiti 19-01-2026
Share:

Writing a command engine 

OK. I am going to jump straight to the point. When building machines that go to space, it means at some point after launch they will be out of your reach. When doing an electronics project development on the ground, you can easily attach a cable to it and print the output on the serial terminal, and just follow through what your code does. Now if a cubesat is in space, you cannot connect a debug cable from space to your machine. I just felt the need to say that. Anyway, you need a way to fetch the operational state of the cubesat, plus a way to control/command your cubesat to do specific functions while in space. This is why we need a command engine. And I am going to show you how I approached building a basic but functional cubesat command engine for my Project Shadow Flight Cubesat

How the command engine is supposed to function 

The flow of the command engine is pretty direct. When a downlink to ground station is established, the ground operator can send a specific set of strings to the cubesat. This specific set of strings is interpreted by the cubesat to perform a specific function. 

Now, the commands you can send are custom to each cubesat. One cubesat cannot interpret the command set of the other cubesat. This means that we will build our own set of commands for our cubesat. The command list is still in development

 

Classification of commands

I have classified the commands into two types.

  • Immediate commands 
  • Scheduled commands

 

Immediate commands are executed immediately they are received. 

Scheduled commands are executed….you guessed it...at a specified time in the future. 

Immediate commands have a different structure compared to scheduled commands. And the only difference is that scheduled commands have a time parameter. The string below shows an immediate command and a scheduled command. 

EPS RESTART 2026-01-13T15:30:00Z -> Scheduled command

COMMS GET_STATE -> Immediate command

This is the basic string that will be sent from the ground station.

Part of a command string

If you notice, the first section of the above commands represents a subsystem. I have the following subsystems on my cubesat. 

  • EPS
  • COMMS
  • PAYLOAD
  • ADCC
  • OBC

 

A command must be able to target the intended subsystem

What follows is the command itself. You might ask how the cubesat knows that this is a valid command. The thing is in my code I have a lookup table containing a list of valid commands allowed to be executed. So if a command is received but it does not exist in the set, it is simply discarded. 

 

The third thing is the time parameter. This is only available for scheduled commands. I use UTC time referenced to the cubesat time. E.g if cubesat is reporting, I send UTC time ( a time marker when to run a command), the CDH converts it into local CPU time, and calculates the time between NOW and the marker, then starts a countdown using the local CPU counter. So you might ask what about time zones. Basically only UTC times are used. The operator is supposed to send time based on their timezones, to make it easier to correctly map commands, but the cubesat will convert the time to UTC and then to local CPU time. Time conversions will be a full article of its own. 

Immediate commands do not have a third parameter. 

Command processing algorithm 

The command processing algorithm is shown in the following flowchart. I have to say that this is still in the development, but so far my implementation works. You can get the full C code implementation here [insert link] here.

(You can skip to the explanation part if reading flowcharts is not your thing : ) )

I will be brief in explaining this and only explain the relevant parts.

Proceeding, assume you receive a string like this:

 

EPS RESTART 2026-01-13T15:30:00Z

 

The first thing is to tokenize the string. I basically want to break down the command into the parts mentioned above. That is, TARGET, COMMAND, [TIME]*

 

So I parse it through a tokenize function that breaks the string into specific strings and stores the extracted tokens in an array that I can reuse in the code. This is a section of the code. See the link to full code in the references below. (I basically do not want this article to be too long). 

/* parse received command */
char** ce_tokenize(CommandEngine* inst, sat_command_t c) {

    /* check type of command */
    if (c.cmd_type == IMMEDIATE) {
        puts("Immediate command");
    } else if(c.cmd_type == SCHEDULED) {
        puts("scheduled command");
    }

    /* make a copy of this command */
    uint8_t cmd_len = inst->get_cmd_length(inst, c.cmd);
    char cmd_cpy[cmd_len + 1];
    strcpy(cmd_cpy, c.cmd);

    printf("Parsing cmd: %s\n", cmd_cpy);
    /* split the string using spaces */
    const char* dlm = " ";
    uint8_t args_count = 0;
    char* tmp = cmd_cpy;

    /* count the number of arguments to be extracted */
    uint8_t j = 0;
    while(tmp[j] != '\0') {
        if(tmp[j] == *dlm) {
            args_count++;
        }
        j++;
    }

    /* handle the last argument, 3 spaces == 4 arguments */
    args_count++;

    /* store these tokens somewhere */
    char** result = malloc(sizeof(char*) * (args_count+1));

    if(result != NULL) {
         size_t index = 0;
         char* token = strtok(cmd_cpy, dlm);
         while (token && index < args_count) {
             result[index] = strdup(token);
             token = strtok(NULL, dlm);
             index++;
         }

         result[index] = NULL;

         return result;
    } else {
        return NULL;
    }
}

 

So this code is not embedded optimized. I write it in pure C first to test the algorithm. 

After you get the tokens, then you can proceed to validate the specific sections of the command, At the moment there are 2 things to validate: The target system and the Command token. I have specific functions that do this. This is the current lookup table holding the allowed set of commands:


/* available sub systems  */
static const char* subsystems_table[] = {
    "OBC",
    "COMMS",
    "EPS",
    "ADCU",
    "PAYLOAD"
};

/* lookup table to store available internal commands */
static const char* commands_table[] = {
        "RESTART",
        "SLEEP",
        "GET_TIME",
        "GET_STATE",
        "SET_STATE",
        "ON",
        "OFF"
};

 

After receiving the commands, I do a simple linear search on the lookup table to find the command. Linear search is predictable in terms of not having any branching compared to binary search in flight software, there is a whole discussion around this reason, based on previous flight software standards. 

Here is the subsystem validation code (not optimized): 

/**
 * @brief check for valid sub-system
 */
uint8_t ce_check_valid_subsystem(CommandEngine* inst, char* system) {
    if(inst == NULL) return 0;
    if(system == NULL) return 0;

    printf("validating subsystem...");

    uint8_t k = 0;
    uint8_t sys_valid = 0;
    while (inst->subsystems_tbl[k] != NULL) k++;

    uint8_t indx = 0;
    uint8_t system_offset = 0;

    /* linear search */
    for (indx; indx < k; indx++) {
        if (strcmp(system, inst->subsystems_tbl[indx]) == 0) {
            sys_valid = 1;
            system_offset = indx;
            break;
        }
    }

    if (sys_valid) {
        printf("Subsystem %s OK\n", system);
        return 1;
    } else {
        return 0;
    }
}

 

Next steps

What I intend to do after the command and subsystems are validated is to send the command to the target subsystems command queue for execution. 

Currently I have only managed to implement the immediate types of commands. For the scheduled, I will write another article to explain the algorithm from that side. 

There is a video explanation of this whole article on my youtube channel here. 

References

Blog card credits: https://www.americaspace.com/2014/11/19/nasa-skunkworks-team-designs-advanced-new-dellingr-cubesat/

Best.

 

 

 

© 2023