Skip to content

paulmccumber/lseq

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cFS lseq - Lua Based Sequence Engine

A NASA Core Flight System (cFS) based mission software stack integrating a Lua-driven sequence engine and a telemetry/attitude management application.

Overview

Video demo of the application

This project builds on NASA's open-source cFS framework and adds two custom flight software applications:

App Repo Description
lseq paulmccumber/lseq Lua-based sequence engine — scheduled by sch_lab
tam paulmccumber/tam Simulated Magnetometer — runs autonomously at 1 Hz, generates HK and sensor traffic

Prerequisites

  • Linux build host (native simulation target)
  • Git with SSH access to GitHub
  • Standard cFS build dependencies: cmake, gcc/g++, make

Build Instructions

1. Clone the cFS Framework

git clone git@github.com:nasa/cFS.git
cd cFS

Optional: Pin to a known-good commit before proceeding:

git checkout 83c735e469e96537b289a50574781abe113c1230   # This README was written building from this hash
git checkout 9c786d2536821aae608560e0d75835e3637b499d   # This app was developed on this hash

2. Initialize Submodules

git submodule init
git submodule update

3. Set Up the Build Environment

Only required once per workspace.

cp cfe/cmake/Makefile.sample Makefile
cp -r cfe/cmake/sample_defs/ sample_defs

4. Build the Baseline cFS

make SIMULATION=native prep
make

Adding the Custom Apps

5. Clone the Application Repos

cd apps/
git clone git@github.com:paulmccumber/lseq.git
git clone git@github.com:paulmccumber/tam.git
cd ..

Configuration

6. Register Apps in the Build System

Edit sample_defs/targets.cmake and add lseq and tam to the global app list:

list(APPEND MISSION_GLOBAL_APPLIST lseq tam)

7. Update the cFE Startup Script

Edit sample_defs/cpu1_cfe_es_startup.scr to load both apps at boot. Add the following entries (and remove sample_app / sample_lib if not needed):

CFE_APP, lseq,     LSEQ_Main,    LSEQ_PP, 51, 16384, 0x0, 0;
CFE_APP, tam_app,  TAM_AppMain,  TAM_APP,  55, 16384, 0x0, 0;

8. Configure the Scheduler

Edit apps/sch_lab/fsw/tables/sch_lab_table.c to add a scheduler slot for lseq.

#include "default_lseq_app_msgids.h"
/* Schedule our lseq hk output */
{CFE_SB_MSGID_WRAP_VALUE(LSEQ_SEND_HK_MID), 100, 0},

Note: tam does not require a scheduler entry — it runs its own 1000 ms sleep loop internally.

Edit:apps/sch_lab/CMakeLists.txt

include_directories(${lseq_MISSION_DIR}/config)

9. Configure Telemetry Output

Edit:apps/to_lab/fsw/tables/to_lab_sub.c

        {CFE_SB_MSGID_WRAP_VALUE(0x0892), {0, 0}, 4},
        {CFE_SB_MSGID_WRAP_VALUE(0x0902), {0, 0}, 4},
        {CFE_SB_MSGID_WRAP_VALUE(0x0903), {0,0},4},

Rebuild After Configuration

make SIMULATION=native prep
make
make install

Stage Runtime Files

After each build, copy the lseq Lua sequences and JSON configuration files into the CPU1 load directory:

cd apps/lseq/fsw/tables/
cp *.lua ../../../../build/exe/cpu1/cf/
cp *.json ../../../../build/exe/cpu1/cf/
cd ../../../..

This step is required after every rebuild — the cf/ folder is not populated automatically.


Run

cd build/exe/cpu1
sudo ./core-cpu1

Build Modes and Lua Sandbox

The repo is configured for "Debug builds" so that the emmy debugger can function. Read and understand the following. Support is in place such that "Flight builds" will be sandboxed.

Why the Lua standard library is restricted

lseq embeds a Lua interpreter in each child task. By default the full Lua standard library (luaL_openlibs) is not loaded. Instead, OpenSafeLibs opens only the libraries a sequence script actually needs:

Library Reason included
base Core language (print, pcall, tostring, etc.)
math Numeric utilities
string String formatting
table Table manipulation
coroutine Co-operative multitasking within a script

The following libraries are excluded in flight builds:

Library Reason excluded
package require() and loadlib() can load arbitrary native .so files at runtime
io Direct file I/O bypasses cFS abstractions
os os.execute() can spawn shell commands; os.exit() can kill the process
debug Exposes Lua VM internals; not needed in sequence scripts

