Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/samples/datalogger/prj.conf
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
CONFIG_CPP=y
CONFIG_REQUIRES_FULL_LIBCPP=y
CONFIG_STD_CPP20=y

CONFIG_ASAN=y
CONFIG_F_CORE=y
CONFIG_F_CORE_OS=y

CONFIG_F_CORE_BASE64_DATA_DUMP_SHELL=y
# outputs
CONFIG_SERIAL=y

Expand Down
2 changes: 2 additions & 0 deletions lib/f_core/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ config F_CORE_UTILS
bool "Utility"
help
This option enables utility functionality for F-Core

rsource "os/base64dump/Kconfig"
2 changes: 2 additions & 0 deletions lib/f_core/os/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Copyright (c) 2025 RIT Launch Initiative
# SPDX-License-Identifier: Apache-2.0

add_subdirectory_ifdef(CONFIG_F_CORE_BASE64_DATA_DUMP_SHELL base64dump)

zephyr_library()
FILE(GLOB sources *.cpp)
zephyr_library_sources(${sources})
Expand Down
4 changes: 4 additions & 0 deletions lib/f_core/os/base64dump/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
zephyr_library()
FILE(GLOB sources *.c)
zephyr_library_sources(${sources})

6 changes: 6 additions & 0 deletions lib/f_core/os/base64dump/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
config F_CORE_BASE64_DATA_DUMP_SHELL
bool "F Core base 64 data dump shell"
depends on FILE_SYSTEM
select BASE64
select SHELL
select FILE_SYSTEM_SHELL
76 changes: 76 additions & 0 deletions lib/f_core/os/base64dump/base64dump.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#include <zephyr/fs/fs.h>
#include <zephyr/shell/shell.h>
#include <zephyr/sys/base64.h>

// read from file into this
// chosen so only the last line will pad with = when there is not enough data for a full line
// all previous lines will not need padding so the entire stream can be reassembled and decoded in one go
// chosen to be 60 to fit on 80 column terminal when encoded

#define READING_BUFFER_LEN 60
uint8_t reading_data[READING_BUFFER_LEN];

// dump base64 data here before printing
// overhead of (8 bits/byte -> 6 bits/byte) plus padding
#define DUMP_BUFFER_LEN ((READING_BUFFER_LEN * 4 / 3) + 2)
uint8_t dumping_data[DUMP_BUFFER_LEN];
static int dump_base64(const struct shell *shell, const char *fname) {
struct fs_dirent dirent;
int ret = fs_stat(fname, &dirent);
if (ret < 0) {
shell_print(shell, "Error getting file size: %d", ret);
return ret;
}
shell_print(shell, "Size: %zu", dirent.size);

struct fs_file_t file;
fs_file_t_init(&file);

ret = fs_open(&file, fname, FS_O_READ);
if (ret < 0) {
shell_print(shell, "Failed to open %s: %d", fname, ret);
return ret;
}

int read = READING_BUFFER_LEN;

while (read == READING_BUFFER_LEN) {
read = fs_read(&file, reading_data, READING_BUFFER_LEN);
if (read < 0) {
shell_print(shell, "Failed file read of %s", fname);
return -1;
}
int num_written = 0;
ret = base64_encode(dumping_data, DUMP_BUFFER_LEN, &num_written, reading_data, read);
if (ret < 0) {
shell_print(shell, "encoding error %d, num_written: %d, read: %d", ret, num_written, read);
return ret;
}

shell_print(shell, "%.*s", DUMP_BUFFER_LEN, dumping_data);
}
fs_close(&file);
return 0;
}
static int cmd_read_file(const struct shell *shell, size_t argc, char **argv) {
ARG_UNUSED(argc);
ARG_UNUSED(argv);
if (argc != 2) {
shell_print(shell, "usage: fdd dump filename: dump specific file");
return -1;
}

return dump_base64(shell, argv[1]);
}
static int cmd_tree(const struct shell *shell, size_t argc, char **argv) {
ARG_UNUSED(argc);
ARG_UNUSED(argv);

shell_print(shell, "Print tree");
return 0;
}

SHELL_STATIC_SUBCMD_SET_CREATE(fdd_subcmds, SHELL_CMD(tree, NULL, "Show Tree of filesystem", cmd_tree),
SHELL_CMD(read, NULL, "Read file.", cmd_read_file), SHELL_SUBCMD_SET_END);

SHELL_CMD_REGISTER(fdd, &fdd_subcmds, "Flight data dumper commands", NULL);
Empty file.
113 changes: 113 additions & 0 deletions tools/flight_data_downloader/fdd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import signal

from fdd_transport import FDDTransport
from serial_transport import SerialTransport, print_serial_help
from tftp_transport import TFTPTransport, print_tftp_help
from print_colors import print_red, print_green

transport = FDDTransport()

def handle_set_command(args):
global transport

attribute = args[0]
args = args[1:]

if attribute == "transport":
if args[0] == "tftp":
transport = TFTPTransport()
print_green("Set to TFTP.")
elif args[0] == "serial":
transport = SerialTransport()
print_green("Set to serial.")
else:
print_red("Invalid transport")
return

