Complete software example building upon Beep-Sat (basic) with additional tasks, complexity, fault handling, and more.
In this Section
⭐ 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:
This example is sufficient for actual real-world missions/applications.
High-level fault handling is introduced to main.py
that ensures the software will continue to run even if our tasks fail to complete (handling software AND hardware failures).
Additionally, a "last resort" fail-safe is added that reboots the board in the event of catastrophic failure.
Compact (bandwidth efficient) data logging is demonstrated using a modern data encoding format called MessagePack to easily store sensor data in files onboard an SD card.
(MessagePack is similar to JSON, but faster and smaller)
Entering and exiting low power or "safe" mode in response to a low-battery condition.
A reasonably-secure method for interacting with the spacecraft via over-the-air commands using the onboard radio. Four commands are demonstrated, 2 without arguments, 3 with arguments:
Without Arguments
no-op
A "no operation" command. Doesn't do anything but respond with an acknowledgement (ACK) packet.reset
Allows remote reset of the board.With Arguments
shutdown
Conclude the mission by remotely stopping all spacecraft activityquery
evaluate string argument as a single python expression and send return valueexec_cmd
execute string argument as entire python code blockDownload the latest Beep-Sat code from its GitHub Repo (either download the zip or if you're familiar with git, fork & clone the repo)
(Optional) With your PyCubed board plugged into your computer, backup your PYCUBED
drive by copying its contents to a directory on your computer.
Copy the files from /beepsat/advanced/
to your PYCUBED
drive, overwriting any files when prompted
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
<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>
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.
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>
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.