A sequence script whose job is to send and receive cFS Software Bus messages has no legitimate need for any of these. Excluding them means a buggy or malicious script cannot load native code, execute shell commands, or manipulate the VM state. This follows the same principle as Lua's _ENV sandboxing mechanism — the language was designed with restricted environments as a first-class feature.


Debug build — EmmyLua debugger support

The EmmyLua VS Code debugger attaches via require('emmy_core'), which requires the package library. A LSEQ_DEBUG_BUILD CMake option re-enables package and io for development use.

To build with the debugger enabled (development only):

In CMakeLists.txt, set the option default to ON:

option(LSEQ_DEBUG_BUILD "Enable debug Lua libs (package, io) for EmmyLua" ON)

Then rebuild:

rm build/native/default_cpu1/CMakeCache.txt build/CMakeCache.txt
make SIMULATION=native prep
make

The lseq_init.lua script guards the EmmyLua block so it is silently skipped when package is not available:

if package then
    package.cpath = package.cpath .. ";cf/?.so"
    local ok, dbg = pcall(require, 'emmy_core')
    if ok then
        dbg.tcpListen('127.0.0.1', 9966)
        dbg.waitIDE()
    else
        cfs.event(100, 4, "emmy_core load failed: " .. tostring(dbg))
    end
end

No script changes are required when switching between build modes.


Flight build

To build for flight, set the option default to OFF:

option(LSEQ_DEBUG_BUILD "Enable debug Lua libs (package, io) for EmmyLua" OFF)

Then do a clean rebuild. Since you are deploying to a target rather than running locally, distclean is safe here:

make distclean
make SIMULATION=native prep
make

In the flight build:

  • package and io are absent from every Lua state
  • The if package then guard in lseq_init.lua skips the EmmyLua block with no error
  • Scripts have access only to the safe library set listed above

Application Notes

lseq — Lua Sequence Engine

  • Scheduled via sch_lab
  • Sequences are authored in Lua and executed by the embedded interpreter

tam — Magnetometer app

  • Runs autonomously; no scheduler slot required
  • Sleeps 1000 ms per cycle
  • Publishes housekeeping (HK) and simulated sensor telemetry

lseq API Reference

Overview

lseq hosts up to 4 independent Lua threads (slots), each running its own Lua state. Every slot is independently started, stopped, and monitored. Sequences interact with the cFS Software Bus through a dedicated cfs.* Lua extension library.


Configuration Table (LSEQ_LuaConfig_t)

The LuaConfig cFS table configures all slots. Each slot (LSEQ_ThreadCfg_t) has the following fields:

Field Type Description
msg_json char[128] Path to the CCDD JSON message registry for this slot
init_script char[128] Path to the Lua script run once at thread startup
tick_script char[128] Path to the Lua script that defines the tick() function
tick_period_ms uint32 Heartbeat interval in milliseconds (0 = compiled default)
enabled uint32 Non-zero to auto-start this slot at app initialization

The table is loaded from lseq_lua_cfg.c and can be updated at runtime via the standard cFS table services mechanism.


Lua Script Structure

Each slot expects two scripts:

init_script — Evaluated once when the thread starts. Use this to load messages, register subscriptions, set up on-receive callbacks, and define the on_error() handler.

tick_script — Evaluated once at startup to define the global tick() function. LSEQ calls tick() on every heartbeat at the configured tick_period_ms rate.

-- init_script: subscribe and register callbacks
cfs.loadmsgs("/cf/table.json")
cfs.subscribe("MY_Telemetry")
cfs.on("MY_Telemetry", function(fields)
    print(fields.some_field)
end)

function on_error(msg)
    cfs.event(199, 4, "lseq error: " .. tostring(msg))
end

-- tick_script: define the periodic function
function tick()
    local ok, val = pcall(cfs.field, "MY_Telemetry", "some_field")
    if ok then
        -- act on val
    end
end

Ground Commands

All commands are sent to LSEQ_CMD_MID. Housekeeping is requested via LSEQ_SEND_HK_MID and published on LSEQ_HK_TLM_MID.

Command CC Payload Description
Noop 0 No-op; increments command counter
Reset Counters 1 Clears CommandCounter and CommandErrorCounter
Start Sequence 2 slot (u8), config_index (u8) Snapshots config from the given table row and spawns a Lua child task in slot
Stop Sequence 3 slot (u8) Signals the running child task in slot to exit

slot is 0-based and must be less than LSEQ_MAX_THREADS (4). Starting a slot that is already running is an error. Stopping a slot that is not running is a no-op.