if attribute == "output":
if len(args) == 1:
transport.set_output_folder(args[0])
print_green("Set output folder to {}".format(args[0]))
elif len(args) == 0: # No folder specified
print_red("Output folder not specified")
else: # Spaces in folder name
print_red("Invalid output folder")

return

if transport is not None and not transport is FDDTransport:
transport.set_attribute(attribute, args)
return
else:
print_red("Transport not set")

def handle_tree_command():
if transport is not None:
transport.tree()


def handle_download_command(args):
if transport is not None:
for file in args:
transport.download(file)


def handle_exit_command(args):
exit(0)


def clear_screen():
print("\033[H\033[J")


def print_help():
print("General commands:")
print("\tset transport <tftp|serial> - Set the transport method")
print("\tset output <folder> - Set the output folder")
print("\ttree - Display the file tree")
print("\tdownload <file> - Download a file")
print("\tclear - Clear the screen")
print("\texit - Exit the program")
print("\thelp - Display this help message")

print_tftp_help()
print_serial_help()


def signal_handler(sig, frame):
if sig == signal.SIGINT:
handle_exit_command([])


def main():
signal.signal(signal.SIGINT, signal_handler)

print_help()

while True:
print(str(transport) + ">> ", end="")
user_input = input().lower()
command = user_input.split(" ")[0]
arguments = user_input.split(" ")[1:]

match command:
case "set":
handle_set_command(arguments)
case "tree":
handle_tree_command()
case "download":
handle_download_command(arguments)
case "clear":
clear_screen()
case "help":
print_help()
case "exit":
handle_exit_command(arguments)
case "":
continue
case _:
print_red("Invalid command")


if __name__ == '__main__':
main()
43 changes: 43 additions & 0 deletions tools/flight_data_downloader/fdd_transport.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import os
from print_colors import print_red

current_output_folder = os.getcwd()

class FDDTransport:

def __init__(self):
pass

def _get_file(self, file: str) -> bytes:
print_red("FDDTransport is an abstract class")

def tree(self):
contents = self._get_file("tree")
if contents is not None:
print(contents)
else:
print_red("Error downloading tree file.")

def download(self, file: str):
file_name = file.split("/")[-1]
out_path = os.path.join(current_output_folder, file_name)

with open(out_path, "wb") as fout:
contents = self._get_file(file)
if contents is not None:
fout.write(contents)
else:
print_red("Error downloading file")

def set_output_folder(self, folder: str):
global current_output_folder
current_output_folder = os.getcwd() + "/" + folder

if not os.path.exists(folder):
os.makedirs(folder)

def set_attribute(self, attribute, args: list):
print_red("FDDTransport is an abstract class")

def __str__(self):
return "unset"
5 changes: 5 additions & 0 deletions tools/flight_data_downloader/print_colors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
def print_red(text):
print("\033[91m" + text + "\033[0m")

def print_green(text):
print("\033[92m" + text + "\033[0m")
78 changes: 78 additions & 0 deletions tools/flight_data_downloader/serial_transport.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from fdd_transport import FDDTransport
from print_colors import print_red, print_green
import serial
import base64
from math import ceil

def print_serial_help():
print("Serial commands:")
print("\tport <port> - Set the serial port")
print("\tbaud <baud_rate> - Set the baud rate")

class SerialTransport(FDDTransport):
__slots__ = ["__serial_port", "__baud_rate"]

def __init__(self):
super().__init__()
self.__serial_port = None
self.__baud_rate = 115200

def set_serial_port(self, serial_port: str):
self.__serial_port = serial_port

def set_baud_rate(self, baud_rate: int):
self.__baud_rate = baud_rate

def _get_file(self, file: str) -> bytes:
if self.__serial_port is None:
print_red("Serial port not set")

with serial.Serial(port = self.__serial_port, baudrate = self.__baud_rate) as ser:
ser.timeout = 0.5 # seconds
ser.write('shell echo off\n'.encode())
_ = ser.readlines() # flush out shell echo before beginning
if not file.startswith('/'):
# zephyr files must start with leading slash
file = '/' + file

# Request the data
ser.write(f"fdd read {file}\n".encode())

ser.readline() # Skip blank newline

# Line containing file size or error message
sizeorerror = ser.readline().decode()
if sizeorerror.startswith("Size: "):
file_size = int(sizeorerror.strip().replace('Size: ', ''))
else:
print_red(f"Error Reading: {sizeorerror}")
return None

data = bytearray()
while True:
l = ser.readline()
if 'uart:~$'.encode() in l or len(l) == 0:
break

dec = base64.decodebytes(l.strip())
data.extend(dec)
if file_size != len(data):
print_red(f"Error decoding expected length != actual length ({file_size}, {len(data)})")
return None
return data

def set_attribute(self, attribute, args: list):
if args[0] == "port":
self.set_serial_port(args[1])
print_green("Serial port set to {}".format(self.__serial_port))
elif args[0] == "baud":
self.set_baud_rate(int(args[1]))
print_green("Serial port set to {}".format(self.__serial_port))
else:
print_red("Invalid argument(s).")

def __str__(self):
return "serial"



Loading