Skip to content
Open
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
Binary file added docs/Final Report.pdf
Binary file not shown.
Binary file not shown.
35 changes: 35 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Documentation Index

This directory contains the primary documentation and final reporting for the OreSat C3 Scheduling Optimization project.

## Core Project Documentation

### Final Project Report
The comprehensive summary of the independent study, including executive summary, learning objectives, detailed chronological logs, hardware results, and self-grading discussion.

### The Scheduler Bible
The technical documentation for future OreSat engineers. It details the process classification tiers, resource budget math, systemd dependency graphs, and known architectural trade-offs like the "Bulkhead Paradox."

## supporting

Detailed technical reports generated during the discovery and design phases of the project can be found in the Supporting Documents folder. This includes:

#### State Analysis: Process Inventory & Source Code Audit

An initial audit of the OreSat Linux App Framework (OLAF). It identifies independent Linux processes, classifies internal Python threads by criticality, and documents the initial risks associated with GIL starvation and hardware watchdog timing.

#### Data Flow Mapping, IPC Analysis, and Vulnerability Determination

A deep dive into how data moves through the C3 card. This document maps external UDP sockets (Uplink/Downlink/Safety) and internal thread-based message passing via SimpleQueue. it formally defines the "Infinite Queue" and "Blocking I/O" scenarios as mission-critical vulnerabilities.

#### Resource Budget & Systemd Design

The foundation for the "Resource Cage." It details the investigation into Cgroups v2 controllers and establishes the 1GB RAM budget—specifically the decision to reserve ~600MB for the Kernel Page Cache to mitigate flash storage latency.

#### Scheduling Architecture Implementation

The technical log of the transition from design to active system configuration. It includes the logic behind the oresat-watchdog.service Real-Time settings and the oresat-c3.service resource limits required for development.

#### Environment Setup Log

A chronological record of establishing the OreSat development environment. It documents the installation of oresat-configs and oresat-c3-software, as well as the technical challenges and resolutions regarding custom WSL2 kernel compilation and Virtual CAN (vcan) support.
Binary file not shown.
Binary file added docs/supporting/Environment Setup.pdf
Binary file not shown.
Binary file added docs/supporting/Resource Budget & Systemd.pdf
Binary file not shown.
Binary file not shown.
Binary file not shown.
30 changes: 20 additions & 10 deletions oresat_c3/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,20 +96,30 @@ def watchdog():
updating = app.od["updater"]["status"].value == UpdaterState.UPDATING
edl = app.od["status"].value == C3State.EDL

if not performance and (updating or edl):
logger.info("setting cpufreq governor to performance mode")
set_cpufreq_gov("performance")
performance = True
elif performance and not updating and not edl:
logger.info("setting cpufreq governor to powersave mode")
set_cpufreq_gov("powersave")
performance = False
# PATCH: Wrap cpufreq calls in try/except for WSL/Desktop compatibility
try:
if not performance and (updating or edl):
logger.info("setting cpufreq governor to performance mode")
set_cpufreq_gov("performance")
performance = True
elif performance and not updating and not edl:
logger.info("setting cpufreq governor to powersave mode")
set_cpufreq_gov("powersave")
performance = False
except FileNotFoundError:
# WSL/Desktop environments do not have access to cpu governor files
pass
except Exception as e:
logger.error(f"Failed to set cpu governor: {e}")


def main():
"""OreSat C3 app main."""

set_system_time_to_rtc_time()
try:
set_system_time_to_rtc_time()
except Exception as e:
logger.error(f"Failed to set RTC time: {e}")

path = os.path.dirname(os.path.abspath(__file__))

Expand Down Expand Up @@ -150,4 +160,4 @@ def main():