Housekeeping Telemetry (LSEQ_HkTlm_MID)

The HK packet payload (LSEQ_HkTlm_Payload_t) contains:

Field Type Description
CommandCounter uint8 Count of successfully accepted commands
CommandErrorCounter uint8 Count of rejected or errored commands
seq[N].running uint8 Non-zero if slot N is active
seq[N].config_index uint8 Table row used to start slot N
seq[N].tick_count uint32 Number of times tick() has been called in slot N
seq[N].err_count uint32 Number of Lua errors caught in slot N

N ranges from 0 to LSEQ_MAX_THREADS - 1 (3).


Error Handling

Lua errors in both init and tick execution are caught with lua_pcall. On any error:

  1. err_count for the affected slot is incremented (visible in HK telemetry).
  2. If the script defines a global function named on_error(msg), it is called with the error message string.
  3. A cFS EVS error event is emitted if on_error() itself fails.
  4. The thread continues running — a single Lua error does not kill the slot.

The tick_count and err_count fields in HK provide a continuous health signal: a slot that has stopped incrementing tick_count while running remains set indicates a stall.

-- Recommended error handler in init_script
function on_error(msg)
    cfs.event(199, 4, "lseq error: " .. tostring(msg))
end

cfs.* Lua Extension Reference

All extensions are available in every slot under the cfs table.

Function Signature Returns Description
loadmsgs cfs.loadmsgs(path) count (int) Parses a CCDD JSON file and registers all messages in the global registry
subscribe cfs.subscribe(mnemonic) mid_value (int) Subscribes the slot's SB pipe to the named message's MID
on cfs.on(mnemonic, fn) Registers fn(fields) as the callback invoked whenever mnemonic is received; fields is a table of decoded payload values keyed by field name
field cfs.field(mnemonic, field_name) value Returns the most recently cached value of a named field from the last received packet; raises a Lua error if no sample has arrived
send cfs.send(mnemonic, fc [, field, val, ...]) fc (int) Builds and transmits a CCSDS packet; optional field/value pairs (must be even count) are written into the payload before transmission
time cfs.time(mnemonic) seconds, subseconds Returns the cFS timestamp of the last received packet for mnemonic; useful for staleness detection
now cfs.now() seconds, subseconds Returns the current cFS mission elapsed time from CFE_TIME_GetTime()
event cfs.event(event_id, event_type, message) Sends a cFS EVS event; prefer this over print() for flight-visible notifications

EVS event type constants (for use with cfs.event):

Constant Value
EVS_DEBUG 1
EVS_INFO 2
EVS_WARN 3
EVS_ERROR 4

Lua Debugger Setup (EmmyLua)

lseq supports interactive Lua debugging via the EmmyLuaDebugger shared library and the EmmyLua VS Code extension.

Build the Debugger Shared Library

Clone and build emmy_core.so against Lua 5.4:

rm -rf EmmyLuaDebugger/
git clone --recursive https://github.com/EmmyLua/EmmyLuaDebugger.git
cd EmmyLuaDebugger
mkdir build && cd build
cmake .. -DEMMY_LUA_VERSION=54 -DCMAKE_BUILD_TYPE=Release
cmake --build . --config Release

Known build fix: If the build fails with a missing CHAR_BIT or similar <climits> error, add the following include to emmy_debugger/src/api/lua_api_loader.cpp before rebuilding:

#include <climits>

Once built, copy emmy_core.so into the CPU1 load directory alongside your Lua scripts:

cp emmy_core.so /path/to/cFS/build/exe/cpu1/cf/

VS Code Configuration

Place a .vscode/ folder inside build/exe/cpu1/ with the following launch.json:

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "emmylua_new",
            "request": "launch",
            "name": "Attach to lseq",
            "host": "localhost",
            "port": 9966,
            "ext": [".lua"],
            "sourcePaths": ["${workspaceFolder}"],
            "ideConnectDebugger": true
        }
    ]
}

Enabling the Debugger in a Slot

Add the following block to the top of the slot's init_script to make the sequencer wait for VS Code to attach before executing:

if package then
    package.cpath = package.cpath .. ";cf/?.so"
    local ok, result = pcall(require, 'emmy_core')
    if ok then
        result.tcpListen('127.0.0.1', 9966)
        result.waitIDE()
    else
        cfs.event(100, 4, "emmy_core load failed: " .. tostring(result))
    end
end

