Files
Kevin Thomas 35eacd2c0e Course update: lessons, CTF 0x0011a_cb, and documentation
- 0x0011a_cb (Operation Dark Vector): nation-state CTF redesign with an
  AES-128-ECB sealed target and a plaintext launch origin; RP2350 firmware with
  bearing-driven servo, tri-color LEDs, GSV stats, and a realistic no-fix path
- docs: story-driven classified brief, GDB and Ghidra tutorials with deep
  step-throughs, regenerated artifacts and PDFs
- scripts: docstring standard, AES per-student randomizer, telemetry monitor
- week 3 to week 5 lessons: Ghidra patching tutorial, CMSIS-SVD hardware RE,
  double floating-point and GPIO architecture chapters, README structure
2026-09-27 14:18:56 -04:00

303 lines
7.4 KiB
Python

#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2026 Kevin Thomas
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# Author: Kevin Thomas
# Email: kevin@mytechnotalent.com
# GitHub: https://github.com/mytechnotalent
# File: telemetry_monitor.py
# Desc: Real-time telemetry monitor for Operation Dark Vector.
# Created: 2026
"""
Real-time telemetry monitor for Operation Dark Vector.
Reads live avionics and GPS telemetry from the FT232RL USB-to-UART ground
station bridge, parses coordinates and distance, and prints a mission status
dashboard to the terminal.
"""
import argparse
import re
import sys
import time
try:
import serial
except ImportError:
serial = None
def _format_coord(val: float, pos_c: str, neg_c: str) -> str:
"""
Format a decimal coordinate with cardinal direction.
Parameters
----------
val : float
Coordinate value in degrees.
pos_c : str
Cardinal letter for positive values.
neg_c : str
Cardinal letter for negative values.
Returns
-------
str
Formatted coordinate string.
"""
card = pos_c if val >= 0.0 else neg_c
return f"{abs(val):.6f} deg {card}"
def _format_pos(lat: float, lon: float) -> str:
"""
Format combined latitude and longitude string.
Parameters
----------
lat : float
Latitude coordinate.
lon : float
Longitude coordinate.
Returns
-------
str
Formatted dual coordinate string.
"""
lat_s = _format_coord(lat, 'N', 'S')
lon_s = _format_coord(lon, 'E', 'W')
return f"{lat_s} {lon_s}"
def _format_hud_rows(
cur_lat: float,
cur_lon: float,
is_rel: bool,
has_lock: bool = False
) -> list[str]:
"""
Format HUD data rows.
Parameters
----------
cur_lat : float
Current UAV latitude.
cur_lon : float
Current UAV longitude.
is_rel : bool
Whether payload has released.
has_lock : bool
Whether active GNSS 3D lock has been acquired.
Returns
-------
list[str]
List of formatted box rows.
"""
if cur_lat == 0.0 and cur_lon == 0.0:
c_s = "0.000000 deg N 0.000000 deg E"
g_s = "SEARCHING SATELLITES"
m_s = "MOTOR STOPPED [WAITING FOR 3D LOCK]"
else:
c_s = _format_pos(cur_lat, cur_lon)
g_s = "ACTIVE 3D LOCK" if has_lock else "SEARCHING SATELLITES"
m_s = "ACTIVE PROPULSION [SERVO SPINNING]" if has_lock else "MOTOR STOPPED [WAITING FOR 3D LOCK]"
return [
f"| CURRENT POSITION : {c_s:<44} |",
f"| GNSS SUBSYSTEM : {g_s:<44} |",
f"| PROPULSION MOTOR : {m_s:<44} |"
]
def _print_box(title: str, link: str, rows: list[str]) -> None:
"""
Print framed ASCII box.
Parameters
----------
title : str
Box title line.
link : str
Sub-header line.
rows : list[str]
Body content rows.
Returns
-------
None
"""
hdr = f"+{'-' * 65}+"
print(hdr)
print(title)
print(link)
print(hdr)
for r in rows:
print(r)
print(hdr + "\n", flush=True)
def _print_hud(
cur_lat: float,
cur_lon: float,
is_released: bool,
has_lock: bool = False
) -> None:
"""
Display the 67-character telemetry mission HUD.
Parameters
----------
cur_lat : float
Current UAV latitude.
cur_lon : float
Current UAV longitude.
is_released : bool
Whether payload solenoid has been energized.
has_lock : bool
Whether active GNSS 3D lock has been acquired.
Returns
-------
None
"""
rows = _format_hud_rows(cur_lat, cur_lon, is_released, has_lock)
title = f"|{'DARK VECTOR TELEMETRY CONSOLE':^65}|"
status = f"{'STATUS: ONLINE':>20}"
link = f"| LINK: FT232RL / RYLR998 LORA GROUND STATION{status} |"
_print_box(title, link, rows)
def _run_demo() -> None:
"""
Execute simulation of drone telemetry stream.
Parameters
----------
None
Returns
-------
None
"""
print("[*] Running simulated ground station telemetry stream...\n")
_print_hud(0.0, 0.0, False, False)
time.sleep(1.0)
_print_hud(38.840280, -77.428890, False, True)
time.sleep(1.0)
_print_hud(38.861110, -77.439585, False, True)
time.sleep(1.0)
_print_hud(38.881940, -77.450280, True, True)
def _process_line(
line: str,
coords: dict[str, any]
) -> bool:
"""
Parse a single line of serial telemetry using regex.
Parameters
----------
line : str
Raw serial string.
coords : dict[str, any]
State dictionary of coordinates.
Returns
-------
bool
True if telemetry data was updated, False otherwise.
"""
updated = False
m_cur = re.search(r"CURRENT LAT:\s*([-+]?\d*\.?\d+).*?LON:\s*([-+]?\d*\.?\d+)", line)
if m_cur:
coords["cur_lat"] = float(m_cur.group(1))
coords["cur_lon"] = float(m_cur.group(2))
coords["has_lock"] = True
updated = True
if "PAYLOAD RELEASED" in line:
coords["released"] = 1.0
updated = True
return updated
def _monitor_serial(port: str, baud: int) -> None:
"""
Monitor serial stream from FT232RL ground station.
Parameters
----------
port : str
Serial device path.
baud : int
Baud rate.
Returns
-------
None
"""
if serial is None:
sys.exit("pyserial required: pip install pyserial")
coords = {
"cur_lat": 0.0,
"cur_lon": 0.0,
"released": 0.0,
"has_lock": False
}
with serial.Serial(port, baud, timeout=1.0) as ser:
while True:
raw = ser.readline().decode("utf-8", errors="ignore").strip()
if raw:
_process_line(raw, coords)
rel = coords["released"] > 0.5
_print_hud(coords["cur_lat"], coords["cur_lon"], rel, coords["has_lock"])
def main() -> None:
"""
Parse arguments and start telemetry monitor.
Parameters
----------
None
Returns
-------
None
"""
parser = argparse.ArgumentParser(description="Dark Vector Telemetry")
parser.add_argument("--port", default="/dev/tty.usbserial-0001")
parser.add_argument("--baud", type=int, default=115200)
parser.add_argument("--demo", action="store_true")
args = parser.parse_args()
if args.demo:
_run_demo()
return
_monitor_serial(args.port, args.baud)
if __name__ == "__main__":
main()