if __name__ == "__main__":
main()
main()
18 changes: 9 additions & 9 deletions oresat_c3/services/beacon.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""'
"""
Beacon Service

Handles making the beacon packets.
Expand Down Expand Up @@ -60,14 +60,14 @@ def on_start(self):
self.node.add_sdo_callbacks("beacon", "last_timestamp", self._on_read_last_ts, None)

def on_loop(self):
if self._delay_obj.value <= 0:
self.sleep(1)
return # do nothing

if self._tx_enabled_obj.value and self._c3_state_obj.value == C3State.BEACON:
self.send()

self.sleep(self._delay_obj.value)
# --- MODIFICATION FOR JITTER TEST ---
# 1. Always Send (Ignore State)
self.send()
# 2. Force Fast Interval (1.0 Second)
# Original: self.sleep(self._delay_obj.value)
self.sleep(1.0)
# -------------------------------------------

def send(self):
"""Send a beacon now."""
Expand Down
18 changes: 18 additions & 0 deletions oresat_c3/services/oresat-c3.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[Unit]
Description=OreSat C3 Flight Software
After=network.target oresat-watchdog.service
Requires=oresat-watchdog.service

[Service]
WorkingDirectory=/home/debian/oresat-c3-software
ExecStart=/usr/bin/python3 -m oresat_c3 -m all
Restart=always
RestartSec=2

# --- RESOURCE LIMITS ---
CPUWeight=500
MemoryMax=256M
MemorySwapMax=0

[Install]
WantedBy=multi-user.target
40 changes: 20 additions & 20 deletions oresat_c3/services/radios.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"""

import socket
import threading
import time
from queue import SimpleQueue

from olaf import Gpio, Service, logger
Expand Down Expand Up @@ -49,9 +51,6 @@ def __init__(self, mock_hw: bool = False):
self._uhf_enable_gpio = Gpio("UHF_ENABLE", mock_hw)
self._lband_enable_gpio = Gpio("LBAND_ENABLE", mock_hw)

# si41xx synth info
self._relock_count = 0

# beacon downlink: UDP client
logger.info(f"Beacon socket: {self.BEACON_DOWNLINK_ADDR}")
self._beacon_downlink_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
Expand All @@ -68,6 +67,20 @@ def __init__(self, mock_hw: bool = False):

self.recv_queue: SimpleQueue[bytes] = SimpleQueue()

# --- BYPASS INJECTION (For Jitter Testing) ---
def bypass_beacon_loop():
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
try:
# Send pulse to port 10015 for jitter analysis
sock.sendto(b"BEACON_HEARTBEAT", ("127.0.0.1", 10015))
except:
pass
time.sleep(1.0)

threading.Thread(target=bypass_beacon_loop, daemon=True).start()
# -----------------------------------------------------

def on_start(self):
if not self._mock_hw:
self.node.add_daemon("lband")
Expand All @@ -81,7 +94,7 @@ def on_loop(self):
self.enable()
if not self.is_si41xx_locked:
logger.error("si41xx unlocked, resetting lband synth")
self._relock_count += 1
self._relock_count = getattr(self, '_relock_count', 0) + 1
self.node.od["lband"]["synth_relock_count"].value = self._relock_count.bit_length()
self._si41xx.stop()
self._si41xx.start()
Expand All @@ -94,7 +107,6 @@ def on_stop(self):

def enable(self):
"""Enable the radios."""

logger.info("enabling radios")
self._radio_enable_gpio.high()
self.sleep_ms(100)
Expand All @@ -103,15 +115,14 @@ def enable(self):
self._lband_enable_gpio.high()
self.uhf_tot_clear()
self._si41xx.start()
self._relock_count += 1
self._relock_count = getattr(self, '_relock_count', 0) + 1
self.node.od["lband"]["synth_relock_count"].value = self._relock_count.bit_length()
if not self._mock_hw:
self.node.daemons["uhf"].start()
self.node.daemons["lband"].start()

def disable(self):
"""Disable the radios."""

logger.info("disabling radios")
if not self._mock_hw:
self.node.daemons["uhf"].stop()
Expand All @@ -125,54 +136,43 @@ def disable(self):

def uhf_tot_clear(self):
"""Clear TOT."""

self._uhf_tot_clear_gpio.high()
self.sleep_ms(self.TOT_CLEAR_DELAY_MS)
self._uhf_tot_clear_gpio.low()

@property
def is_uhf_tot_okay(self) -> bool:
"""bool: check if the UHF TOT is okay."""

return bool(self._uhf_tot_ok_gpio.value)

@property
def is_si41xx_locked(self) -> bool:
"""bool: check if the si41xx is locked."""