Important: With this block in place, the slot will block at waitIDE() until the VS Code debugger connects. Make sure to launch the "Attach to lseq" debug configuration in VS Code before or immediately after starting the sequence, otherwise the slot will hang waiting for the connection.

Remove or comment out the block for normal (non-debug) operation.


Ground System Tools

Utility Scripts (cmdUtil)

Two bash scripts are provided in tools/cFS-GroundSystem/Subsystems/cmdUtil/ to start and stop sequence slots from the command line.

start_seq.sh — Start a slot using a given config table index:

#!/bin/bash
SLOT=${1}
CONFIG_INDEX=${2}
HALF=$(printf "0x%02X%02X" "$SLOT" "$CONFIG_INDEX")
./cmdUtil --host 127.0.0.1 --port 1234 \
    --pktid 0x1884 --cmdcode 2 \
    --half "$HALF" \
    --half 0x0000

Usage: ./start_seq.sh <slot> <config_index> — e.g. ./start_seq.sh 0 0

stop_seq.sh — Stop a running slot:

#!/bin/bash
SLOT=${1}
./cmdUtil --host 127.0.0.1 --port 1234 \
    --pktid 0x1884 --cmdcode 3 \
    --half $(printf "0x%02X00" "$SLOT") \
    --half 0x0000

Usage: ./stop_seq.sh <slot> — e.g. ./stop_seq.sh 0

Both scripts target 127.0.0.1:1234 (the default cFS-GroundSystem cmdUtil port) and use MID 0x1884 (LSEQ_CMD_MID).


Telemetry Display (tlmGUI)

To monitor lseq housekeeping telemetry in the cFS Ground System, add the following two files.

tools/cFS-GroundSystem/Subsystems/tlmGUI/lseq-hk-tlm.txt:

CommandErrorCounter,  12,  1,  B, Dec, NULL, NULL, NULL, NULL
CommandCounter,       13,  1,  B, Dec, NULL, NULL, NULL, NULL
seq0_running,         16,  1,  B, Dec, NULL, NULL, NULL, NULL
seq0_config_index,    17,  1,  B, Dec, NULL, NULL, NULL, NULL
seq0_tick_count,      20,  4,  I, Dec, NULL, NULL, NULL, NULL
seq0_err_count,       24,  4,  I, Dec, NULL, NULL, NULL, NULL
seq1_running,         28,  1,  B, Dec, NULL, NULL, NULL, NULL
seq1_config_index,    29,  1,  B, Dec, NULL, NULL, NULL, NULL
seq1_tick_count,      32,  4,  I, Dec, NULL, NULL, NULL, NULL
seq1_err_count,       36,  4,  I, Dec, NULL, NULL, NULL, NULL
seq2_running,         40,  1,  B, Dec, NULL, NULL, NULL, NULL
seq2_config_index,    41,  1,  B, Dec, NULL, NULL, NULL, NULL
seq2_tick_count,      44,  4,  I, Dec, NULL, NULL, NULL, NULL
seq2_err_count,       48,  4,  I, Dec, NULL, NULL, NULL, NULL
seq3_running,         52,  1,  B, Dec, NULL, NULL, NULL, NULL
seq3_config_index,    53,  1,  B, Dec, NULL, NULL, NULL, NULL
seq3_tick_count,      56,  4,  I, Dec, NULL, NULL, NULL, NULL
seq3_err_count,       60,  4,  I, Dec, NULL, NULL, NULL, NULL

Add to tools/cFS-GroundSystem/Subsystems/tlmGUI/telemetry-pages.txt:

LSEQ HK,    GenericTelemetry.py,    0x892,    lseq-hk-tlm.txt

This registers the LSEQ HK telemetry page (MID 0x892) in the Ground System telemetry browser, displaying all four slot status fields — running, config_index, tick_count, and err_count — in real time.


Repository Structure

cFS/
├── apps/
│   ├── lseq/                  # Lua sequence engine
│   ├── tam/                   # Telemetry & attitude manager
│   └── sch_lab/
│       └── fsw/tables/
│           └── sch_lab_table.c   # <-- Add lseq schedule slot here
├── sample_defs/
│   ├── targets.cmake             # <-- Add lseq, tam to MISSION_GLOBAL_APPLIST
│   └── cpu1_cfe_es_startup.scr   # <-- Add app startup entries here
├── Makefile
└── ...

License

The cFS framework is released under NASA's Open Source Agreement (NOSA) 1.3.
lseq and tam are proprietary — see each repository for license terms.

About

Lua based sequencing application

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors