Complete software example building upon Beep-Sat (basic) with additional tasks, complexity, fault handling, and more.

In this Section

↩ Return Home

↩ Return to Resources

↩ Return to Quick Start

Highlights

(top ⬆)


⭐ Builds upon core concepts from Beep-Sat (basic) to yield a reliable a full-featured flight software example.

This example elaborates on four key aspects common to most small satellite missions:

  1. System-wide fault handling
  2. File management, data encoding, storage, and down-linking
  3. Low power (safe) mode
  4. Over-the-air commanding

Overview


This example is sufficient for actual real-world missions/applications.

Installation


  1. Download the latest Beep-Sat code from its GitHub Repo (either download the zip or if you're familiar with git, fork & clone the repo)

  2. (Optional) With your PyCubed board plugged into your computer, backup your PYCUBED drive by copying its contents to a directory on your computer.

  3. Copy the files from /beepsat/advanced/ to your PYCUBED drive, overwriting any files when prompted

  4. Open a serial terminal and observe the output as the beep-sat conducts its mission

    Refer to Accessing the Serial Console for help opening the REPL

Required Hardware

Code Breakdown & Task Explanation

<aside> 📢 All tasks from the basic example are still present in advanced, see their explanations on Beep-Sat (basic). Only the tasks were expanded upon are discussed below.

</aside>

main.py

All changes to main.py pertain to fault handling and software robustness.

In the basic example, our state machine was initialized and ran (continuously) with the single call:

# should run forever
cubesat.tasko.run()

This is "risky" because ANY unhandled error/exception in any of our tasks would raise an exception within cubesat.tasko.run() and cause our entire flight software to stop.

  1. To address this, we wrap the cubesat.tasko.run() call in a try and except catch. Now if all else fails and we have an unhandled exception in any of our tasks, we'll attempt to log the exception before proceeding to our "fail-safe."

    try:
        # should run forever
        cubesat.tasko.run()
    except Exception as e:
        print('FATAL ERROR: {}'.format(e))
        try:
            # increment our NVM error counter
            cubesat.c_state_err+=1
            # try to log everything
            cubesat.log('{},{},{}'.format(e,cubesat.c_state_err,cubesat.c_boot))
        except:
            pass
    

    Although this is vital for the final "flight" software, it makes general software development and debugging much more difficult since any task-level exception is going to get abstracted up to this "FATAL ERROR" message without any additional helpful information (like where in the code we were when the exception was thrown, line numbers, etc...).

    <aside> ⚠️ Therefore, for general software development use the main.py from the basic example (or comment out everything we've just added) so you can benefit from the detailed REPL error messages when debugging.

    </aside>

  2. After the try and except catch, we add our "fail-safe" which performs a hard reset of the board. This way, no matter what happens, we'll keep the spacecraft running (in some capacity).

    # we shouldn't be here!
    print('Engaging fail safe: hard reset')
    from time import sleep
    sleep(10)
    cubesat.micro.on_next_reset(cubesat.micro.RunMode.NORMAL)
    cubesat.micro.reset()
    

    This is considered a "nuclear" option and should only be treated as a last resort.

Although it provides a nice safety net, the goal is to never end up triggering the high-level fault handling shown here for main.py. But it is important to carry this try and except pattern into the individual tasks, thereby providing you with tools to handle targeted Exception behavior specific to the task. A good example of task-level Exception handling can be seen later in this tutorial for the beacon task.

1. IMU data task