# si41xx_nlock is active low
state = not bool(self._si41xx_nlock_gpio.value)
self.node.od["lband"]["synth_lock"].value = state
return state

def send_beacon(self, message: bytes):
"""Send a beacon."""

try:
self._beacon_downlink_socket.sendto(message, self.BEACON_DOWNLINK_ADDR)
except Exception as e: # pylint: disable=W0718
except Exception as e:
logger.error(f"failed to send beacon message: {e}")

logger.debug(f'Sent beacon downlink packet: {message.hex(sep=" ")}')

def _recv_edl_request(self) -> bytes:
"""Recieve an EDL packet."""

try:
message, _ = self._edl_uplink_socket.recvfrom(self.BUFFER_LEN)
except socket.timeout:
return b""

logger.debug(f'received EDL uplink packet: {message.hex(sep=" ")}')

return message

def send_edl_response(self, message: bytes):
"""Send an EDL packet."""

try:
self._edl_downlink_socket.sendto(message, self.EDL_DOWNLINK_ADDR)
except Exception as e: # pylint: disable=W0718
except Exception as e:
logger.error(f"failed to send mess over EDL downlink: {e}")

logger.debug(f'sent EDL downlink packet: {message.hex(sep=" ")}')
97 changes: 97 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Validation & Stress Testing Tools

This directory contains the scripts used to verify the performance and resilience of the updated C3 Scheduling Architecture.

## 1. Metric Collection

### ```measureJitter.py```

#### Function:
Listens for UDP beacons on port 10015 and calculates the microsecond-precision delta between packet arrivals.

#### Goal:
Proves that telemetry pulses remain consistent (low jitter) even when the system is under extreme load.

### Usage:
python3 measureJitter.py --seconds 600 --out results.csv

## 2. Resource Stressors

These scripts simulate worst-case software failures to test the kernel's response.

### ```stressCPU.py```

#### Function:
Spawns rogue processes to saturate all available CPU cores with infinite arithmetic loops.

#### Goal:
Verifies that the Watchdog (Priority 99) and Flight Software (Weight 500) successfully preempt background noise and maintain execution.

### ```stressMemory.py```

#### Function:
Floods the flight software's UDP ingress port (10025) with a high-volume stream of 1KB packets.

#### Goal:
Simulates a radio/network flood to trigger the "Infinite Queue" scenario, testing the MemoryMax resource limit.

### ```memoryBomb.py```

#### Function:
Memory allocator that writes to bytearray buffers to force Resident Set Size (RSS) accounting.

#### Goal:
Tries to trigger the Linux OOM (Out of Memory) Killer to prove the "Resource Cage" effectively isolates memory leaks and prevents system-wide crashes.

## 3. Deployment & Testing Helpers

### ```pureBeacon.py```

#### Function:
A "Pure" 1Hz UDP beacon generator.

#### Goal:
Used for baseline testing to remove the confounding variables of theflight software stack.

### ```modeController.sh```

#### Function:
A Bash shell script that automates the transition between testing modes.

#### Goal:
Reconfigures the oresat-c3.service to run either the production software, the OOM validation bomb, or the beacon test.

#### Usage:

chmod +x modeController.sh
./modeController.sh {oom|pure|restore}

## 4. Analysis Tools

Scripts used to transform raw data into visuals and stats.

### ```analysis/compareJitter.py```

#### Function:
Analyze legacy and optimized CSV files from the *results/jitter/* and *results/initial_validation* folder.

#### Goal:
Produces a summary table (Mean, StdDev, Max Spike) and a comparative boxplot illustrating how the optimization eliminates timing spikes during CPU saturation.

#### Usage:

python3 analysis/compareJitter.py

### ```analysis/IOSaturationAnalysis.py```

#### Function:

Analyze transient and sustained IO saturation CSV files from the *results/stability* folder.

#### Goal:

Produces a Distribution Curve (probability density) and a 10-Packet Rolling Jitter plot. This proves that timing standard deviation remains flat and does not degrade during sustained storage I/O blocks.

#### Usage:

python3 analysis/IOSaturationAnalysis.py
Loading
Loading